From d245db91feea187501fb014f95fce4b83d13cc62 Mon Sep 17 00:00:00 2001 From: Danny White <3104761+dnywh@users.noreply.github.com> Date: Thu, 30 Apr 2026 18:23:04 +1000 Subject: [PATCH] restrict auth email template editing --- .../guides/auth/auth-email-passwordless.mdx | 6 +- .../guides/auth/auth-email-templates.mdx | 52 ++++++------- .../auth/auth-hooks/send-email-hook.mdx | 54 ++++++------- apps/docs/content/guides/auth/passwords.mdx | 12 +-- .../getting-started/tutorials/with-nextjs.mdx | 2 +- .../customizing-email-templates.mdx | 51 ++++++------ .../self-hosting/custom-email-templates.mdx | 2 +- .../Auth/EmailTemplates/EmailTemplates.tsx | 50 ++++++++++-- .../EmailTemplates.utils.test.ts | 78 +++++++++++++++++++ .../EmailTemplates/EmailTemplates.utils.ts | 41 ++++++++++ .../Auth/EmailTemplates/TemplateEditor.tsx | 12 ++- .../[ref]/auth/templates/[templateId].tsx | 33 +++++++- .../docs/nextjs/password-based-auth.mdx | 12 +-- .../docs/nuxtjs/password-based-auth.mdx | 12 +-- .../docs/react-router/password-based-auth.mdx | 12 +-- .../docs/tanstack/password-based-auth.mdx | 12 +-- .../supabase/templates/confirmation.html | 6 +- .../supabase/templates/recovery.html | 6 +- .../supabase/auth/email/magic-link.html | 6 +- .../supabase/config.toml | 4 +- 20 files changed, 326 insertions(+), 137 deletions(-) create mode 100644 apps/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.utils.test.ts diff --git a/apps/docs/content/guides/auth/auth-email-passwordless.mdx b/apps/docs/content/guides/auth/auth-email-passwordless.mdx index b98a1f43a0f..647211150ba 100644 --- a/apps/docs/content/guides/auth/auth-email-passwordless.mdx +++ b/apps/docs/content/guides/auth/auth-email-passwordless.mdx @@ -145,10 +145,10 @@ That's it for the implicit flow. If you're using PKCE flow, edit the Magic Link [email template](/docs/guides/auth/auth-email-templates) to send a token hash: ```html -

Magic Link

+

Sign in to your account

-

Follow this link to login:

-

Log In

+

Use this link to sign in to your account:

+

Sign in

``` At the `/auth/confirm` endpoint, exchange the hash for the session: diff --git a/apps/docs/content/guides/auth/auth-email-templates.mdx b/apps/docs/content/guides/auth/auth-email-templates.mdx index 0aa0cb881bb..24d27ba3474 100644 --- a/apps/docs/content/guides/auth/auth-email-templates.mdx +++ b/apps/docs/content/guides/auth/auth-email-templates.mdx @@ -68,39 +68,39 @@ 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 signup", - "mailer_templates_confirmation_content": "

Confirm your signup

Follow this link to confirm your user:

Confirm your email

", - "mailer_subjects_magic_link": "Your Magic Link", - "mailer_templates_magic_link_content": "

Magic Link

Follow this link to login:

Log In

", - "mailer_subjects_recovery": "Rest Your Password", - "mailer_templates_recovery_content": "

Reset Password

Follow this link to reset the password for your user:

Reset Password

", + "mailer_subjects_confirmation": "Confirm your email", + "mailer_templates_confirmation_content": "

Confirm your email

Confirm this email address to finish setting up your account.

Confirm your email address

", + "mailer_subjects_magic_link": "Your sign-in link", + "mailer_templates_magic_link_content": "

Sign in to your account

Use this link to sign in to your account.

Sign in

", + "mailer_subjects_recovery": "Reset your password", + "mailer_templates_recovery_content": "

Reset password

Use this link to choose a new password for your account.

Reset password

", "mailer_subjects_invite": "You have been invited", - "mailer_templates_invite_content": "

You have been invited

You have been invited to create a user on {{ .SiteURL }}. Follow this link to accept the invite:

Accept the invite

", + "mailer_templates_invite_content": "

You have been invited

You have been invited to create an account.

Accept invitation

", "mailer_subjects_reauthentication": "Confirm reauthentication", - "mailer_templates_reauthentication_content": "

Confirm reauthentication

Enter the code: {{token}}

", - "mailer_subjects_email_change": "Confirm email change", - "mailer_templates_email_change_content": "

Confirm email change

Follow this link to confirm the update of your email:

Change email

", + "mailer_templates_reauthentication_content": "

Confirm reauthentication

Enter this code to continue: {{token}}

", + "mailer_subjects_email_change": "Confirm your new email", + "mailer_templates_email_change_content": "

Confirm your new email

Confirm changing the email address for your account.

Confirm new email

", "mailer_notifications_password_changed_enabled": true, - "mailer_subjects_password_changed_notification": "Your password has been changed", - "mailer_templates_password_changed_notification_content": "

Your password has been changed

