BLOGS

Custom Email Notification Templates in Supabase (2026)

March 26, 2026

How to customize Supabase auth email templates, from built-in variables and custom metadata to Go template conditionals and the Send Email Hook for full control.

Custom Email Notification Templates in Supabase (2026)

Motivation

Supabase ships six authentication email templates out of the box. They work, but the default HTML is bare. No branding, no localization, no way to send different content based on who the user is.

There are typical scenarios that require customization, such as:

This guide covers four levels of customization to address these:

  1. Dashboard templates with built-in variables
  2. Dashboard templates with custom variables - such as {{ .Data.first_name }}
  3. Dashboard templates with logic - with Go template conditionals
  4. Custom templates - bypass Supabase’s email system entirely

Approach 1: Dashboard Templates with Built-in Variables

The simplest approach. In the Supabase dashboard, go to Authentication > Email Templates and edit the HTML directly.

Available variables

Every template has access to these Go template variables:

VariableDescriptionExample
{{ .ConfirmationURL }}The full verification linkhttps://abc.supabase.co/auth/...
{{ .Token }}6-digit OTP code482910
{{ .TokenHash }}Hashed version of the tokena1b2c3d4e5f6...
{{ .SiteURL }}Your app’s site URLhttps://myapp.com
{{ .RedirectTo }}Redirect URL passed from the clienthttps://myapp.com/welcome
{{ .Email }}The user’s email addressmaria@example.com

For email change templates, you also get {{ .NewEmail }} and {{ .OldEmail }}.

Example: signup confirmation

<h2>Confirm your email</h2>
<p>Hey {{ .Email }},</p>
<p>Click below to confirm your account:</p>
<a href="{{ .ConfirmationURL }}">Confirm Email</a>
<p>Or enter this code manually: <strong>{{ .Token }}</strong></p>

Approach 2: Dashboard Templates with Custom Variables

You can pass custom metadata when a user signs up, then reference those fields in your templates via {{ .Data }}.

Passing metadata at signup

When a user signs up, include metadata that your templates can reference:

const { data, error } = await supabase.auth.signUp({
  email: 'user@example.com',
  password: 'securepassword',
  options: {
    data: {
      role: 'seller',
      language: 'es',
      first_name: 'Maria',
      plan: 'pro'
    }
  }
});

All fields under data are accessible via {{ .Data.role }}, {{ .Data.language }}, {{ .Data.first_name }}, etc. in your templates.

Users can also update their metadata later, and future auth emails will reflect the updated values:

const { data, error } = await supabase.auth.updateUser({
  data: { language: 'fr', first_name: 'Marie' }
});

Example: personalized signup confirmation

<h2>Welcome, {{ .Data.first_name }}!</h2>
<p>Thanks for signing up for the {{ .Data.plan }} plan.</p>
<p>Click below to confirm your account:</p>
<a href="{{ .ConfirmationURL }}">Confirm Email</a>
<p>Or enter this code manually: <strong>{{ .Token }}</strong></p>

Approach 3: Dashboard Templates with Logic

Since Supabase uses Go’s html/template engine, you can use conditionals and comparisons to render different content per user.

Example: different emails per user role

A common need, e.g. send sellers a different confirmation than buyers:

{{ if eq .Data.role "seller" }}
<h2>Welcome, Seller!</h2>
<p>Hi {{ .Data.first_name }},</p>
<p>Confirm your seller account to start listing products:</p>
<a href="{{ .ConfirmationURL }}">Activate Seller Account</a>
<p>
  After confirming, you'll be able to set up your storefront and payout details.
</p>

{{ else if eq .Data.role "buyer" }}
<h2>Welcome!</h2>
<p>Hi {{ .Data.first_name }},</p>
<p>Confirm your email to start shopping:</p>
<a href="{{ .ConfirmationURL }}">Confirm Email</a>

