Docs/Integrations

Supabase

Supabase Auth emails its users at every turn: signup confirmations, magic links, password resets, invites. Its built-in email service is for prototyping only. Point Supabase at Anypost and auth email sends from your verified domain, with the same delivery, bounce, and engagement events as everything else you send.

There are two ways in, and they solve different problems:

  • Custom SMTP is a settings change. Supabase keeps rendering its own auth templates and hands the finished message to Anypost for delivery. Right for most projects.
  • A Send Email Hook replaces Supabase's rendering entirely. Supabase calls an Edge Function with the token, and the function sends whatever it wants through the Anypost API. Reach for it when you need full control of the markup, localization, or Anypost templates.

Application email that has nothing to do with Auth sends from Edge Functions with the HTTP API.

Before you start

  • Verify the domain you will send from. See Domains & DNS setup. Supabase recommends keeping auth email off your marketing domain; a subdomain such as auth.yourdomain.com verifies like any other domain.
  • Create an API key. A send_only key is enough for both paths. See Authentication.

Custom SMTP

In your Supabase project, open Project Settings, then Authentication, and enable Custom SMTP. Fill in:

SettingValue
Hostsmtp.anypost.com
Port587
Usernameanypost
Passwordyour API key secret (ap_...)
Sender emailan address on your verified domain
Sender namethe name recipients see

Supabase performs the required STARTTLS upgrade on port 587 automatically. If your network blocks 587, use 2587. See Sending over SMTP for the full connection contract.

Raise the Auth rate limit

After you enable custom SMTP, Supabase caps auth email at 30 messages per hour by default. Raise it under Authentication, then Rate Limits, to match your signup volume. Anypost applies no per-hour cap of its own; auth sends count toward your plan like any other message. See Sending limits.

Test it

Trigger a password reset or a signup from your app. The message appears in the Anypost dashboard within seconds, and delivery, bounce, open, and click events fire for it exactly as for an API send. See Events.

Send Email Hook

Supabase's Send Email Hook hands each auth email to an HTTP endpoint instead of sending it. An Edge Function receives the user, the one-time token, and the action type, and is free to build the message however it likes. While the hook is enabled it replaces SMTP for auth email entirely.

Write the function

supabase functions new send-auth-email

The function verifies the hook signature, builds a confirmation URL from the token, and sends through the Anypost API:

// supabase/functions/send-auth-email/index.ts
import { Webhook } from 'npm:[email protected]';
 
const hookSecret = Deno.env
    .get('SEND_EMAIL_HOOK_SECRET')!
    .replace('v1,whsec_', '');
const anypostKey = Deno.env.get('ANYPOST_API_KEY')!;
 
const subjects: Record<string, string> = {
    signup: 'Confirm your email',
    invite: 'You have been invited',
    magiclink: 'Your login link',
    recovery: 'Reset your password',
    email_change: 'Confirm your new email address',
};
 
Deno.serve(async (req) => {
    const payload = await req.text();
 
    let user, email_data;
    try {
        ({ user, email_data } = new Webhook(hookSecret).verify(
            payload,
            Object.fromEntries(req.headers),
        ) as { user: { email: string }; email_data: Record<string, string> });
    } catch {
        return Response.json({ error: 'invalid signature' }, { status: 401 });
    }
 
    const { token, token_hash, email_action_type, redirect_to } = email_data;
    const confirmUrl =
        `${Deno.env.get('SUPABASE_URL')}/auth/v1/verify` +
        `?token=${token_hash}&type=${email_action_type}` +
        `&redirect_to=${encodeURIComponent(redirect_to)}`;
 
    const res = await fetch('https://api.anypost.com/v1/email', {
        method: 'POST',
        headers: {
            Authorization: `Bearer ${anypostKey}`,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            from: 'Acme <[email protected]>',
            to: [user.email],
            subject: subjects[email_action_type] ?? 'Your verification code',
            html:
                `<p><a href="${confirmUrl}">Confirm</a>, ` +
                `or enter the code <strong>${token}</strong>.</p>`,
            topic: 'auth',
        }),
    });
 
    if (!res.ok) {
        return Response.json({ error: await res.text() }, { status: 500 });
    }
 
    return Response.json({});
});

A topic of auth keeps these sends filterable in your event stream; see Tags, topics & campaigns. To render stored content instead of inline HTML, send template_id and variables; see Sending with templates.

Two action types carry extra fields: with secure email change enabled, email_change includes token_new and token_hash_new for the second message to the old address, and reauthentication carries a code but no link. Handle them if your app uses those flows.

Deploy and enable

Deploy without JWT verification; Supabase authenticates hook calls with the signature, not a user token:

supabase functions deploy send-auth-email --no-verify-jwt
supabase secrets set ANYPOST_API_KEY="ap_..."

Then, in the dashboard under Authentication, then Hooks, add a Send Email hook pointing at the function. Copy the secret Supabase generates and store it:

supabase secrets set SEND_EMAIL_HOOK_SECRET="v1,whsec_..."
Careful

While the hook is enabled, a non-200 response fails the auth request itself: the user's signup or reset attempt errors and no email is sent. Keep the function minimal and watch its logs after enabling.

Test each flow you use (signup, magic link, recovery) before relying on it.

Send application email from Edge Functions

Any Edge Function can send through the HTTP API directly; there is nothing Supabase-specific about it:

const res = await fetch('https://api.anypost.com/v1/email', {
    method: 'POST',
    headers: {
        Authorization: `Bearer ${Deno.env.get('ANYPOST_API_KEY')}`,
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({
        from: 'Acme <[email protected]>',
        to: ['[email protected]'],
        subject: 'Your export is ready',
        html: '<p>Download it from your dashboard.</p>',
    }),
});
 
const { id } = await res.json();

A 202 and the id mean Anypost accepted the message; delivery and engagement arrive later as events. Pair this with a Supabase Database Webhook to email on a table insert. Webhook deliveries can retry, so set an Idempotency-Key header derived from the row to keep a retried delivery from sending twice. See Send a single email.

Where to go next