June 1, 202612 minute read

Supabase Email Templates in 2026: Setup, Customization, and Fixes

A developer's guide to Supabase Auth email templates: where they live, the variables you can use, how to customize them, and the rate limit and deliverability problems that bite in production.

Ajay Sohmshetty

Ajay Sohmshetty

Supabase Email Templates in 2026: Setup, Customization, and Fixes

If you ship anything on Supabase Auth, you will touch its email templates within the first hour. A user signs up, Supabase fires off a confirmation email, and whatever that email looks like is what your brand new product looks like to that user. By default it looks like a plain text link from an address they have never heard of, sent through shared infrastructure that lets you send two of them per hour.

This guide covers the whole thing for developers: where the templates live, every variable you can put in them, how to customize them properly, the rate limit that catches everyone in production, and the deliverability problems that turn a working signup flow into one where users never get the email. I have verified the dashboard paths and variable names against the current Supabase docs, because both have shifted over the past couple of years.

Where Supabase email templates live

For hosted Supabase projects, the templates are in the Dashboard under Authentication, in the dedicated Emails section. The direct path is supabase.com/dashboard/project/_/auth/templates, where _ resolves to your project ref.

Each template type gets its own tab with a subject line field and an HTML body editor. There is no WYSIWYG. You are editing raw HTML with template variables in it, which is good news if you know HTML and slightly annoying if you do not.

One caveat that landed in June 2026: new free-tier projects on Supabase's default email provider can no longer edit these templates, they send with the defaults as-is. Projects created before the change keep their templates, paid plans are unaffected, and configuring custom SMTP (covered below) restores editing on any plan. If your tabs look locked, that is usually why.

If you self-host Supabase, the dashboard UI for templates is not available. You configure templates through the GoTrue environment variables or your config.toml instead, pointing each template at an HTML file or an inline string. The variable syntax is identical, only the place you put the markup changes.

The templates Supabase gives you

There are six auth templates you can edit. Each maps to a specific moment in the auth lifecycle:

TemplateFires whenMain variable
Confirm signupA user signs up with email and password{{ .ConfirmationURL }}
Invite userYou invite a user via the admin API or dashboard{{ .ConfirmationURL }}
Magic LinkA user requests a passwordless sign-in link{{ .ConfirmationURL }} or {{ .Token }}
Change Email AddressA user changes their email{{ .ConfirmationURL }}, {{ .Email }}, {{ .NewEmail }}
Reset PasswordA user requests a password recovery link{{ .ConfirmationURL }}
ReauthenticationA user re-confirms identity for a sensitive action{{ .Token }}
Breakdown

The six editable Supabase auth templates

Chart data
The six editable Supabase auth templates
ItemValue
Confirm signup1
Invite user1
Magic Link1
Change Email1
Reset Password1
Reauthentication1

Supabase also sends a set of account security notification emails (password changed, email changed, phone changed, an identity provider linked or unlinked, an MFA factor added or removed). As of 2026 these live in the same Emails section, and you can edit each one's content and toggle it on or off, so the editable set is now broader than the classic six. What stays constant is that all of it is account security mail.

A thing worth saying out loud: this is auth and account mail only. There is no welcome email here, no receipt, no "your trial ends in 3 days" email. Those are transactional email messages too, but they are not Supabase's job, and I see people waste a lot of time hunting the dashboard for a template that was never going to exist. More on that further down.

The template variables

The bodies use Go's text/template syntax, which is why every variable looks like {{ .Something }} with a leading dot and exact casing. Get the casing wrong and the variable renders as empty, not as an error, which makes for a fun debugging session.

Here are the main auth-template variables you can reference, verified against the current docs:

  • {{ .ConfirmationURL }}: the complete action link the user clicks. This is the one you almost always want for confirm, recovery, invite, and magic link.
  • {{ .Token }}: a 6-digit one-time password, for when you would rather show a code than a link.
  • {{ .TokenHash }}: a hashed version of the token, used when you want to build the verification URL yourself instead of using {{ .ConfirmationURL }}.
  • {{ .SiteURL }}: your project's Site URL from the auth settings.
  • {{ .RedirectTo }}: the redirect target you passed into the signUp or signInWithOtp call.
  • {{ .Email }}: the user's current email address.
  • {{ .NewEmail }}: the new address, available only in the Change Email template.
  • {{ .Data }}: the user's metadata from auth.users.user_metadata, so you can drop in a first name or plan tier if you stored one at signup.

Security notification templates add variables tied to the event, such as {{ .OldEmail }}, {{ .Phone }}, {{ .OldPhone }}, {{ .Provider }}, and {{ .FactorType }}. Check the current Supabase docs for the exact variables available in each notification template.