\n\n

This is a confirmation that the password for your account {{ .Email }} has just been changed.

\n

If you did not make this change, please contact support.

", + "mailer_subjects_password_changed_notification": "Your password was changed", + "mailer_templates_password_changed_notification_content": "

Your password was changed

\n\n

The password for your account {{ .Email }} was changed.

\n

If you did not make this change, contact support for your account.

", "mailer_notifications_email_changed_enabled": true, - "mailer_subjects_email_changed_notification": "Your email address has been changed", - "mailer_templates_email_changed_notification_content": "

Your email address has been changed

\n\n

The email address for your account has been changed from {{ .OldEmail }} to {{ .Email }}.

\n

If you did not make this change, please contact support.

", + "mailer_subjects_email_changed_notification": "Your email address was changed", + "mailer_templates_email_changed_notification_content": "

Your email address was changed

\n\n

The email address for your account was changed from {{ .OldEmail }} to {{ .Email }}.

\n

If you did not make this change, contact support for your account.

", "mailer_notifications_phone_changed_enabled": true, - "mailer_subjects_phone_changed_notification": "Your phone number has been changed", - "mailer_templates_phone_changed_notification_content": "

Your phone number has been changed

\n\n

The phone number for your account {{ .Email }} has been changed from {{ .OldPhone }} to {{ .Phone }}.

\n

If you did not make this change, please contact support immediately.

", + "mailer_subjects_phone_changed_notification": "Your phone number was changed", + "mailer_templates_phone_changed_notification_content": "

Your phone number was changed

\n\n

The phone number for your account {{ .Email }} was changed from {{ .OldPhone }} to {{ .Phone }}.

\n

If you did not make this change, contact support for your account.

", "mailer_notifications_mfa_factor_enrolled_enabled": true, - "mailer_subjects_mfa_factor_enrolled_notification": "A new MFA factor has been enrolled", - "mailer_templates_mfa_factor_enrolled_notification_content": "

A new MFA factor has been enrolled

\n\n

A new factor ({{ .FactorType }}) has been enrolled for your account {{ .Email }}.

\n

If you did not make this change, please contact support immediately.

", + "mailer_subjects_mfa_factor_enrolled_notification": "A new MFA factor was added", + "mailer_templates_mfa_factor_enrolled_notification_content": "

A new MFA factor was added

\n\n

A new {{ .FactorType }} MFA factor was added to your account {{ .Email }}.

\n

If you did not make this change, contact support for your account.

", "mailer_notifications_mfa_factor_unenrolled_enabled": true, - "mailer_subjects_mfa_factor_unenrolled_notification": "An MFA factor has been unenrolled", - "mailer_templates_mfa_factor_unenrolled_notification_content": "

An MFA factor has been unenrolled

\n\n

A factor ({{ .FactorType }}) has been unenrolled for your account {{ .Email }}.

\n

If you did not make this change, please contact support immediately.

", + "mailer_subjects_mfa_factor_unenrolled_notification": "An MFA factor was removed", + "mailer_templates_mfa_factor_unenrolled_notification_content": "

An MFA factor was removed

\n\n

A {{ .FactorType }} MFA factor was removed from your account {{ .Email }}.

\n

If you did not make this change, contact support for your account.

", "mailer_notifications_identity_linked_enabled": true, - "mailer_subjects_identity_linked_notification": "A new identity has been linked", - "mailer_templates_identity_linked_notification_content": "

A new identity has been linked

\n\n

A new identity ({{ .Provider }}) has been linked to your account {{ .Email }}.

\n

If you did not make this change, please contact support immediately.

", + "mailer_subjects_identity_linked_notification": "A new identity was linked", + "mailer_templates_identity_linked_notification_content": "

A new identity was linked

\n\n

A new {{ .Provider }} identity was linked to your account {{ .Email }}.

\n

If you did not make this change, contact support for your account.

", "mailer_notifications_identity_unlinked_enabled": true, - "mailer_subjects_identity_unlinked_notification": "An identity has been unlinked", - "mailer_templates_identity_unlinked_notification_content": "

An identity has been unlinked

\n\n

An identity ({{ .Provider }}) has been unlinked from your account {{ .Email }}.

\n

If you did not make this change, please contact support immediately.

" + "mailer_subjects_identity_unlinked_notification": "An identity was unlinked", + "mailer_templates_identity_unlinked_notification_content": "

An identity was unlinked

\n\n

A {{ .Provider }} identity was unlinked from your account {{ .Email }}.

\n

If you did not make this change, contact support for your account.

