mirror of
https://github.com/supabase/supabase.git
synced 2026-10-13 21:29:08 +03:00
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## Problem The Docs E2E link checker found broken links throughout docs, starting with `phone-login.mdx` pointing to `/docs/guides/cli/config` (404). Old links like `/docs/guides/cli/config` still work on the live site because `supabase.com` has redirects set up for them, but these links break on the docs preview site, which is what the E2E check tests against. These issues look clean on the live site, and I didn't catch them in my first pass because I was testing production instead of the preview. The E2E check only tests the ~20 pages a given PR happens to touch, so fixing the pages it flagged kept exposing more of the same problem one page at a time as each fix pulled in a new file. To stop chasing this incrementally, I cross-referenced every `/docs/guides/*` and `/docs/reference/*` redirect source in `apps/www/lib/redirects.js` against actual usage across all of `apps/docs`, and verified each candidate against the live preview. ## Solution Rather than updating the Docs E2E link checker, this PR resolves the links. **Why:** we own these docs, so keeping the links clean without redirects is keeping the house maintained. See [Broken Window Theory](https://blog.codinghorror.com/the-broken-window-theory/). Updated every link still using an old path to point straight at the current page instead of relying on a redirect. This covers old links like: - `/docs/guides/cli/config` → `/docs/guides/local-development/cli/config` - `/docs/guides/cli/getting-started` → `/docs/guides/local-development/cli/getting-started` - `/docs/guides/cli/local-development` → `/docs/guides/local-development/database-migrations` - `/docs/guides/cli/managing-environments` → `/docs/guides/deployment/managing-environments` - `/docs/guides/cli/seeding-your-database` → `/docs/guides/local-development/seeding-your-database` - bare `/docs/guides/cli` → `/docs/guides/local-development` - `/docs/guides/platform/compute-add-ons` → `/docs/guides/platform/compute-and-disk` - `/docs/guides/platform/shared-responsibility-model` → `/docs/guides/deployment/shared-responsibility-model` - `/docs/guides/database` → `/docs/guides/database/overview` - `/docs/reference/javascript`, `/docs/reference/dart`, `/docs/reference/kotlin`, `/docs/reference/python`, `/docs/reference/csharp` → their `/introduction` pages (the redirect's own destination, `/start`, turned out to be dead even on production — a separate bug in `redirects.js` I didn't touch here) - and about 35 more of the same pattern, listed in the commit messages Also fixed a handful of dead heading anchors found along the way (links that resolve to the right page but point at a `#section` that got renamed or moved), including the original `#bigquery` anchor and a few in `connecting-to-postgres.mdx` where content moved to its own dedicated page. Left alone on purpose: - `content/guides/cli.mdx` — this page has no route in the docs app at all (no `app/guides/cli/` directory), so it 404s even in production before the `www` redirect ever fires. Fixing its internal link wouldn't change that; it needs an actual routing/content decision, not a link fix. - A few candidates that already resolve fine as-is (`pg_partman`, bare `/docs/reference/api`, bare `/docs/reference/cli`) — confirmed via curl, left untouched. ## Manual testing 1. Confirmed every new link target actually exists by checking the destination file/page and matching heading anchors. 2. Cross-referenced every `/docs/guides/*` and `/docs/reference/*` redirect source in `apps/www/lib/redirects.js` against real usage in `apps/docs`, and curl-verified each old path (404) and new path (200) against the live PR preview before fixing it. 3. Ran the Docs E2E link checker locally against changed pages. 4. Spot-checked the original broken link from CI (`/docs/guides/cli/config`) to confirm it now points to a working page.
226 lines
17 KiB
Plaintext
226 lines
17 KiB
Plaintext
---
|
|
title: 'Email Templates'
|
|
description: 'Learn how to manage the email templates in Supabase.'
|
|
subtitle: 'Learn how to manage the email templates in Supabase.'
|
|
---
|
|
|
|
Email templates in Supabase fall into two categories: authentication and security notifications.
|
|
|
|
Authentication emails:
|
|
|
|
- Confirm sign up
|
|
- Invite user
|
|
- Magic link or OTP
|
|
- Change email address
|
|
- Reset password
|
|
- Reauthentication
|
|
|
|
Security notification emails:
|
|
|
|
- Password changed
|
|
- Email address changed
|
|
- Phone number changed
|
|
- Sign-in method linked
|
|
- Sign-in method removed
|
|
- Verification method added
|
|
- Verification method removed
|
|
|
|
Security emails are only sent to users if the respective security notifications have been enabled at a project-level.
|
|
|
|
## Terminology
|
|
|
|
The templating system provides the following variables for use:
|
|
|
|
| Name | Description |
|
|
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| `{{ .ConfirmationURL }}` | Contains the confirmation URL. For example, a signup confirmation URL would look like: `https://project-ref.supabase.co/auth/v1/verify?token={{ .TokenHash }}&type=email&redirect_to=https://example.com/path`. |
|
|
| `{{ .Token }}` | Contains a 6-digit One-Time-Password (OTP) that can be used instead of the `{{. ConfirmationURL }}`. |
|
|
| `{{ .TokenHash }}` | Contains a hashed version of the `{{ .Token }}`. This is useful for constructing your own email link in the email template. |
|
|
| `{{ .SiteURL }}` | Contains your application's Site URL. This can be configured in your project's [authentication settings](/dashboard/project/_/auth/url-configuration). |
|
|
| `{{ .RedirectTo }}` | Contains the redirect URL passed when `signUp`, `signInWithOtp`, `signInWithOAuth`, `resetPasswordForEmail` or `inviteUserByEmail` is called. The redirect URL allow list can be configured in your project's [authentication settings](/dashboard/project/_/auth/url-configuration). |
|
|
| `{{ .Data }}` | Contains metadata from `auth.users.user_metadata`. Use this to personalize the email message. |
|
|
| `{{ .Email }}` | Contains the original email address of the user. Empty when trying to [link an email address to an anonymous user](/docs/guides/auth/auth-anonymous#link-an-email--phone-identity). |
|
|
| `{{ .NewEmail }}` | Contains the new email address of the user. This variable is only supported in the "Change email address" template. |
|
|
| `{{ .OldEmail }}` | Contains the old email address of the user. This variable is only supported in the "Email address changed notification" template. |
|
|
| `{{ .Phone }}` | Contains the new phone number of the user. This variable is only supported in the "Phone number changed notification" template. |
|
|
| `{{ .OldPhone }}` | Contains the old phone address of the user. This variable is only supported in the "Phone number changed notification" template. |
|
|
| `{{ .Provider }}` | Contains the provider of the linked or removed sign-in method. This variable is only supported in the "Sign-in method linked" and "Sign-in method removed" notification templates. |
|
|
| `{{ .FactorType }}` | Contains the type of verification method that was added or removed. This variable is only supported in the "Verification method added" and "Verification method removed" notification templates. |
|
|
|
|
## Editing email templates
|
|
|
|
Where you edit templates depends on how you run Supabase.
|
|
|
|
### Hosted projects
|
|
|
|
Edit templates on the [Email Templates](/dashboard/project/_/auth/templates) page in the dashboard. The template builder uses the same [terminology](#terminology) and variables documented on this page.
|
|
|
|
### Local development and self-hosted
|
|
|
|
The dashboard template builder **does not apply** when running [local development with CLI](/docs/guides/local-development) or [self-hosted Supabase](/docs/guides/self-hosting). Customize templates in `supabase/config.toml` and local HTML files instead.
|
|
|
|
<Admonition type="note" title="Customizing templates locally">
|
|
|
|
See [Customizing email templates](/docs/guides/local-development/customizing-email-templates) for:
|
|
|
|
- [`config.toml` keys and HTML file paths](/docs/guides/local-development/customizing-email-templates#configuring-templates)
|
|
- [Template variables](/docs/guides/local-development/customizing-email-templates#template-variables) — the same placeholders as the [terminology](#terminology) table above
|
|
- Default subjects and behavior for each [authentication](/docs/guides/local-development/customizing-email-templates#available-authentication-email-templates) and [security notification](/docs/guides/local-development/customizing-email-templates#available-security-notification-email-templates) template
|
|
|
|
For self-hosted deployments, refer to [Custom email templates](/docs/guides/self-hosting/custom-email-templates).
|
|
|
|
</Admonition>
|
|
|
|
You can also manage email templates using the Management API:
|
|
|
|
```bash
|
|
# Get your access token from https://supabase.com/dashboard/account/tokens
|
|
export SUPABASE_ACCESS_TOKEN="your-access-token"
|
|
export PROJECT_REF="your-project-ref"
|
|
|
|
# Get current email templates
|
|
curl -X GET "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
|
|
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
|
|
| jq 'to_entries | map(select(.key | startswith("mailer_templates"))) | from_entries'
|
|
|
|
# Update email templates
|
|
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
|
|
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"mailer_subjects_confirmation": "Confirm your email address",
|
|
"mailer_templates_confirmation_content": "<h2>Confirm your email address</h2><p>Follow the link below to confirm this email address and finish signing up.</p><p><a href=\"{{ .ConfirmationURL }}\">Confirm email address</a></p>",
|
|
"mailer_subjects_magic_link": "Your sign-in link",
|
|
"mailer_templates_magic_link_content": "<h2>Your sign-in link</h2><p>Follow the link below to sign in. This link expires shortly and can only be used once.</p><p><a href=\"{{ .ConfirmationURL }}\">Sign in</a></p>",
|
|
"mailer_subjects_recovery": "Reset your password",
|
|
"mailer_templates_recovery_content": "<h2>Reset your password</h2><p>We received a request to reset your password. Follow the link below to choose a new one.</p><p><a href=\"{{ .ConfirmationURL }}\">Reset password</a></p><p>If you didn't request this, you can safely ignore this email.</p>",
|
|
"mailer_subjects_invite": "You've been invited",
|
|
"mailer_templates_invite_content": "<h2>You've been invited</h2><p>You've been invited to create an account. Follow the link below to accept.</p><p><a href=\"{{ .ConfirmationURL }}\">Accept invitation</a></p>",
|
|
"mailer_subjects_reauthentication": "{{ .Token }} is your verification code",
|
|
"mailer_templates_reauthentication_content": "<h2>Your verification code</h2><p>Use the code below to verify your identity. It expires shortly.</p><p>{{ .Token }}</p>",
|
|
"mailer_subjects_email_change": "Confirm your new email address",
|
|
"mailer_templates_email_change_content": "<h2>Confirm your new email address</h2><p>Follow the link below to confirm {{ .NewEmail }} as your new email address.</p><p><a href=\"{{ .ConfirmationURL }}\">Confirm new email address</a></p><p>If you didn't request this change, you can safely ignore this email.</p>",
|
|
"mailer_notifications_password_changed_enabled": true,
|
|
"mailer_subjects_password_changed_notification": "Your password was changed",
|
|
"mailer_templates_password_changed_notification_content": "<h2>Your password was changed</h2>\n\n<p>The password for your account was recently changed.</p>\n<p>If you didn't make this change, reset your password and contact support immediately.</p>",
|
|
"mailer_notifications_email_changed_enabled": true,
|
|
"mailer_subjects_email_changed_notification": "Your email address was changed",
|
|
"mailer_templates_email_changed_notification_content": "<h2>Your email address was changed</h2>\n\n<p>The email address for your account was changed from {{ .OldEmail }} to {{ .Email }}.</p>\n<p>If you didn't make this change, contact support immediately.</p>",
|
|
"mailer_notifications_phone_changed_enabled": true,
|
|
"mailer_subjects_phone_changed_notification": "Your phone number was changed",
|
|
"mailer_templates_phone_changed_notification_content": "<h2>Your phone number was changed</h2>\n\n<p>The phone number for your account was changed from {{ .OldPhone }} to {{ .Phone }}.</p>\n<p>If you didn't make this change, contact support immediately.</p>",
|
|
"mailer_notifications_mfa_factor_enrolled_enabled": true,
|
|
"mailer_subjects_mfa_factor_enrolled_notification": "A new verification method was added to your account",
|
|
"mailer_templates_mfa_factor_enrolled_notification_content": "<h2>A new verification method was added</h2>\n\n<p>Sign-in verification method {{ .FactorType }} was added to your account.</p>\n<p>If you didn't make this change, contact support immediately.</p>",
|
|
"mailer_notifications_mfa_factor_unenrolled_enabled": true,
|
|
"mailer_subjects_mfa_factor_unenrolled_notification": "A verification method was removed from your account",
|
|
"mailer_templates_mfa_factor_unenrolled_notification_content": "<h2>A verification method was removed</h2>\n\n<p>Sign-in verification method {{ .FactorType }} was removed from your account.</p>\n<p>If you didn't make this change, contact support immediately.</p>",
|
|
"mailer_notifications_identity_linked_enabled": true,
|
|
"mailer_subjects_identity_linked_notification": "A sign-in method was linked to your account",
|
|
"mailer_templates_identity_linked_notification_content": "<h2>A sign-in method was linked</h2>\n\n<p>Your {{ .Provider }} account was linked as a sign-in method for {{ .Email }}.</p>\n<p>If you didn't make this change, contact support immediately.</p>",
|
|
"mailer_notifications_identity_unlinked_enabled": true,
|
|
"mailer_subjects_identity_unlinked_notification": "A sign-in method was removed from your account",
|
|
"mailer_templates_identity_unlinked_notification_content": "<h2>A sign-in method was removed</h2>\n\n<p>Your {{ .Provider }} account was removed as a sign-in method for {{ .Email }}.</p>\n<p>If you didn't make this change, contact support immediately.</p>"
|
|
}'
|
|
```
|
|
|
|
## Mobile deep linking
|
|
|
|
For mobile applications, you might need to link or redirect to a specific page within your app. See the [Mobile Deep Linking guide](/docs/guides/auth/native-mobile-deep-linking) to set this up.
|
|
|
|
## Limitations
|
|
|
|
### Email prefetching
|
|
|
|
Certain email providers may have spam detection or other security features that prefetch URL links from incoming emails (e.g. [Safe Links in Microsoft Defender for Office 365](https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/safe-links-about?view=o365-worldwide)).
|
|
In this scenario, the `{{ .ConfirmationURL }}` sent will be consumed instantly which leads to a "Token has expired or is invalid" error.
|
|
To guard against this there are the options below:
|
|
|
|
**Option 1**
|
|
|
|
- Use an email OTP instead by including `{{ .Token }}` in the email template
|
|
- Create your own custom email link to redirect the user to a page where they can enter their email and token to sign in
|
|
|
|
```html
|
|
<a href="{{ .SiteURL }}/confirm-signup">Confirm email address</a>
|
|
```
|
|
|
|
- Log them in by verifying the OTP token value with their email e.g. with [`supabase.auth.verifyOtp`](/docs/reference/javascript/auth-verifyotp) show below
|
|
|
|
```ts
|
|
const { data, error } = await supabase.auth.verifyOtp({ email, token, type: 'email' })
|
|
```
|
|
|
|
**Option 2**
|
|
|
|
- Create your own custom email link to redirect the user to a page where they can click on a button to confirm the action
|
|
|
|
```html
|
|
<a href="{{ .SiteURL }}/confirm-signup?confirmation_url={{ .ConfirmationURL }}">
|
|
Confirm email address
|
|
</a>
|
|
```
|
|
|
|
- The button should contain the actual confirmation link which can be obtained from parsing the `confirmation_url={{ .ConfirmationURL }}` query parameter in the URL.
|
|
|
|
### Email tracking
|
|
|
|
If you are using an external email provider that enables "email tracking", the links inside the Supabase email templates will be overwritten and won't perform as expected. We recommend disabling email tracking to ensure email links are not overwritten.
|
|
|
|
### Redirecting the user to a server-side endpoint
|
|
|
|
If you intend to use [Server-side rendering](/docs/guides/auth/server-side/advanced-guide), you might want the email link to redirect the user to a server-side endpoint to check if they are authenticated before returning the page. However, the default email link will redirect the user after verification to the redirect URL with the session in the query fragments. Since the session is returned in the query fragments by default, you won't be able to access it on the server-side.
|
|
|
|
You can customize the email link in the email template to redirect the user to a server-side endpoint successfully. For example:
|
|
|
|
```html
|
|
<a
|
|
href="https://api.example.com/v1/authenticate?token_hash={{ .TokenHash }}&type=invite&redirect_to={{ .RedirectTo }}"
|
|
>
|
|
Accept the invite
|
|
</a>
|
|
```
|
|
|
|
When the user clicks on the link, the request will hit `https://api.example.com/v1/authenticate` and you can grab the `token_hash`, `type` and `redirect_to` query parameters from the URL. Then, you can call the [`verifyOtp`](/docs/reference/javascript/auth-verifyotp) method to get back an authenticated session before redirecting the user back to the client. Since the `verifyOtp` method makes a `POST` request to Supabase Auth to verify the user, the session will be returned in the response body, which can be read by the server. For example:
|
|
|
|
```ts
|
|
import { createClient, type EmailOtpType } from '@supabase/supabase-js'
|
|
|
|
const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...')
|
|
|
|
// ---cut---
|
|
const { token_hash, type } = Object.fromEntries(new URLSearchParams(window.location.search))
|
|
const {
|
|
data: { session },
|
|
error,
|
|
} = await supabase.auth.verifyOtp({ token_hash, type: type as EmailOtpType })
|
|
|
|
// subsequently redirect the user back to the client using the redirect_to param
|
|
// ...
|
|
```
|
|
|
|
## Customization
|
|
|
|
Supabase Auth makes use of [Go Templates](https://pkg.go.dev/text/template). This means it is possible to conditionally render information based on template properties.
|
|
|
|
### Send different email to early access users
|
|
|
|
Send a different email to users who signed up via an early access domain (`https://www.earlyaccess.trial.com`).
|
|
|
|
```
|
|
{{ if eq .Data.Domain "https://www.example.com" }}
|
|
<h1>Welcome to Our Database Service!</h1>
|
|
<p>Dear Developer,</p>
|
|
<p>Welcome to Billy, the scalable developer platform!</p>
|
|
<p>Best Regards,<br>
|
|
Billy Team</p>
|
|
{{ else if eq .Data.Domain "https://www.earlyaccess.trial.com" }}
|
|
<h1>Welcome to Our Database Service!</h1>
|
|
<p>Dear Developer,</p>
|
|
<p>Welcome Billy, the scalable developer platform!</p>
|
|
<p> As an early access member, you have access to select features like Point To Space Restoration.</p>
|
|
<p>Best Regards,<br>
|
|
Billy Team</p>
|
|
{{ end }}
|
|
```
|