Supabase Auth email with SuperSend TX

Send Supabase signup, magic link, password reset, and invite emails through SuperSend TX using the Send Email auth hook and an Edge Function.

For hook-based delivery (Edge Functions), use the HTTP API (POST /emails) from your handler — the same pattern Supabase documents for other ESPs.


When to use this

  • You built on Supabase Auth (often via Lovable, Vite, or Next.js) and want branded mail on your domain
  • You outgrew Supabase’s built-in SMTP limits or need delivery visibility in SuperSend TX
  • You want one transactional provider for auth mail and product mail (receipts, alerts)

Architecture

  1. User triggers auth (signup, reset, magic link, etc.)
  2. Supabase Auth calls your Send Email hook (HTTPS Edge Function)
  3. The function verifies the webhook signature, builds the message, and calls POST https://api.supersendtx.com/emails
  4. Return 200 with {} so Supabase knows the email was handled

Reference: Supabase Send Email Hook.


Prerequisites

  1. SuperSend TX account + stx_… API key
  2. Verified sending domain (or Sandbox for self-tests only — see Quickstart)
  3. Supabase project with the Supabase CLI installed

1. Create the Edge Function

supabase functions new send-email

supabase/functions/send-email/index.ts:

import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0'

const hookSecret = (Deno.env.get('SEND_EMAIL_HOOK_SECRET') ?? '').replace(/^v1,whsec_/, '')
const apiKey = Deno.env.get('SUPERSENDTX_API_KEY')
const fromEmail = Deno.env.get('SUPERSENDTX_FROM_EMAIL') ?? 'noreply@yourdomain.com'
const projectRef = Deno.env.get('SUPABASE_PROJECT_REF')

const subjects: Record<string, string> = {
  signup: 'Confirm your email',
  recovery: 'Reset your password',
  invite: 'You have been invited',
  magiclink: 'Your sign-in link',
  email_change: 'Confirm your new email',
  email_change_new: 'Confirm your new email',
  reauthentication: 'Your verification code',
}

function confirmationUrl(emailData: {
  token_hash: string
  email_action_type: string
  redirect_to: string
}) {
  const base = `https://${projectRef}.supabase.co/auth/v1/verify`
  const params = new URLSearchParams({
    token: emailData.token_hash,
    type: emailData.email_action_type,
    redirect_to: emailData.redirect_to,
  })
  return `${base}?${params.toString()}`
}

Deno.serve(async (req) => {
  if (req.method !== 'POST') {
    return new Response('method not allowed', { status: 405 })
  }
  if (!apiKey || !hookSecret || !projectRef) {
    return new Response('server misconfigured', { status: 500 })
  }

  const payload = await req.text()
  const headers = Object.fromEntries(req.headers)

  try {
    const wh = new Webhook(hookSecret)
    const { user, email_data } = wh.verify(payload, headers) as {
      user: { email: string }
      email_data: {
        token: string
        token_hash: string
        redirect_to: string
        email_action_type: string
      }
    }

    const action = email_data.email_action_type
    const subject = subjects[action] ?? 'Notification'
    const link = confirmationUrl(email_data)

    const html =
      action === 'reauthentication'
        ? `<p>Your verification code is <strong>${email_data.token}</strong></p>`
        : `<p><a href="${link}">${subject}</a></p>`

    const text =
      action === 'reauthentication'
        ? `Your verification code is ${email_data.token}`
        : `${subject}: ${link}`

    const res = await fetch('https://api.supersendtx.com/emails', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${apiKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        from: fromEmail,
        to: user.email,
        subject,
        html,
        text,
      }),
    })

    if (!res.ok) {
      const body = await res.text()
      throw new Error(`SuperSend TX ${res.status}: ${body}`)
    }
  } catch (error) {
    console.error(error)
    return new Response(JSON.stringify({ error: String(error) }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' },
    })
  }

  return new Response(JSON.stringify({}), {
    status: 200,
    headers: { 'Content-Type': 'application/json' },
  })
})

Customize subjects and HTML (or render React Email in the function) for production.


2. Set secrets and deploy

supabase/functions/.env (local) — do not commit:

SUPERSENDTX_API_KEY=stx_your_key_here
SUPERSENDTX_FROM_EMAIL=noreply@yourdomain.com
SUPABASE_PROJECT_REF=your-project-ref
SEND_EMAIL_HOOK_SECRET=v1,whsec_...   # from Supabase dashboard after step 3
supabase secrets set --env-file supabase/functions/.env
supabase functions deploy send-email --no-verify-jwt

Note the deployed function URL (e.g. https://<project-ref>.supabase.co/functions/v1/send-email).


3. Enable the Send Email hook

Alternative: If you prefer Supabase’s built-in SMTP settings UI, create an SMTP credential in SuperSend TX (SMTP in the dashboard) and configure custom SMTP in Supabase with host smtp.supersendtx.com, port 587, username supersendtx, and your credential password. Skip the hook steps below.

In Supabase Dashboard → Authentication → Hooks:

  1. Add Send Email hook
  2. Type: HTTPS
  3. URL: your Edge Function URL
  4. Generate secret — copy the v1,whsec_… value into SEND_EMAIL_HOOK_SECRET
  5. Save

Redeploy or update secrets if you generated the secret after the first deploy.

While the hook is enabled, Supabase does not send auth mail itself — your function must succeed and return 200 + {}.


4. Auth email types

Handle every email_action_type your app uses:

email_action_type Typical use
signup Email confirmation
recovery Password reset
magiclink Passwordless sign-in
invite User invite
email_change Confirm email change
email_change_new Confirm new address (secure email change)
reauthentication Step-up OTP

If the hook is on and a type is missing from your templates, that mail can fail silently.


5. Sandbox vs production

Stage from to
Sandbox (no domain verified) noreply@mail.supersendtx.com (or your sandbox domain) Your SuperSend TX account email only
Production noreply@yourdomain.com Any recipient

Verify your domain in SuperSend TX before switching SUPERSENDTX_FROM_EMAIL.


6. Webhooks (optional)

Subscribe to email.delivered, email.bounced, and email.complained in SuperSend TX to monitor auth mail health. See Webhooks.


Lovable / AI builders

If you generated the app in Lovable, store SUPERSENDTX_API_KEY in project secrets and ask the agent to wire this hook — see Lovable + SuperSend TX.