That {{ .Data }} one is underused. If you write full_name into user metadata during signup, you can greet people by name in the confirmation email with {{ .Data.full_name }}. Small touch, makes the email feel less like a robot sent it.

One detail about {{ .ConfirmationURL }} versus {{ .TokenHash }} that trips people up. The ConfirmationURL is a fully-formed link Supabase builds for you, pointing at its own verify endpoint, which then redirects to your app. That is fine for most cases. But if you want the link to hit your app first (say, to show a branded loading screen, or because you are on a native mobile flow with a custom scheme), you build the URL yourself out of {{ .TokenHash }} and the verification type. The link looks roughly like {{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email, though you should use the verification type documented for the specific flow. Your app calls verifyOtp with those values. Reach for this only when you actually need it, since the default ConfirmationURL is one less thing to get wrong.

Customizing a template properly

The default confirm-signup template is roughly this:

html
<h2>Confirm your signup</h2>
<p>Follow this link to confirm your user:</p>
<p><a href="{{ .ConfirmationURL }}">Confirm your mail</a></p>

That works, but it is bare. A more realistic version with your branding, a fallback URL, and a name greeting looks like this:

html
<table width="100%" cellpadding="0" cellspacing="0">
  <tr>
    <td align="center" style="padding: 24px 0;">
      <img src="https://yourapp.com/logo.png" alt="YourApp" width="120" />
    </td>
  </tr>
  <tr>
    <td style="font-family: -apple-system, sans-serif; color: #1a1a1a;">
      <h2>Confirm your email</h2>
      <p>Hi {{ .Data.full_name }}, thanks for signing up. Confirm your
      address to finish setting up your account.</p>
      <p>
        <a href="{{ .ConfirmationURL }}"
           style="background: #111; color: #fff; padding: 12px 20px;
                  border-radius: 6px; text-decoration: none;">
          Confirm email
        </a>
      </p>
      <p style="color: #777; font-size: 13px;">
        If the button does not work, paste this link into your browser:<br />
        {{ .ConfirmationURL }}
      </p>
    </td>
  </tr>
</table>

A few rules that save pain when you do this:

Use table-based layout. Email clients, especially Outlook, still render tables more predictably than modern CSS. Inline your styles too, because Gmail strips <style> blocks in a lot of cases.

Always include the raw {{ .ConfirmationURL }} as text somewhere, not only inside the button. Some corporate mail clients rewrite or break button links, and the plain URL is the user's escape hatch.

Keep the subject line specific. "Confirm your YourApp account" beats "Confirm your signup" for both clarity and spam scoring. You edit the subject in the same tab, above the HTML body.

Do not load images from random hosts. Host them on your own domain or a CDN you control, and give every image an alt attribute so the email still reads when images are blocked.

The rate limit nobody plans for

Here is the one that ends up in a support ticket on launch day. Supabase's built-in email service is capped at 2 messages per hour as of 2026. Not 2 per user, 2 total across your whole project.

It is documented as best-effort, with no SLA on delivery or uptime, and it will refuse to send to any address that is not part of your project's team. So in development with your own three email addresses, everything looks fine. The moment a real user signs up, their confirmation email is throttled or the send fails, and you find out from a confused user or an auth error rather than a successful signup flow.

The fix is custom SMTP, and it is not optional for production. Once you wire up a provider under Authentication > Emails > SMTP Settings (the direct path is supabase.com/dashboard/project/_/auth/smtp), the cap moves to 30 messages per hour, and you can raise it further on the Rate Limits page in the dashboard. The 30 is a starting safety value, not a ceiling.

By the numbers

Hourly send cap: default vs custom SMTP

Default email service 2 Custom SMTP (starting) 30
Chart data
Hourly send cap: default vs custom SMTP
ItemValue
Default email service2
Custom SMTP (starting)30

We wrote a deeper walkthrough on getting past this in how to send emails from Supabase beyond the 2 per hour limit, including the SMTP credential fields for the common providers.

Setting up custom SMTP

Under Authentication > Emails > SMTP Settings you toggle "Enable Custom SMTP" and fill in five things: host, port, username, password, and the sender email plus sender name. The provider gives you the host, port, and credentials. The sender email has to be on a domain you have verified with that provider, which is the part that actually controls deliverability.

Good provider choices for Supabase auth email, in rough order of how often I see them:

ProviderFree tier (verify current)Notes
Resend3,000/month, 100/dayPopular with Supabase and React stacks, clean SMTP setup
Postmark100/month developer tierStrong transactional reputation, fast delivery
Amazon SESPay-as-you-go, very cheapCheapest at scale, more setup work
SendGrid60-day trial at 100/dayWidely supported, dashboard-heavy

Pricing is current as of 2026 and changes often; confirm on each provider's site.

By the numbers

SMTP provider free tiers for Supabase auth

1 10 100 1,000 Resend Postmark SendGrid
Chart data
SMTP provider free tiers for Supabase auth
ItemFree emails / monthFree emails / day
Resend3,000100
Postmark1003
SendGrid3,000100

If you want to compare these properly for the transactional side, our best transactional email services roundup goes through deliverability, pricing, and developer experience for each. For deliverability specifically, the email deliverability guide covers SPF, DKIM, and DMARC setup, which is what actually keeps these emails out of spam.

A note on ports while you are filling in those SMTP fields. Port 587 with STARTTLS is the safe default and what most providers want. Port 465 (implicit TLS) also works with several of them. Avoid port 25, which a lot of hosts block outbound anyway. If your test emails just hang and never arrive, a blocked or wrong port is the first thing to check, before you start blaming the template. The username field is sometimes a literal API key name like apikey or resend rather than your account email, so read the provider's SMTP docs rather than guessing, because a silent auth failure here looks identical to a rate-limit drop.

Common breakages and how to fix them

Emails going to spam

The single biggest cause is sending from Supabase's shared infrastructure with no authentication tied to your domain. Custom SMTP on a domain where you have set up SPF, DKIM, and DMARC fixes most of it. After that, watch for image-only emails, too many links, and a From address that does not match your sending domain. We have a dedicated post on why Supabase auth emails go to spam that walks through the DNS records.

This is almost always the redirect allowlist. The URL in {{ .RedirectTo }} and any deep link you pass to signUp has to be listed under Authentication > URL Configuration in the Redirect URLs section. If it is not on the allowlist, Supabase falls back to the Site URL or drops the redirect, and the user lands somewhere unexpected. Wildcards are supported, which matters if you deploy preview branches with changing subdomains.

The action links have a lifetime, and if your email sits in a queue, gets scanned by a corporate security gateway, or the user opens it the next day, the token can be dead by the time they click. Security scanners that pre-fetch links are a sneaky version of this, since they consume the one-time token before the human ever clicks. If you see this a lot, look at increasing the token expiry in the auth settings and check whether a mail security product is touching your links.

Variables rendering empty

If {{ .Data.full_name }} shows up blank, either the casing is off or you never wrote full_name into user metadata at signup. Template variables fail silently, so test every variable with a real signup before you trust it.

If your magic link or confirmation opens in an in-app browser (someone clicking from the Gmail app, say) the session can land in a browser context your app cannot read. Using the OTP {{ .Token }} as a fallback, so the user can type a 6-digit code instead of relying on the link, sidesteps the whole class of problem.

When templates are the wrong tool entirely

Everything above is about auth and account-security templates. The bigger gap is everything else your app needs to send: the welcome sequence after someone confirms, the receipt after a payment, the "you left something unfinished" nudge, or product lifecycle messages with real context. Those are not ordinary Supabase Dashboard templates, and stitching them together with edge functions, a queue, and an email API is real work.

This is where an AI email agent fits, and where Dreamlit comes in. Dreamlit connects directly to your Supabase or Postgres database and builds email workflows from your schema. You describe the email you want in plain English, and it handles the trigger logic, the template, the copy, and the timing based on what your tables actually contain. It can cover non-auth transactional, drip, and broadcast email, and with Supabase Auth hooks configured it can also handle auth email delivery outside the default Supabase template path.

To be honest about scope: Dreamlit is centered on database-driven email workflows, so no SMS, no push, no WhatsApp, and it works with Supabase and Postgres only. It does not give you a REST API, an SMTP relay, or an SDK. Instead it exposes an MCP server, so you drive it from Claude, Cursor, Lovable, or Bolt rather than from code you write yourself. If your stack is Postgres-backed and you would rather not hand-build every transactional flow, it is worth a look. Pricing is on the Dreamlit pricing page.

It does not simply replace the six Supabase auth templates in the Dashboard. Those still live in Supabase if you use the default template path. Dreamlit is mainly for the long tail of product emails Supabase was never going to generate for you, plus auth-email delivery when you deliberately opt into the Auth hooks path.

For more on that mindset, thinking in database-driven notifications lays out the idea of triggering email off database state instead of scattering send calls through your code.

A sensible setup order

If you are starting fresh on Supabase, this is the order that avoids the most rework:

First, configure custom SMTP before you write a single template, even in development. You want to be testing against a real provider, not the 2-per-hour service. Second, set up SPF, DKIM, and DMARC on your sending domain right away, since deliverability problems compound the longer you wait. Third, customize the six auth templates with your branding and a plain-text URL fallback. Fourth, set your redirect allowlist under URL Configuration so confirmation links land correctly. After that, decide how you are handling the non-auth emails, whether that is hand-rolled functions, a transactional provider directly from a React app, or an agent that builds them off your schema.

Get those four in place and the email side of your Supabase app stops being the thing that breaks on launch day.


Frequently asked questions

Where are the email templates in Supabase?

For hosted projects they live in the Dashboard under Authentication, in the Emails section (supabase.com/dashboard/project/_/auth/templates). You get one tab per auth template type: Confirm signup, Invite user, Magic Link, Change Email Address, Reset Password, and Reauthentication. There is also a set of security notification templates (password changed, email changed, MFA factor added, and so on) that you can edit and toggle on or off. Self-hosted projects configure templates through environment variables or the config file instead of the dashboard UI.

What template variables can I use in Supabase email templates?

The main ones are {{ .ConfirmationURL }} (the full action link), {{ .Token }} (a 6-digit OTP), {{ .TokenHash }} (a hashed token for building your own links), {{ .SiteURL }} (your project's Site URL), {{ .RedirectTo }} (the redirect passed in your signup or sign-in call), {{ .Email }}, {{ .NewEmail }} (change-email template only), and {{ .Data }} (which exposes auth.users.user_metadata). Use exact casing and the leading dot, since the templates use Go's text/template syntax.

What is the Supabase email rate limit?

The built-in email service is capped at 2 messages per hour as of 2026. It is best-effort only, has no delivery or uptime SLA, and will refuse to send to addresses outside your project's team. Once you configure custom SMTP the cap starts at 30 messages per hour, and you can raise it on the Rate Limits page in the dashboard.

Do I need custom SMTP for Supabase Auth emails in production?

Yes. Supabase's own docs describe the default service as intended for getting started, testing templates with teammates, and toy projects, not production. For a real app you connect a provider like Resend, Postmark, SendGrid, or Amazon SES under Authentication > SMTP so your emails come from your own authenticated domain and are not throttled to 2 per hour. There is a second reason now: as of June 2026, new free-tier projects using the default email provider can no longer customize their auth templates at all, and configuring custom SMTP is what restores that ability (paid plans and projects created before the change are unaffected).

Why are my Supabase auth emails going to spam?

Usually because they are sent from Supabase's shared infrastructure with no SPF, DKIM, or DMARC alignment to your domain. Switching to custom SMTP on a domain you have authenticated fixes most of it. The remaining causes are link-heavy or image-only templates, mismatched From addresses, and a cold sending domain. There is a full walkthrough in our post on Supabase auth emails going to spam.

Can I send non-auth emails like welcome or receipt emails from Supabase templates?

No. The Emails section covers the six auth flows triggered by Supabase Auth plus a set of account security notifications (password changed, email changed, and so on). Product transactional emails like welcome messages, receipts, or trial reminders are your application's job. You send those from your backend or an edge function through an email API, or you let a tool build them off your schema.

How do I test Supabase email templates without hitting the rate limit?

Configure custom SMTP early, even in development, so you are working against a real provider's limits instead of the 2-per-hour cap. Most providers have a test or sandbox mode and a 100-or-so message daily free tier. You can also trigger a single flow at a time and check the rendered output in your provider's logs rather than repeatedly signing up test users.

Does my redirect URL need to be allowlisted?

Yes. The address in {{ .RedirectTo }} and any deep link in your confirmation flow must be listed under Authentication > URL Configuration in the Redirect URLs allowlist. If it is not, Supabase falls back to the Site URL or strips the redirect, which is the most common cause of a confirmation link that opens the wrong page. Wildcards are supported for preview deployments.

What is Dreamlit and how does it relate to Supabase templates?

Dreamlit is an AI email agent that connects to your Supabase or Postgres database and builds email workflows from your schema. It can cover the product emails Supabase templates do not cover (welcome, transactional, drip, broadcast), and when configured through Supabase Auth hooks it can also handle Supabase Auth email delivery rather than merely editing Dashboard templates. It is centered on database-driven email workflows, Supabase and Postgres only, and exposes an MCP server instead of a REST API or SDK. Sources:

About the Author

Ajay Sohmshetty
Ajay Sohmshetty

Co-Founder

Ajay is CEO and Co-Founder of Dreamlit AI. His job is to get Dreamlit in front of the businesses that need it and to make sure the company scales in a way that actually works. Full bio →

Other articles

Ajay Sohmshetty
Ajay Sohmshetty
May 23, 2025Engineering

How to Send Emails from Supabase (Beyond the 2/Hour Limit)

Supabase caps you at 2 emails per hour out of the box. Here's how to set up auth, transactional, and scheduled emails that actually scale.

Andrew Kim
Andrew Kim
May 1, 2026Company

Why Your Supabase Auth Emails Go to Spam in 2026: Causes and Fixes

Learn why your Supabase authentication emails end up in spam and how to fix it. improve deliverability with proven strategies.