" }' ``` @@ -122,7 +122,7 @@ To guard against this there are the options below: - Create your own custom email link to redirect the user to a page where they can enter with their email and token to login ```html -Confirm your signup +Confirm your email ``` - 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 @@ -137,7 +137,7 @@ const { data, error } = await supabase.auth.verifyOtp({ email, token, type: 'ema ```html - Confirm your signup + Confirm your email ``` diff --git a/apps/docs/content/guides/auth/auth-hooks/send-email-hook.mdx b/apps/docs/content/guides/auth/auth-hooks/send-email-hook.mdx index ac5a90549ef..33868bd5105 100644 --- a/apps/docs/content/guides/auth/auth-hooks/send-email-hook.mdx +++ b/apps/docs/content/guides/auth/auth-hooks/send-email-hook.mdx @@ -642,8 +642,8 @@ supabase functions deploy send-email --no-verify-jwt Your company is expanding to France and Spain. As part of expansion efforts, the company would like to deliver internationalized email templates to best support local users in their native language. Ensure that you have configured `POSTMARK_SERVER_TOKEN` and `SEND_EMAIL_HOOK_SECRET` in your `.env` file. ```javascript -import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0' import { readAll } from 'https://deno.land/std/io/read_all.ts' +import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0' const postmarkEndpoint = 'https://api.postmarkapp.com/email' // Replace this with your email @@ -653,13 +653,13 @@ const PROJECT_REF = '' // Email Subjects const subjects = { en: { - signup: 'Confirm Your Email', - recovery: 'Reset Your Password', + signup: 'Confirm your email', + recovery: 'Reset your password', invite: 'You have been invited', - magiclink: 'Your Magic Link', - email_change: 'Confirm Email Change', - email_change_new: 'Confirm New Email Address', - reauthentication: 'Confirm Reauthentication', + magiclink: 'Your sign-in link', + email_change: 'Confirm your new email', + email_change_new: 'Confirm new email address', + reauthentication: 'Confirm reauthentication', }, es: { signup: 'Confirma tu correo electrónico', @@ -684,13 +684,13 @@ const subjects = { // HTML Body const templates = { en: { - signup: `

Confirm your email

Follow this link to confirm your email:

Confirm your email address

Alternatively, enter the code: {{token}}

`, - recovery: `

Reset password

Follow this link to reset the password for your user:

Reset password

Alternatively, enter the code: {{token}}

`, - invite: `

You have been invited

You have been invited to create a user on {{site_url}}. Follow this link to accept the invite:

Accept the invite

Alternatively, enter the code: {{token}}

`, - magiclink: `

Magic Link

Follow this link to login:

Log In

Alternatively, enter the code: {{token}}

`, - email_change: `

Confirm email address change

Follow this link to confirm the update of your email address from {{old_email}} to {{new_email}}:

Change email address

Alternatively, enter the codes: {{token}} and {{new_token}}

`, - email_change_new: `

Confirm New Email Address

Follow this link to confirm your new email address:

Confirm new email address

Alternatively, enter the code: {{new_token}}

`, - reauthentication: `

Confirm reauthentication

Enter the code: {{token}}

`, + signup: `

Confirm your email

Confirm this email address to finish setting up your account.

Confirm your email address

Alternatively, enter this code: {{token}}

`, + recovery: `

Reset password

Use this link to choose a new password for your account.

Reset password

Alternatively, enter this code: {{token}}

`, + invite: `

You have been invited

You have been invited to create an account.

Accept invitation

Alternatively, enter this code: {{token}}

`, + magiclink: `

Sign in to your account

Use this link to sign in to your account.

Sign in

Alternatively, enter this code: {{token}}

`, + email_change: `

Confirm your new email

Confirm changing the email address for your account from {{old_email}} to {{new_email}}.

Confirm new email

Alternatively, enter these codes: {{token}} and {{new_token}}

`, + email_change_new: `

Confirm new email address

Confirm this new email address for your account.

Confirm new email address

Alternatively, enter this code: {{new_token}}

`, + reauthentication: `

Confirm reauthentication

Enter this code to continue: {{token}}

`, }, es: { signup: `

Confirma tu correo electrónico

Sigue este enlace para confirmar tu correo electrónico:

Confirma tu correo electrónico

Alternativamente, ingresa el código: {{token}}

`, @@ -810,24 +810,24 @@ const FROM_EMAIL = 'myemail@gmail.com' // Email Subjects const subjects = { - signup: 'Confirm Your Email', - recovery: 'Reset Your Password', + signup: 'Confirm your email', + recovery: 'Reset your password', invite: 'You have been invited', - magiclink: 'Your Magic Link', - email_change: 'Confirm Email Change', - email_change_new: 'Confirm New Email Address', - reauthentication: 'Confirm Reauthentication' + magiclink: 'Your sign-in link', + email_change: 'Confirm your new email', + email_change_new: 'Confirm new email address', + reauthentication: 'Confirm reauthentication' }; // HTML Body const templates = { - signup: `

Confirm your email

Follow this link to confirm your email:

Confirm your email address

Alternatively, enter the code: {{token}}

`, - recovery: `

Reset password

Follow this link to reset the password for your user:

Reset password

Alternatively, enter the code: {{token}}

`, - invite: `

You have been invited

You have been invited to create a user on {{site_url}}. Follow this link to accept the invite:

Accept the invite

Alternatively, enter the code: {{token}}

`, - magiclink: `

Magic Link

Follow this link to login:

Log In

Alternatively, enter the code: {{token}}

`, - email_change: `

Confirm email address change

Follow this link to confirm the update of your email address from {{old_email}} to {{new_email}}:

