Use dashboard templates when Supabase sends through SMTP. Use the Send Email Hook when your code needs to render and send through an email service. An enabled hook takes over sending, so handle every supported authentication action and verify its signature before reading or using tokens.
Before you start #
- For
- Developers building and operating application email.
- Bring
- Node.js 22+, npm, and the pinned standardwebhooks dependency. A Supabase project is only needed for the later setup steps.
- Scope
- The download runs locally with synthetic data. All email delivery is simulated; no provider credentials or live sends are needed.
Choose which system renders the email #
Changing a dashboard template and enabling the Send Email Hook are different integration paths. With SMTP sending, Supabase renders its configured templates. With the hook enabled, your handler takes responsibility for rendering and sending. Dashboard edits will not automatically change the HTML that your hook constructs.
Start with the simpler SMTP/template path if layout and wording are the only changes you need. Choose a hook for custom rendering, routing, or other application logic. Keep the email provider enabled in Supabase; a hook does not turn disabled email signup into an enabled flow.
The ZIP contains HTML and plain-text previews produced by the hook renderer. They are not Go-template source to paste directly into Supabase’s dashboard. Translate variables into the dashboard’s documented syntax if you choose that separate path.
The local hook accepts the dashboard’s v1,whsec_ signing-secret prefix. Error responses carry an error object with an HTTP code and message; unsupported actions and incomplete email-change payloads cannot return a success.
Exercise signed hook requests without a Supabase project #
Run npm ci, npm test, and npm run demo after extracting the ZIP. The demo signs a synthetic email-change payload with an obvious fixture secret and passes the original request body through the same verification library used by the local handler.
Run node write-previews.mjs to refresh the templates directory. It contains signup, recovery, magic-link, invitation, current/new email-change, and reauthentication previews. Open the HTML files locally; the confirmation destinations are illustrative and no confirmation server is included.
npm ci
npm test
npm run demoRead the key part of the example #
This excerpt comes from hook.mjs, lines 49–68, in the download. It shows the central decision; run the complete package with the commands above.
if (action === "email_change") {
if (
!address(user.new_email) ||
user.new_email.toLowerCase() === user.email.toLowerCase()
)
throw Error("Invalid new recipient");
// Supabase's backwards-compatible mapping: *_new hash belongs to CURRENT email.
recipients = d.token_hash_new
? [
{ to: user.email, token: d.token, hash: d.token_hash_new },
{ to: user.new_email, token: d.token_new, hash: d.token_hash },
]
: [
{
to: user.new_email,
token: d.token_new || d.token,
hash: d.token_hash,
},
];
}Find the files you’ll change #
Pinned dependencies: standardwebhooks 1.1.1. Install with npm ci so the lockfile controls the resolved versions.
| File | Purpose |
|---|---|
| hook.mjs | The example behavior shown in this article. |
| test.mjs | Acceptance checks and synthetic failure cases. |
| demo.mjs / expected-output.json | A repeatable local experiment and its recorded result. |
| README.md | Setup commands, expected behavior and production boundaries. |
| BUILD-BRIEF.md | The coding-agent brief below. |
| package.json / package-lock.json | Pinned dependencies and runnable commands. |
| LICENSE | MIT license for adapting this example. |
Compare the recorded local result #
Captured from this package’s demo command on October 5, 2026. These results use synthetic fixtures and simulated delivery; they do not measure a live provider or inbox placement.
{
"simulated": true,
"recipients": [
{
"to": "current@example.test",
"tokenHash": "hash-for-current"
},
{
"to": "new@example.test",
"tokenHash": "hash-for-new-or-single"
}
]
}Cover the full authentication lifecycle #
| Action | Email behavior |
|---|---|
| signup | Confirm the initial address. |
| recovery | Start account recovery; the app completes the password change. |
| magiclink | Offer the sign-in link or code. |
| invite | Let the invited person accept in the application. |
| email_change | Map the correct token and hash to each required recipient. |
| reauthentication | Present a code to enter in the app. |
Check the counterintuitive email-change mapping #
With secure email change enabled, user.email is the current address and user.new_email is the replacement. Supabase’s compatibility mapping associates token_hash_new with the current address and token; token_hash belongs to the new address and token_new. Do not interpret the _new suffix on the hash as the destination.
The local tests assert both recipients and both code/hash pairs. They also cover the single-recipient flow when the second hash is absent. Missing required values and unknown action types return an error; the handler cannot silently acknowledge a flow it did not render.
Reject unsafe requests before capture #
The handler verifies the raw body with Standard Webhooks, including signature freshness. Reformatting JSON before verification changes the signed bytes. Only after verification does the renderer inspect the action, destination addresses, and approved redirect.
A failed local capture returns a retryable failure instead of a successful acknowledgement. The callback in this educational lab is not a durable queue, and repeated valid requests can be captured repeatedly. Use a persisted operation identity and durable storage before putting this pattern behind a real Supabase hook.
Connect the selected path in Supabase #
For SMTP, configure the provider connection, verify the sending identity, and update each dashboard template using Supabase’s supported variables. For a hook, deploy your authenticated HTTPS handler, configure its signing secret, replace the local capture with a durable delivery operation, and enable the Send Email Hook in Auth settings.
Keep real tokens and secrets out of logs. Allowlist your own callback origins and paths. The sample /confirm URL is a placeholder: your application must implement the verification step using the documented Supabase token type and hash before establishing a session or completing a change.
Test the whole account journey in a separate environment: verification, recovery, invitation, both email-change modes, and reauthentication. A successfully rendered email does not establish that the destination flow works. Keep provider setup and live delivery checks separate from the offline lab.
Can I turn on the hook for password resets only? #
The Send Email Hook takes responsibility for the authentication email flows handled by the hook. Keep handlers for signup, recovery, magic links, invitations, email changes and reauthentication before enabling it. Test each action with the project’s actual settings.
Use with your coding agent #
Download the example, then copy this brief into your coding agent. The same brief is included as BUILD-BRIEF.md.
# Customize Supabase authentication emails — coding-agent brief
## Objective
Customize all six Supabase authentication actions with HTML/text rendering and a locally verified Send Email Hook.
## Read first
Read README.md, package.json, hook.mjs, test.mjs and demo.mjs. Keep dependency versions pinned to package-lock.json. Use Node.js 22+.
## Dependencies
standardwebhooks 1.1.1. Install with npm ci and preserve the lockfile.
## Work
Start at `renderEmails` in hook.mjs. Run the existing synthetic fixtures before changing behavior. Preserve the guide's original scenario and add a regression check for each changed failure case.
## Constraints
All email is simulated. Do not add provider credentials, send email, deploy services, or use the application's database. Preserve unknown outcomes instead of claiming delivery. Do not turn the local demonstration into production authentication or a public mail relay. Explain the production setup separately.
## Verification
Run `npm ci`, `npm test`, and `npm run demo`.
## Acceptance
Check all six actions and both email-change modes. Verify dashboard-prefixed secrets, wrong and stale signatures, incomplete token pairs, unsupported actions and rejected redirects. Never acknowledge failed capture as success.
No real tokens, signing secrets, or recipient data appear in logs. All fixtures use synthetic values.Check the download #
The ZIP contains 16,197 bytes. Compare its SHA-256 digest with this value before extracting. Downloads are free and require no signup.
feea9b5fd210800a1902f445cbf74ebbad4d5a68cfce73a4ee80ea7beb0a3fe8Sources and further reading
- Supabase: Send Email Hook and email-change mapping
- Supabase: email templates
- Supabase: redirect URLs
- Standard Webhooks specification
Written for Stampwing with AI assistance and checked against the linked documentation. Examples are educational; simulated results are labelled. How these resources are maintained.