# Transactional email templates

> Configure consistent Supabase Auth email templates for confirmation, recovery, invitations, and security notifications.

# Transactional email templates

Supacharger uses Supabase Auth templates for account confirmation, OTP and magic-link sign-in, recovery, invitations, email changes, reauthentication, and security notifications.

Install hosted templates under **Supabase → Authentication → Email Templates**. Files referenced by `supabase/config.toml` configure only the local CLI stack; editing one environment does not update the other.

## Before customizing

Configure [production SMTP](../../Application%20Development/smtp.md) first. New Free plan projects cannot customize Auth templates while using Supabase's default SMTP service.

Then confirm these settings:

- the canonical Site URL and allowed redirects;
- `PASSWORDLESS_EMAIL` mode and `OTP_LENGTH` in `src/supacharger.config.ts`;
- `SIGN_UP_EMAIL_VERIFICATION` in `src/supacharger.config.ts`;
- hosted Confirm Email policy;
- Email OTP Expiration; and
- whether secure email change is enabled.

Template URLs and copy must match those settings.

## Template set

Review every enabled authentication template:

- Confirm signup
- Magic link or OTP
- Reset password
- Invite user
- Change email address
- Reauthentication

Also enable appropriate notifications for password, email, phone, sign-in-method, and verification-method changes.

The [Supabase Auth template examples](./free-supabase-email-templates.md) provide small, auditable OTP and link examples. Apply product branding only after the destinations and token variables work.

## Which template Supabase sends

The API method does not select a template by name. Supabase combines the requested operation with the account's current state and the hosted **Confirm Email** policy. In particular, `shouldCreateUser: false` prevents an unknown address from being registered; it does not force the **Magic link or OTP** template for an account that exists but is still unconfirmed.

| Request | Account state | Relevant option or policy | Result | Hosted template |
| --- | --- | --- | --- | --- |
| Email-and-password signup | Unknown address | Confirm Email enabled | Creates an unconfirmed user and sends signup verification | **Confirm signup** |
| Email-and-password signup | Unknown address | Confirm Email disabled | Creates and confirms the user immediately; no verification message is needed | None |
| Email-and-password signup | Existing, unconfirmed account | Confirm Email enabled | Treats the request as another attempt to complete signup, subject to email rate limits | **Confirm signup** |
| Email-and-password signup | Existing, confirmed account | Confirm Email enabled | Returns an obfuscated existing-account response to avoid disclosing registration state | None |
| Passwordless `signInWithOtp()` | Unknown address | `shouldCreateUser: true` and Confirm Email enabled | Creates an unconfirmed user and starts signup verification | **Confirm signup** |
| Passwordless `signInWithOtp()` | Unknown address | `shouldCreateUser: true` and Confirm Email disabled | Creates and confirms the user, then sends the passwordless credential | **Magic link or OTP** |
| Passwordless `signInWithOtp()` | Unknown address | `shouldCreateUser: false` | Rejects the request with `otp_disabled`; no user is created | None |
| Passwordless `signInWithOtp()` | Existing, unconfirmed account | Either `shouldCreateUser` value | Treats the account as an incomplete signup | **Confirm signup** |
| Passwordless `signInWithOtp()` | Existing, confirmed account | Either `shouldCreateUser` value | Starts passwordless sign-in | **Magic link or OTP** |
| Resend signup verification | Existing, unconfirmed account | Resend type `signup` | Sends another signup credential, subject to email rate limits | **Confirm signup** |
| Password recovery | Existing account | Recovery request | Sends the password-reset credential | **Reset password** |
| Password recovery | Unknown address | Recovery request | Does not deliver a message; the public response may remain deliberately non-enumerating | None |
| Administrator invitation | Invited address | Admin invite request | Sends the invitation credential | **Invite user** |
| Email-address change | Authenticated user | Email update request | Sends the address-change credential according to secure email-change policy | **Change email address** |
| Reauthentication | Authenticated user | Reauthentication request | Sends the verification code | **Reauthentication** |

The **Magic link or OTP** name describes one template whose content chooses the passwordless experience. Passwordless mode is exclusive: include only `{{ .Token }}` for `otp`, or only the application-owned `{{ .TokenHash }}` link for `link`. The **Confirm signup** template is separate and follows `SIGN_UP_EMAIL_VERIFICATION`; it may include both alternatives only for `otp-and-link`. Supacharger's SSR flow supplies `/auth/confirm?next=...` as the complete `.RedirectTo` for link mode. Use this HTML link where a link is configured:

```html
<a href="{{ .RedirectTo }}&amp;token_hash={{ .TokenHash }}&amp;type=email">...</a>
```

Do not append another `/auth/confirm` path. Do not place the token hash on `/auth/callback` or `/account/login`.

An unconfirmed Auth row therefore does not behave like a confirmed existing account. Supabase Auth explicitly treats a missing or unconfirmed user as someone who has not completed signup before it chooses the confirmation path. See the [passwordless email guide](https://supabase.com/docs/guides/auth/auth-email-passwordless), [`signInWithOtp()` reference](https://supabase.com/docs/reference/javascript/auth-signinwithotp), and [Auth template reference](https://supabase.com/docs/guides/auth/auth-email-templates).

All confirmation links and email OTPs are one-time credentials. If a template exposes both forms for one request, successfully using either consumes that request and makes the other form invalid. Disable SMTP-provider click tracking and automated link rewriting so scanners or relay redirects do not consume or deform the credential before the user opens it.

## Important variables

| Variable | Use |
| --- | --- |
| `{{ .Token }}` | Configured-length email OTP or reauthentication code |
| `{{ .TokenHash }}` | Hashed token used to construct an application-owned confirmation link |
| `{{ .ConfirmationURL }}` | Supabase-generated verification URL |
| `{{ .RedirectTo }}` | Per-request allowed redirect supplied by the application |
| `{{ .SiteURL }}` | Fixed fallback Site URL configured in Supabase |

Use `{{ .RedirectTo }}` when Supacharger supplies the destination for a request. Do not replace it with `{{ .SiteURL }}` merely because both happen to share the same origin in production.

## Operating rules

- Use only the configured OTP or link method in a passwordless email.
- Include both signup alternatives only when `SIGN_UP_EMAIL_VERIFICATION` is `otp-and-link`.
- Do not hard-code an expiry that differs from Email OTP Expiration.
- Disable provider click tracking that rewrites one-time URLs.
- Avoid tracking pixels in security-sensitive messages.
- Never use user-editable metadata for authorization decisions.
- Test each template in real desktop and mobile mail clients.

The complete installation sequence is in [Install email templates](../../guides/hosted-setup/07-email-templates.md).