{{ else }}
<h2>Confirm your email</h2>
<p>Follow this link: <a href="{{ .ConfirmationURL }}">Confirm</a></p>

{{ end }}

Example: localized emails based on user language

Store a language preference in metadata, then conditionally render different content:

{{ if eq .Data.language "es" }}
<h2>¡Bienvenido!</h2>
<p>Haz clic aquí para confirmar tu correo electrónico:</p>
<a href="{{ .ConfirmationURL }}">Confirmar correo</a>
<p>O ingresa este código: <strong>{{ .Token }}</strong></p>

{{ else if eq .Data.language "fr" }}
<h2>Bienvenue !</h2>
<p>Cliquez ici pour confirmer votre adresse e-mail :</p>
<a href="{{ .ConfirmationURL }}">Confirmer l'e-mail</a>
<p>Ou saisissez ce code : <strong>{{ .Token }}</strong></p>

{{ else }}
<h2>Welcome!</h2>
<p>Click here to confirm your email:</p>
<a href="{{ .ConfirmationURL }}">Confirm Email</a>
<p>Or enter this code: <strong>{{ .Token }}</strong></p>

{{ end }}

Example: personalized onboarding based on signup source

If users come from different landing pages or campaigns:

{{ if eq .Data.Domain "https://www.earlyaccess.example.com" }}
<h2>Welcome, Early Access Member!</h2>
<p>You've been granted early access to premium features.</p>
<a href="{{ .ConfirmationURL }}">Activate Early Access</a>

{{ else }}
<h2>Welcome to Example App</h2>
<p>Confirm your email to get started:</p>
<a href="{{ .ConfirmationURL }}">Confirm Email</a>

{{ end }}

Gotchas with Go templates

Approach 4: Custom Templates with the Send Email Hook

When dashboard templates aren’t enough - e.g. you need dynamic content from your database, rich HTML rendering frameworks like React Email, or a third-party email provider - the Send Email Hook takes over completely.

The hook intercepts every auth email before it’s sent and delegates delivery to your Edge Function. Supabase’s built-in SMTP is bypassed entirely.

How it works

  1. A user triggers an auth action (signup, password reset, etc.)
  2. Instead of sending the email itself, Supabase POSTs the email data to your Edge Function
  3. Your function receives the full user object (with all metadata) and email_data (token, action type, redirect URL)
  4. Your function decides what to send, how to render it, and which provider delivers it
  5. Return a 200 status to confirm success

This gives you full programmatic control. Any email provider, any templating framework, any business logic. The trade-off is more setup: you need to write, deploy, and maintain an Edge Function.

For a complete walkthrough with code, deployment steps, and edge cases like email change (double confirmation), see our dedicated guide: Supabase Send Email Hook: Customize Auth Emails with Edge Functions.

Common Issues and Fixes

Microsoft Defender and some enterprise email filters prefetch links, consuming the confirmation URL before the user clicks it. Fix this by using OTP codes instead of links:

<p>Enter this code to confirm: <strong>{{ .Token }}</strong></p>

Then verify on the client:

const { data, error } = await supabase.auth.verifyOtp({
  email,
  token: userEnteredCode,
  type: 'email'
});

Templates not updating

Template changes in the dashboard can take a few minutes to propagate. If you’re testing locally, make sure your config.toml paths are correct and you’ve restarted supabase start.

User metadata not available in templates

Metadata must be passed at signup time via options.data. You can’t retroactively add metadata and expect it to appear in the signup confirmation template - it’s captured at the moment the email is triggered.

TLDR

For most apps, custom variables and conditionals with user_metadata are enough to handle personalization without deploying an Edge Function. When you outgrow that - need external data lookups, React Email, or provider switching - the Send Email Hook is the escape hatch.

If you’re looking for an email provider that works out of the box with Supabase (both as SMTP and via the SDK in Edge Functions), Pingram offers 3,000 free emails/month and a one-click Supabase integration.