Recipe: Supabase Auth → Notify (Send Email Hook)
Route Supabase Auth emails (password reset, magic link, confirm signup) through Notify using the Send Email Auth Hook — no SMTP required.
Supabase Auth’s Custom SMTP settings need a real SMTP host. Notify is HTTPS-only (POST /api/email/send), so Custom SMTP cannot point at Notify.
Use Supabase’s Send Email Hook instead: Auth calls your Edge Function; the function verifies the webhook signature and sends with Notify.
That covers password reset, magic link, signup confirm, invite, and email-change messages.
Prerequisites
- Notify API key
- Verified domain for your production
fromaddress - Supabase project with Edge Functions enabled
- Ability to create an Auth Hook in the dashboard (Authentication → Hooks)
1. Set secrets
supabase secrets set NOTIFY_API_KEY=your_notify_api_key
You will also need SEND_EMAIL_HOOK_SECRET. Generate it when you create the Send Email hook in the Supabase dashboard (step 4). Format looks like v1,whsec_....
supabase secrets set SEND_EMAIL_HOOK_SECRET="v1,whsec_your_secret"
Optional: set a verified sender once:
supabase secrets set NOTIFY_FROM="Auth <noreply@your-verified-domain.com>"
2. 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 notifyKey = Deno.env.get('NOTIFY_API_KEY')!;
const from =
Deno.env.get('NOTIFY_FROM') ?? 'noreply@your-verified-domain.com';
const subjects: Record<string, string> = {
signup: 'Confirm your email',
recovery: 'Reset your password',
invite: "You've been invited",
magiclink: 'Your magic link',
email_change: 'Confirm your email change',
email_change_new: 'Confirm your email change',
reauthentication: 'Your verification code'
};
Deno.serve(async (req) => {
if (req.method !== 'POST') {
return new Response('not allowed', { status: 400 });
}
const payload = await req.text();
const headers = Object.fromEntries(req.headers);
const wh = new Webhook(hookSecret);
try {
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;
site_url: string;
};
};
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
const verifyUrl =
`${supabaseUrl}/auth/v1/verify` +
`?token=${email_data.token_hash}` +
`&type=${email_data.email_action_type}` +
`&redirect_to=${encodeURIComponent(email_data.redirect_to)}`;
const action = email_data.email_action_type;
const subject = subjects[action] ?? 'Your authentication email';
const html = `
<p>Use this link to continue:</p>
<p><a href="${verifyUrl}">${verifyUrl}</a></p>
<p>Or enter this code: <strong>${email_data.token}</strong></p>
`;
const res = await fetch('https://notify.cx/api/email/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': notifyKey
},
body: JSON.stringify({
to: user.email,
from,
subject,
html
})
});
if (!res.ok) {
const body = await res.text();
throw new Error(`Notify send failed: ${res.status} ${body}`);
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return new Response(
JSON.stringify({
error: {
http_code: 500,
message
}
}),
{
status: 401,
headers: { 'Content-Type': 'application/json' }
}
);
}
return new Response(JSON.stringify({}), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
});
Notes:
- Prefer
token_hashin the verify URL (not the raw OTP alone as the only path). - Lead with
htmlfor the body (message/textalso work). - A non-2xx response from the hook fails the Auth email step — keep the function fast and reliable.
Deploy with JWT verification off so Auth can call the function:
supabase functions deploy send-email --no-verify-jwt
3. Point Auth at the function
In the Supabase dashboard: Authentication → Hooks → Send Email.
- Choose HTTPS
- URL:
https://<project-ref>.supabase.co/functions/v1/send-email - Generate / paste the hook secret (same value as
SEND_EMAIL_HOOK_SECRET) - Save
While the hook is enabled, it replaces built-in Auth email delivery for that project.
4. Smoke test
- Trigger a password reset or signup confirm for an address you control
- Confirm the message in Notify logs
- Open the link — it should hit
/auth/v1/verifyand redirect
If nothing arrives: check Edge Function logs, hook secret mismatch, and that from uses a verified Notify domain (and that the account is not stuck in sandbox-only).
Why not Custom SMTP?
| Path | Works with Notify? |
|---|---|
| Supabase Custom SMTP (host / port / user / password) | No — Notify has no SMTP relay |
| Supabase Send Email Hook → Edge Function → Notify HTTP | Yes |
For a generic Edge Function you invoke yourself (not Auth’s hook), see Send email from Supabase Edge Functions.