Change email address

Alternatively, enter the codes: {{token}} and {{new_token}}

`, - email_change_new: `

Confirm New Email Address

Follow this link to confirm your new email address:

Confirm new email address

Alternatively, enter the code: {{new_token}}

`, - reauthentication: `

Confirm reauthentication

Enter the code: {{token}}

` + signup: `

Confirm your email

Confirm this email address to finish setting up your account.

Confirm your email address

Alternatively, enter this code: {{token}}

`, + recovery: `

Reset password

Use this link to choose a new password for your account.

Reset password

Alternatively, enter this code: {{token}}

`, + invite: `

You have been invited

You have been invited to create an account.

Accept invitation

Alternatively, enter this code: {{token}}

`, + magiclink: `

Sign in to your account

Use this link to sign in to your account.

Sign in

Alternatively, enter this code: {{token}}

`, + email_change: `

Confirm your new email

Confirm changing the email address for your account from {{old_email}} to {{new_email}}.

Confirm new email

Alternatively, enter these codes: {{token}} and {{new_token}}

`, + email_change_new: `

Confirm new email address

Confirm this new email address for your account.

Confirm new email address

Alternatively, enter this code: {{new_token}}

`, + reauthentication: `

Confirm reauthentication

Enter this code to continue: {{token}}

` }; function generateConfirmationURL(email_data) { diff --git a/apps/docs/content/guides/auth/passwords.mdx b/apps/docs/content/guides/auth/passwords.mdx index dae2ff5cd26..c98bfb86c89 100644 --- a/apps/docs/content/guides/auth/passwords.mdx +++ b/apps/docs/content/guides/auth/passwords.mdx @@ -160,13 +160,13 @@ Update your signup email template to send the token hash. For detailed instructi Your signup email template should contain the following HTML: ```html -

Confirm your signup

+

Confirm your email

-

Follow this link to confirm your user:

+

Confirm this email address to finish setting up your account.

Confirm your emailConfirm your email address

``` @@ -682,13 +682,13 @@ Update your reset password email template to send the token hash. See [Email Tem Your reset password email template should contain the following HTML: ```html -

Reset Password

+

Reset password

-

Follow this link to reset the password for your user:

+

Use this link to choose a new password for your account.

Reset PasswordReset password

``` diff --git a/apps/docs/content/guides/getting-started/tutorials/with-nextjs.mdx b/apps/docs/content/guides/getting-started/tutorials/with-nextjs.mdx index 5144851b5cf..9f324c0cf2b 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-nextjs.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-nextjs.mdx @@ -248,7 +248,7 @@ npm run dev And then open the browser to [localhost:3000/login](http://localhost:3000/login) and you should see the completed app. -When you enter your email and password, you will receive an email with the title **Confirm Your Signup**. Congrats 🎉!!! +When you enter your email and password, you will receive an email with the title **Confirm your email**. Congrats 🎉!!! ## Bonus: Profile photos diff --git a/apps/docs/content/guides/local-development/customizing-email-templates.mdx b/apps/docs/content/guides/local-development/customizing-email-templates.mdx index 60bcb1ea69e..15908b72442 100644 --- a/apps/docs/content/guides/local-development/customizing-email-templates.mdx +++ b/apps/docs/content/guides/local-development/customizing-email-templates.mdx @@ -23,8 +23,9 @@ content_path = "./supabase/templates/invite.html" ```html name=supabase/templates/invite.html -

Confirm your signup

-

Confirm your email

+

Confirm your email

+

Confirm this email address to finish setting up your account.

+

Confirm your email address

``` @@ -38,17 +39,15 @@ content_path = "./supabase/templates/invite.html" ```toml name=supabase/config.toml [auth.email.notification.password_changed] enabled = true -subject = "Your password has been changed" +subject = "Your password was changed" content_path = "./templates/password_changed_notification.html" ``` ```html name=templates/password_changed_notification.html -

- This is a confirmation that the password for your account {{ .Email }} has just been changed. -

-

If you did not make this change, please contact support.

+

The password for your account {{ .Email }} was changed.

+

If you did not make this change, contact support for your account.

