---
title: Supabase Auth emails | SendHQ Docs
description: "Send Supabase sign-up, magic link, password reset, and email change messages through SendHQ with Supabase's Send Email hook and an Edge Function."
canonical: https://sendhq.cc/docs/supabase-auth-emails
last-updated: 2026-09-07
---
# Supabase Auth emails

Send Supabase sign-up, magic link, password reset, and email change messages through SendHQ with Supabase's Send Email hook and an Edge Function.

## How it works

Supabase Auth can hand every authentication email to an HTTPS endpoint through its **Send Email** hook instead of sending it itself. A small Supabase Edge Function receives the hook, verifies its signature, and sends the message through the SendHQ API from your verified domain. Confirmation links, magic links, and one-time codes stay generated by Supabase; SendHQ only delivers them.

**Before you start** Verify the sending domain in SendHQ and create an API key. On the integration trial, SendHQ delivers only to your account email, so test with that address first.

## 1. Store the secrets

Generate the hook secret in the Supabase dashboard under **Authentication → Hooks**, then set both secrets on the project.

```shell
SENDHQ_API_KEY="re_your_key_here"
SEND_EMAIL_HOOK_SECRET="v1,whsec_<base64_secret>"
```

```shell
supabase secrets set --env-file .env
supabase functions new send-email
```

## 2. Write the Edge Function

The function verifies the Standard Webhooks signature, builds the verification link from the token hash, and calls SendHQ. Send each email with an idempotency key derived from the token hash so a retried hook never sends twice.

```typescript
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("SENDHQ_API_KEY")!;
const projectUrl = Deno.env.get("SUPABASE_URL")!;

const subjects: Record<string, string> = {
  signup: "Confirm your email",
  magiclink: "Your sign-in link",
  recovery: "Reset your password",
  invite: "You have been invited",
  email_change: "Confirm your new email address",
};

Deno.serve(async (req) => {
  const payload = await req.text();
  let event;
  try {
    event = new Webhook(hookSecret).verify(payload, Object.fromEntries(req.headers));
  } catch {
    return Response.json({ error: { http_code: 401, message: "Invalid signature" } }, { status: 401 });
  }
  const { user, email_data } = event as any;
  const link = `${projectUrl}/auth/v1/verify?` + new URLSearchParams({
    token: email_data.token_hash,
    type: email_data.email_action_type,
    redirect_to: email_data.redirect_to,
  });

  const res = await fetch("https://sendhq.cc/api/v1/emails", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
      "Idempotency-Key": `supabase-${email_data.token_hash}`,
    },
    body: JSON.stringify({
      from: "Acme <auth@example.com>",
      to: [user.email],
      subject: subjects[email_data.email_action_type] ?? "Your account",
      html: `<p><a href="${link}">Continue</a></p><p>Or enter this code: ${email_data.token}</p>`,
      text: `Continue: ${link}\nOr enter this code: ${email_data.token}`,
    }),
  });
  if (!res.ok) {
    return Response.json({ error: { http_code: res.status, message: await res.text() } }, { status: 500 });
  }
  return Response.json({});
});
```

## 3. Deploy and enable the hook

```shell
supabase functions deploy send-email --no-verify-jwt
```

In **Authentication → Hooks**, add a **Send Email** hook of type HTTPS pointing at the function URL, paste the same secret, and enable it. Supabase now calls the function instead of sending authentication email itself.

## Email change and other action types

`email_action_type` tells you which message to send: `signup`, `invite`, `magiclink`, `recovery`, `email_change`, `reauthentication`, and notification types such as `password_changed_notification`. With Secure Email Change on, an `email_change` event can carry two codes: send `token` with `token_hash_new` to the current address and `token_new` with `token_hash` to the new one.

## Check delivery

The SendHQ response contains the email ID. Look it up with `GET /api/v1/emails/:id/events`, the dashboard, or `sendhq email events em_123` to see delivery, bounce, and complaint events. Addresses that hard-bounce are suppressed automatically, which protects your domain's reputation when users mistype their email at sign-up.
