Skip to main content

Send Email Hook Setup Guide

Overview

This Send Email Hook replaces Supabase’s default email templates with SendGrid’s dynamic templates, providing platform-specific email handling for mobile and web applications.

Features

  • Platform detection (mobile vs web)
  • SendGrid dynamic templates
  • Conditional email content (code-only for mobile, link-only for web)
  • Support for all 6 Supabase auth email types: signup, recovery, magic link, email change, invite, and reauthenticate emails

Setup Steps

1. Environment Variables

Add these environment variables to your Supabase project:
# SendGrid API Key
SENDGRID_API_KEY=your_sendgrid_api_key_here

# Hook secret (generate in Supabase Dashboard > Auth > Hooks)
SEND_EMAIL_HOOK_SECRET=v1,whsec_your_base64_secret_here

# Supabase Anonymous Key (already available in your environment)
SUPABASE_ANON_KEY=your_supabase_anon_key_here

2. SendGrid Template IDs

The current template IDs configured in the system:
const SENDGRID_TEMPLATES = {
  signup: "d-f616cf918fea42b2ac48870b0953154b", // Confirm signup
  recovery: "d-4264768063af464bb138244a4cfc011e", // Reset password
  magiclink: "d-615be9bd4d34493f80a7c7ba283516a3", // Magic link login
  email_change: "d-your-email-change-template-id", // TODO: Configure email change template
  invite: "d-6f15a805896e468fb8ad74cb69d62c5f", // Invite user
  reauthenticate: "d-your-reauthenticate-template-id", // TODO: Configure reauthenticate template
};
Note: The email_change and reauthenticate templates still need to be configured with actual SendGrid template IDs.

3. SendGrid Dynamic Template Setup

Create dynamic templates in SendGrid with these variables:

Available Variables for Templates

Common Variables (Available in All Templates):
{{first_name}}
<!-- User's first name (only present if available) -->
{{email}}
<!-- User's email address -->
{{platform}}
<!-- "mobile" or "web" -->
{{token}}
<!-- 6-digit OTP code -->
{{confirmation_url}}
<!-- Full confirmation URL with /auth/v1/verify endpoint -->
{{site_url}}
<!-- Base site URL -->
{{action_type}}
<!-- Email action type -->
{{subject}}
<!-- Email subject line -->
Template-Specific Variables:
  • Signup Template (d-f616cf918fea42b2ac48870b0953154b)
    • {{subject}} → “Confirm Your Signup - ADHD Simple”
  • Recovery Template (d-4264768063af464bb138244a4cfc011e)
    • {{subject}} → “Reset Your Password - ADHD Simple”
  • Magic Link Template (d-615be9bd4d34493f80a7c7ba283516a3)
    • {{subject}} → “Magic Link Login - ADHD Simple”
  • Invite Template (d-6f15a805896e468fb8ad74cb69d62c5f)
    • {{subject}} → “You’ve Been Invited - ADHD Simple”
  • Email Change Template (TODO)
    • {{subject}} → “Confirm Email Change - ADHD Simple”
    • {{new_email}} → New email address (if applicable)
  • Reauthenticate Template (TODO)
    • {{subject}} → “Security Verification Required - ADHD Simple”