``` @@ -68,35 +67,35 @@ There are several authentication-related email templates which can be configured ### `auth.email.template.confirmation` -**Default subject**: "Confirm Your Signup" +**Default subject**: "Confirm your email" **When sent**: When a user signs up and needs to verify their email address **Purpose**: Ask users to confirm their email address after signing up **Content**: Contains a confirmation link to verify the user's email address ### `auth.email.template.recovery` -**Default subject**: "Reset Your Password" +**Default subject**: "Reset your password" **When sent**: When a user requests a password reset **Purpose**: Allow users to reset their password if they forget it **Content**: Contains a link to reset the user's password ### `auth.email.template.magic_link` -**Default subject**: "Your Magic Link" +**Default subject**: "Your sign-in link" **When sent**: When a user requests a magic link for passwordless authentication **Purpose**: Allow users to sign in via a one-time link sent to their email **Content**: Contains a secure link that automatically logs the user in when clicked ### `auth.email.template.email_change` -**Default subject**: "Confirm Email Change" +**Default subject**: "Confirm your new email" **When sent**: When a user requests to change their email address **Purpose**: Ask users to verify their new email address after changing it **Content**: Contains a confirmation link to verify the new email address ### `auth.email.template.reauthentication` -**Default subject**: "Confirm Reauthentication" +**Default subject**: "Confirm reauthentication" **When sent**: When a user needs to re-authenticate for sensitive operations **Purpose**: Ask users to re-authenticate before performing a sensitive action **Content**: Contains a 6-digit OTP code for verification @@ -107,49 +106,49 @@ There are several security notification email templates which can be configured. ### `auth.email.notification.password_changed` -**Default subject**: "Your password has been changed" +**Default subject**: "Your password was changed" **When sent**: When a user's password is changed **Purpose**: Notify users when their password has changed **Content**: Confirms that the password for the account has been changed ### `auth.email.notification.email_changed` -**Default subject**: "Your email address has been changed" +**Default subject**: "Your email address was changed" **When sent**: When a user's email address is changed **Purpose**: Notify users when their email address has changed **Content**: Confirms the change from the old email to the new email address ### `auth.email.notification.phone_changed` -**Default subject**: "Your phone number has been changed" +**Default subject**: "Your phone number was changed" **When sent**: When a user's phone number is changed **Purpose**: Notify users when their phone number has changed **Content**: Confirms the change from the old phone number to the new phone number ### `auth.email.notification.mfa_factor_enrolled` -**Default subject**: "A new MFA factor has been enrolled" +**Default subject**: "A new MFA factor was added" **When sent**: When a new MFA factor is added to the user's account **Purpose**: Notify users when a new multi-factor authentication method has been added to their account **Content**: Confirms that a new MFA factor type has been enrolled ### `auth.email.notification.mfa_factor_unenrolled` -**Default subject**: "An MFA factor has been unenrolled" +**Default subject**: "An MFA factor was removed" **When sent**: When an MFA factor is removed from the user's account **Purpose**: Notify users when a multi-factor authentication method has been removed from their account **Content**: Confirms that an MFA factor type has been unenrolled ### `auth.email.notification.identity_linked` -**Default subject**: "A new identity has been linked" +**Default subject**: "A new identity was linked" **When sent**: When a new identity is linked to the account **Purpose**: Notify users when a new identity has been linked to their account **Content**: Confirms that a new identity has been linked ### `auth.email.notification.identity_unlinked` -**Default subject**: "An identity has been unlinked" +**Default subject**: "An identity was unlinked" **When sent**: When an identity has been unlinked from the account **Purpose**: Notify users when an identity has been unlinked from their account **Content**: Confirms that an identity has been unlinked @@ -189,10 +188,10 @@ Contains a hashed version of the `Token`. This is useful for constructing your o **Usage** ```html -

Follow this link to confirm your user:

+

Confirm this email address to finish setting up your account.

Confirm your emailConfirm your email address

``` @@ -234,7 +233,7 @@ Contains the user's old email address. This is only available in the `email_chan **Usage** ```html -

The email address for your account has been changed from {{ .OldEmail }} to {{ .Email }}.

+

The email address for your account was changed from {{ .OldEmail }} to {{ .Email }}.

``` ### `Phone` @@ -244,7 +243,7 @@ Contains the user's new phone number. This is only available in the `phone_chang **Usage** ```html -

The phone number for your account has been changed from {{ .OldPhone }} to {{ .Phone }}.

+

The phone number for your account was changed from {{ .OldPhone }} to {{ .Phone }}.

``` ### `OldPhone` @@ -254,7 +253,7 @@ Contains the user's old phone number. This is only available in the `phone_chang **Usage** ```html -

The phone number for your account has been changed from {{ .OldPhone }} to {{ .Phone }}.

+

The phone number for your account was changed from {{ .OldPhone }} to {{ .Phone }}.

``` ### `Provider` @@ -264,7 +263,7 @@ Contains the provider of the newly linked/unlinked identity. This is only availa **Usage** ```html -

A new identity ({{ .Provider }}) has been linked to your account.

+

A new {{ .Provider }} identity was linked to your account.

``` ### `FactorType` @@ -274,7 +273,7 @@ Contains the type of the newly enrolled/unenrolled MFA factor. This is only avai **Usage** ```html -

A new factor ({{ .FactorType }}) has been enrolled for your account.

+

A new {{ .FactorType }} MFA factor was added to your account.

``` ## Deploying email templates diff --git a/apps/docs/content/guides/self-hosting/custom-email-templates.mdx b/apps/docs/content/guides/self-hosting/custom-email-templates.mdx index 090dfc390c3..694ab393496 100644 --- a/apps/docs/content/guides/self-hosting/custom-email-templates.mdx +++ b/apps/docs/content/guides/self-hosting/custom-email-templates.mdx @@ -159,7 +159,7 @@ services: environment: GOTRUE_MAILER_NOTIFICATIONS_PASSWORD_CHANGED_ENABLED: 'true' # 👈 enabling the notification is required GOTRUE_MAILER_TEMPLATES_PASSWORD_CHANGED_NOTIFICATION: 'http://templates-server/password_changed_notification.html' - GOTRUE_MAILER_SUBJECTS_PASSWORD_CHANGED_NOTIFICATION: 'Your password has been changed' + GOTRUE_MAILER_SUBJECTS_PASSWORD_CHANGED_NOTIFICATION: 'Your password was changed' templates-server: image: caddy:2-alpine diff --git a/apps/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.tsx b/apps/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.tsx index 2f7f6c4e780..a207520ada8 100644 --- a/apps/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.tsx +++ b/apps/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.tsx @@ -19,12 +19,18 @@ import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' import * as z from 'zod' import { TEMPLATES_SCHEMAS } from '../AuthTemplatesValidation' -import { slugifyTitle } from './EmailTemplates.utils' +import { + hasCustomEmailSender, + isCustomEmailTemplateEditingRestricted, + slugifyTitle, +} from './EmailTemplates.utils' import AlertError from '@/components/ui/AlertError' import { InlineLink } from '@/components/ui/InlineLink' import { useAuthConfigQuery } from '@/data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from '@/data/auth/auth-config-update-mutation' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' +import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' +import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { DOCS_URL } from '@/lib/constants' const notificationEnabledKeys = TEMPLATES_SCHEMAS.filter( @@ -67,10 +73,14 @@ export const EmailTemplates = () => { }, }) - const builtInSMTP = - isSuccess && - authConfig && - (!authConfig.SMTP_HOST || !authConfig.SMTP_USER || !authConfig.SMTP_PASS) + const { data: selectedOrganization } = useSelectedOrganizationQuery() + const { data: selectedProject } = useSelectedProjectQuery() + const usingBuiltInEmailSender = isSuccess && authConfig && !hasCustomEmailSender(authConfig) + const isTemplateEditBlocked = isCustomEmailTemplateEditingRestricted({ + authConfig, + organization: selectedOrganization, + project: selectedProject, + }) const defaultValues = notificationEnabledKeys.reduce( (acc, key) => { @@ -115,8 +125,36 @@ export const EmailTemplates = () => { )} {isSuccess && ( <> + {isTemplateEditBlocked && ( + + + + Free projects using Supabase's built-in email service can view the default + email templates, but cannot edit their subject or HTML. Set up custom SMTP, + configure a send-email hook, or upgrade to customise templates.{' '} + + Learn more + +

+ } + actions={ + + } + /> +
+
+ )} + - {builtInSMTP && ( + {usingBuiltInEmailSender && !isTemplateEditBlocked && ( { + it('restricts free projects that use the built-in email sender', () => { + expect( + isCustomEmailTemplateEditingRestricted({ + authConfig: {}, + organization: freeOrganization, + project: restrictedProject, + }) + ).toBe(true) + }) + + it('allows older free projects that use the built-in email sender', () => { + expect( + isCustomEmailTemplateEditingRestricted({ + authConfig: {}, + organization: freeOrganization, + project: unrestrictedProject, + }) + ).toBe(false) + }) + + it('allows free projects with custom SMTP configured', () => { + const authConfig = { + SMTP_ADMIN_EMAIL: 'support@example.com', + SMTP_SENDER_NAME: 'Example', + SMTP_USER: 'smtp-user', + SMTP_HOST: 'smtp.example.com', + SMTP_PORT: '587', + SMTP_MAX_FREQUENCY: 60, + } + + expect(hasCustomEmailSender(authConfig)).toBe(true) + expect( + isCustomEmailTemplateEditingRestricted({ + authConfig, + organization: freeOrganization, + project: restrictedProject, + }) + ).toBe(false) + }) + + it('allows free projects with a configured send-email hook', () => { + expect( + isCustomEmailTemplateEditingRestricted({ + authConfig: { + HOOK_SEND_EMAIL_ENABLED: true, + HOOK_SEND_EMAIL_URI: 'https://example.com/auth/send-email', + }, + organization: freeOrganization, + project: restrictedProject, + }) + ).toBe(false) + }) + + it('allows paid projects using the built-in email sender', () => { + expect( + isCustomEmailTemplateEditingRestricted({ + authConfig: {}, + organization: proOrganization, + project: restrictedProject, + }) + ).toBe(false) + }) +}) diff --git a/apps/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.utils.ts b/apps/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.utils.ts index e1297392939..8c5dab3bd3d 100644 --- a/apps/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.utils.ts +++ b/apps/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.utils.ts @@ -1,3 +1,11 @@ +import { isSmtpEnabled } from '../SmtpForm/SmtpForm.utils' +import type { components } from '@/data/api' +import type { Project } from '@/data/projects/project-detail-query' +import type { Organization } from '@/types' + +type AuthConfig = components['schemas']['GoTrueConfigResponse'] +const CUSTOM_EMAIL_TEMPLATES_RESTRICTED_PROJECT_CUTOFF = '2026-05-01T00:00:00.000Z' + /** * Convert template title to URL-friendly slug * Shared function to ensure slug matching works correctly across multiple files @@ -6,3 +14,36 @@ export const slugifyTitle = (title: string) => { return title.trim().replace(/\s+/g, '-').toLowerCase() } + +export const hasCustomEmailSender = (config?: Partial) => { + const hasSendEmailHook = !!config?.HOOK_SEND_EMAIL_ENABLED && !!config?.HOOK_SEND_EMAIL_URI + + return isSmtpEnabled(config) || hasSendEmailHook +} + +export const isProjectInCustomEmailTemplateRestrictedCohort = ( + project?: Pick +) => { + const projectInsertedAtMs = Date.parse(project?.inserted_at ?? '') + + return ( + Number.isFinite(projectInsertedAtMs) && + projectInsertedAtMs >= Date.parse(CUSTOM_EMAIL_TEMPLATES_RESTRICTED_PROJECT_CUTOFF) + ) +} + +export const isCustomEmailTemplateEditingRestricted = ({ + authConfig, + organization, + project, +}: { + authConfig?: Partial + organization?: Organization + project?: Pick +}) => { + return ( + organization?.plan.id === 'free' && + isProjectInCustomEmailTemplateRestrictedCohort(project) && + !hasCustomEmailSender(authConfig) + ) +} diff --git a/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx b/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx index b701cdb43e7..8c68a6a1ed6 100644 --- a/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx +++ b/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx @@ -34,14 +34,16 @@ import type { FormSchema } from '@/types' interface TemplateEditorProps { template: FormSchema + isReadOnly?: boolean } -export const TemplateEditor = ({ template }: TemplateEditorProps) => { +export const TemplateEditor = ({ template, isReadOnly = false }: TemplateEditorProps) => { const { ref: projectRef } = useParams() const { can: canUpdateConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_gotrue' ) + const canEdit = canUpdateConfig && !isReadOnly const editorRef = useRef() // [Joshen] Error state is handled in the parent @@ -88,6 +90,7 @@ export const TemplateEditor = ({ template }: TemplateEditorProps) => { const onSubmit = (values: any) => { if (!projectRef) return console.error('Project ref is required') + if (!canEdit) return setIsSavingTemplate(true) @@ -259,7 +262,7 @@ export const TemplateEditor = ({ template }: TemplateEditorProps) => { } > - + )} @@ -289,7 +292,7 @@ export const TemplateEditor = ({ template }: TemplateEditorProps) => { { setBodyValue(e ?? '') @@ -310,6 +313,7 @@ export const TemplateEditor = ({ template }: TemplateEditorProps) => { size="tiny" className="rounded-full" onClick={() => insertTextAtCursor(variable)} + disabled={!canEdit} > {variable} @@ -357,7 +361,7 @@ export const TemplateEditor = ({ template }: TemplateEditorProps) => { + } + /> + + + )} + {showConfigurationSection && ( @@ -265,7 +294,7 @@ const RedirectToTemplates = () => { )} - + diff --git a/apps/ui-library/content/docs/nextjs/password-based-auth.mdx b/apps/ui-library/content/docs/nextjs/password-based-auth.mdx index 07b126ab2ea..52f76bd7672 100644 --- a/apps/ui-library/content/docs/nextjs/password-based-auth.mdx +++ b/apps/ui-library/content/docs/nextjs/password-based-auth.mdx @@ -40,13 +40,13 @@ NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY= 1. Add an [email template for sign-up](https://supabase.com/dashboard/project/_/auth/templates) to the Supabase project. Your signup email template should contain at least the following HTML: ```html -