Platform Detection:
  • {{platform}} will be “mobile” or “web”
  • Use {{#equals platform "mobile"}} for conditional content in SendGrid templates
  • Use {{#if first_name}} for checking if variables exist

Template Examples

Signup Template:
<h2>{{subject}}</h2>

<p>Hi {{#if first_name}}{{first_name}}{{else}}there{{/if}},</p>

{{#equals platform "mobile"}}
  <!-- Mobile: Show code only -->
  <p style="font-size: 16px; margin-bottom: 10px; color: #333;">Enter this code
    in the app:</p>
  <div
    style="font-size: 32px; font-weight: bold; text-align: center; color: #3C93CD; letter-spacing: 4px; font-family: 'Courier New', monospace; padding: 20px; background: #f8f9fa; border: 2px solid #3C93CD; border-radius: 8px;"
  >
    {{token}}
  </div>
  <p style="font-size: 14px; color: #666; margin-top: 10px;">This code expires
    in 1 hour.</p>
{{else}}
  <!-- Web: Show button with link -->
  <p>Follow this link to confirm your signup:</p>
  <div style="text-align: center; margin: 20px 0;">
    <a
      href="{{confirmation_url}}"
      style="background-color: #3C93CD; color: white; padding: 12px 44px; text-decoration: none; border-radius: 100px; font-weight: bold; letter-spacing: 1px; display: inline-block;"
    >
      VERIFY EMAIL
    </a>
  </div>
{{/equals}}

<p>Welcome to ADHD Simple!</p>
Recovery Template:
<h2>{{subject}}</h2>

<p>Hi {{#if first_name}}{{first_name}}{{else}}there{{/if}},</p>

{{#equals platform "mobile"}}
  <!-- Mobile: Show code only -->
  <p style="font-size: 16px; margin-bottom: 10px; color: #333;">Enter this code
    in the app to reset your password:</p>
  <div
    style="font-size: 32px; font-weight: bold; text-align: center; color: #ff9800; letter-spacing: 4px; font-family: 'Courier New', monospace; padding: 20px; background: #f8f9fa; border: 2px solid #ff9800; border-radius: 8px;"
  >
    {{token}}
  </div>
  <p style="font-size: 14px; color: #666; margin-top: 10px;">This code expires
    in 1 hour.</p>
{{else}}
  <!-- Web: Show button with link -->
  <p>Click this link to reset your password:</p>
  <div style="text-align: center; margin: 20px 0;">
    <a
      href="{{confirmation_url}}"
      style="background-color: #ff9800; color: white; padding: 12px 44px; text-decoration: none; border-radius: 100px; font-weight: bold; letter-spacing: 1px; display: inline-block;"
    >
      RESET PASSWORD
    </a>
  </div>
{{/equals}}
Magic Link Template:
<h2>{{subject}}</h2>

<p>Hi {{#if first_name}}{{first_name}}{{else}}there{{/if}},</p>

{{#equals platform "mobile"}}
  <!-- Mobile: Show code only -->
  <p style="font-size: 16px; margin-bottom: 10px; color: #333;">Use this code to
    sign in to the app:</p>
  <div
    style="font-size: 32px; font-weight: bold; text-align: center; color: #2196f3; letter-spacing: 4px; font-family: 'Courier New', monospace; padding: 20px; background: #f8f9fa; border: 2px solid #2196f3; border-radius: 8px;"
  >
    {{token}}
  </div>
  <p style="font-size: 14px; color: #666; margin-top: 10px;">This code expires
    in 1 hour.</p>
{{else}}
  <!-- Web: Show button with link -->
  <p>Click this magic link to sign in:</p>
  <div style="text-align: center; margin: 20px 0;">
    <a
      href="{{confirmation_url}}"
      style="background-color: #2196f3; color: white; padding: 12px 44px; text-decoration: none; border-radius: 100px; font-weight: bold; letter-spacing: 1px; display: inline-block;"
    >
      SIGN IN
    </a>
  </div>
{{/equals}}
Invite Template:
<h2>{{subject}}</h2>

<p>Hi {{#if first_name}}{{first_name}}{{else}}there{{/if}},</p>

{{#equals platform "mobile"}}
  <!-- Mobile: Show code only -->
  <p style="font-size: 16px; margin-bottom: 10px; color: #333;">You've been
    invited to join ADHD Simple! Use this code in the mobile app:</p>
  <div
    style="font-size: 32px; font-weight: bold; text-align: center; color: #9c27b0; letter-spacing: 4px; font-family: 'Courier New', monospace; padding: 20px; background: #f8f9fa; border: 2px solid #9c27b0; border-radius: 8px;"
  >
    {{token}}
  </div>
  <p style="font-size: 14px; color: #666; margin-top: 10px;">This code expires
    in 1 hour.</p>
{{else}}
  <!-- Web: Show button with link -->
  <p>You've been invited to join ADHD Simple! Click to accept:</p>
  <div style="text-align: center; margin: 20px 0;">
    <a
      href="{{confirmation_url}}"
      style="background-color: #9c27b0; color: white; padding: 12px 44px; text-decoration: none; border-radius: 100px; font-weight: bold; letter-spacing: 1px; display: inline-block;"
    >
      ACCEPT INVITATION
    </a>
  </div>
{{/equals}}

4. Deploy the Function

# Set environment variables in Supabase
supabase secrets set SENDGRID_API_KEY=your_key_here
supabase secrets set SEND_EMAIL_HOOK_SECRET=your_secret_here

# Deploy the function
supabase functions deploy send-email --no-verify-jwt

5. Configure Auth Hook

  1. Go to Supabase Dashboard > Auth > Hooks
  2. Create a new “Send Email” hook
  3. Set the URL to: https://your-project-id.supabase.co/functions/v1/send-email
  4. Generate and copy the hook secret to your environment variables (format: v1,whsec_base64_secret)

6. Platform Detection Logic

The hook determines platform based on (in priority order):
  1. User metadata: user.user_metadata.platform === "mobile"
  2. App metadata: user.app_metadata.platform === "mobile"
  3. Redirect URL patterns:
    • Mobile indicators: adhdsimple://, exp://, /mobile/, app.adhdsimple.co.uk
    • Default: web

7. URL Construction

The confirmation URLs are built with the correct Supabase auth endpoint:
https://your-project-id.supabase.co/auth/v1/verify?token=TOKEN_HASH&type=EMAIL_TYPE&redirect_to=ENCODED_REDIRECT_URL
Important: No apikey parameter is needed - Supabase handles this internally.

8. Testing

Test different email types:
# Signup
curl -X POST 'https://your-project-id.supabase.co/auth/v1/signup' \
  -H 'apikey: your-anon-key' \
  -H 'Content-Type: application/json' \
  -d '{"email": "test@example.com", "password": "password123"}'

# Recovery
curl -X POST 'https://your-project-id.supabase.co/auth/v1/recover' \
  -H 'apikey: your-anon-key' \
  -H 'Content-Type: application/json' \
  -d '{"email": "test@example.com"}'

# Magic Link
curl -X POST 'https://your-project-id.supabase.co/auth/v1/magiclink' \
  -H 'apikey: your-anon-key' \
  -H 'Content-Type: application/json' \
  -d '{"email": "test@example.com"}'

# Invite User (requires service role key)
curl -X POST 'https://your-project-id.supabase.co/auth/v1/invite' \
  -H 'apikey: your-service-role-key' \
  -H 'Content-Type: application/json' \
  -d '{"email": "test@example.com"}'

Platform-Specific Behavior

Mobile Apps

  • Shows only the 6-digit verification code
  • Code expires in 1 hour
  • No clickable links (users enter code manually)
  • Platform detected via user metadata or redirect URL patterns

Web Apps

  • Shows only confirmation button/link
  • No verification code displayed
  • Standard email confirmation flow with clickable links
  • Users are redirected after clicking the link

Troubleshooting

Common Issues

  1. “No API key found in request” error:
    • Fixed by ensuring URL uses /auth/v1/verify endpoint (not /auth/v1)
    • No apikey parameter needed in the URL
  2. Hook secret format: Must start with v1,whsec_
  3. Template IDs: Must start with d- and be valid SendGrid dynamic template IDs
  4. Platform detection: Check user metadata and redirect URL patterns
  5. SendGrid templates: Ensure Handlebars syntax is correct (use {{#equals platform "mobile"}} for comparisons, {{#if first_name}} for existence checks)

Logs

Check function logs:
supabase functions logs send-email --follow

Testing Platform Detection

Set platform explicitly in user metadata:
// Mobile signup
supabase.auth.signUp({
  email: "test@example.com",
  password: "password123",
  options: {
    data: {
      platform: "mobile",
    },
  },
});

// Web signup (default)
supabase.auth.signUp({
  email: "test@example.com",
  password: "password123",
});

Configuration Status

Configured and Working:
  • Signup emails
  • Recovery emails
  • Magic link emails
  • Invite emails
⚠️ Needs Configuration:
  • Email change emails (template ID needed)
  • Reauthenticate emails (template ID needed)

Next Steps

  1. Create SendGrid templates for email_change and reauthenticate actions
  2. Update the template IDs in packages/supabase/functions/send-email/index.ts
  3. Test all email flows in both mobile and web contexts
  4. Monitor function logs for any issues