Confirm your signup

+

Confirm your email

-

Follow this link to confirm your user:

+

Confirm this email address to finish setting up your account.

Confirm your emailConfirm your email address

``` @@ -56,13 +56,13 @@ NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY= 1. Add an [email template for reset password](https://supabase.com/dashboard/project/_/auth/templates) to the Supabase project. Your reset password email template should contain at least the following HTML: ```html -

Reset Password

+

Reset password

-

Follow this link to reset the password for your user:

+

Use this link to choose a new password for your account.

Reset PasswordReset password

``` diff --git a/apps/ui-library/content/docs/nuxtjs/password-based-auth.mdx b/apps/ui-library/content/docs/nuxtjs/password-based-auth.mdx index 6b606e77ca4..b887ed237c6 100644 --- a/apps/ui-library/content/docs/nuxtjs/password-based-auth.mdx +++ b/apps/ui-library/content/docs/nuxtjs/password-based-auth.mdx @@ -40,13 +40,13 @@ NUXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY= 1. Add an [email template for sign-up](https://supabase.com/dashboard/project/_/auth/templates) to the Supabase project. Your signup email template should contain at least the following HTML: ```html -

Confirm your signup

+

Confirm your email

-

Follow this link to confirm your user:

+

Confirm this email address to finish setting up your account.

Confirm your emailConfirm your email address

``` @@ -56,13 +56,13 @@ NUXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY= 1. Add an [email template for reset password](https://supabase.com/dashboard/project/_/auth/templates) to the Supabase project. Your reset password email template should contain at least the following HTML: ```html -

Reset Password

+

Reset password

-

Follow this link to reset the password for your user:

+

Use this link to choose a new password for your account.

Reset PasswordReset password

``` diff --git a/apps/ui-library/content/docs/react-router/password-based-auth.mdx b/apps/ui-library/content/docs/react-router/password-based-auth.mdx index a271e69d9ba..0dcb4c5b521 100644 --- a/apps/ui-library/content/docs/react-router/password-based-auth.mdx +++ b/apps/ui-library/content/docs/react-router/password-based-auth.mdx @@ -40,13 +40,13 @@ VITE_SUPABASE_PUBLISHABLE_KEY= 1. Add an [email template for sign-up](https://supabase.com/dashboard/project/_/auth/templates) to the Supabase project. Your signup email template should contain at least the following HTML: ```html -

Confirm your signup

+

Confirm your email

-

Follow this link to confirm your user:

+

Confirm this email address to finish setting up your account.

Confirm your emailConfirm your email address

``` @@ -56,13 +56,13 @@ VITE_SUPABASE_PUBLISHABLE_KEY= 1. Add an [email template for reset password](https://supabase.com/dashboard/project/_/auth/templates) to the Supabase project. Your reset password email template should contain at least the following HTML: ```html -

Reset Password

+

Reset password

-

Follow this link to reset the password for your user:

+

Use this link to choose a new password for your account.

Reset PasswordReset password

``` diff --git a/apps/ui-library/content/docs/tanstack/password-based-auth.mdx b/apps/ui-library/content/docs/tanstack/password-based-auth.mdx index 22380eae482..6854ba2d165 100644 --- a/apps/ui-library/content/docs/tanstack/password-based-auth.mdx +++ b/apps/ui-library/content/docs/tanstack/password-based-auth.mdx @@ -37,13 +37,13 @@ VITE_SUPABASE_PUBLISHABLE_KEY= 1. Add an [email template for sign-up](https://supabase.com/dashboard/project/_/auth/templates) to the Supabase project. Your signup email template should contain at least the following HTML: ```html -

Confirm your signup

+

Confirm your email

-

Follow this link to confirm your user:

+

Confirm this email address to finish setting up your account.

Confirm your emailConfirm your email address

``` @@ -53,13 +53,13 @@ VITE_SUPABASE_PUBLISHABLE_KEY= 1. Add an [email template for reset password](https://supabase.com/dashboard/project/_/auth/templates) to the Supabase project. Your reset password email template should contain at least the following HTML: ```html -

Reset Password

+

Reset password

-

Follow this link to reset the password for your user:

+

Use this link to choose a new password for your account.

Reset PasswordReset password

``` diff --git a/apps/ui-library/supabase/templates/confirmation.html b/apps/ui-library/supabase/templates/confirmation.html index 13948c48cff..a4949a7ddc7 100644 --- a/apps/ui-library/supabase/templates/confirmation.html +++ b/apps/ui-library/supabase/templates/confirmation.html @@ -1,9 +1,9 @@ -

Confirm your signup

+

Confirm your email

-

Follow this link to confirm your user:

+

Confirm this email address to finish setting up your account.

Confirm your emailConfirm your email address

diff --git a/apps/ui-library/supabase/templates/recovery.html b/apps/ui-library/supabase/templates/recovery.html index ce529ecfb17..0e5a607ab8b 100644 --- a/apps/ui-library/supabase/templates/recovery.html +++ b/apps/ui-library/supabase/templates/recovery.html @@ -1,9 +1,9 @@ -

Reset Password

+

Reset password

-

Follow this link to reset the password for your user:

+

Use this link to choose a new password for your account.

Reset PasswordReset password

diff --git a/examples/user-management/nextjs-user-management/supabase/auth/email/magic-link.html b/examples/user-management/nextjs-user-management/supabase/auth/email/magic-link.html index 25aa5f98a40..d2ecf1c4b1f 100644 --- a/examples/user-management/nextjs-user-management/supabase/auth/email/magic-link.html +++ b/examples/user-management/nextjs-user-management/supabase/auth/email/magic-link.html @@ -1,4 +1,4 @@ -

Magic Link

+

Sign in to your account

-

Follow this link to login:

-

Log In

+

Use this link to sign in to your account.

+

Sign in

diff --git a/examples/user-management/nextjs-user-management/supabase/config.toml b/examples/user-management/nextjs-user-management/supabase/config.toml index 34e7da418d8..1ca3ff27931 100644 --- a/examples/user-management/nextjs-user-management/supabase/config.toml +++ b/examples/user-management/nextjs-user-management/supabase/config.toml @@ -87,11 +87,11 @@ port = 54324 # sender_name = "Admin" [auth.email.template.confirmation] -subject = "Confirm Your Email" +subject = "Confirm your email" content_path = "./supabase/auth/email/confirmation.html" [auth.email.template.magic_link] -subject = "Your Magic Link" +subject = "Your sign-in link" content_path = "./supabase/auth/email/magic-link.html" [storage.buckets.avatars]