Skip to content

feat(otp): add support for test email OTPs - #2790

Open
itsybitsci wants to merge 1 commit into
supabase:masterfrom
itsybitsci:feat/email-test-otp
Open

feat(otp): add support for test email OTPs#2790
itsybitsci wants to merge 1 commit into
supabase:masterfrom
itsybitsci:feat/email-test-otp

Conversation

@itsybitsci

Copy link
Copy Markdown

What kind of change does this PR introduce?

Feature. Closes #901.

What is the current behavior?

Test OTPs exist for SMS (GOTRUE_SMS_TEST_OTP), but every email OTP is randomly generated and always sent. Teams cannot provide a fixed email + code pair for app store review accounts or end-to-end tests without a real inbox.

What is the new behavior?

Adds GOTRUE_MAILER_TEST_OTP and GOTRUE_MAILER_TEST_OTP_VALID_UNTIL, mirroring the SMS feature.

When a request targets a listed email and the configuration has not expired:

  • The configured code is used instead of crypto.GenerateOtp. The stored value is the normal crypto.GenerateTokenHash(email, otp) with the PKCE flow prefix, and the one-time token row is created as usual, so verify.go is unchanged: POST /verify with { type, email, token } recomputes the same hash.
  • No email is sent. sendEmail returns early before the global rate limiter, the Send Email hook and the mailer, and does not increment emailSendCounter.
  • The per-user frequency limit (SMTP.MaxFrequency) still applies, matching the SMS path which keeps Sms.MaxFrequency ahead of the test OTP lookup.
  • The authorized-addresses check still applies, so a test email must be an allowed address.
  • Email keys are normalized to lowercase and trimmed in ApplyDefaults, matching validateEmail.

Why not the send-email hook?

A send-email hook can already swallow mail for test addresses, but it requires deploying and maintaining an HTTP or Postgres function just to get a fixed code, and it still burns the email rate limit. SMS users get this as plain configuration today; this PR gives email users the same thing, so both channels behave consistently and hosted-platform users can be offered it as a setting.

Scope is deliberately limited to the flows the issue asks for: magic link / email OTP sign-in (sendMagicLink) and signup confirmation (sendConfirmation, which MagicLink goes through for a brand-new user when autoconfirm is off). Recovery, invite, reauthentication, email change and adminGenerateLink keep generating real OTPs. Each of those can adopt test OTPs later by swapping crypto.GenerateOtp for the new a.generateEmailOtp(email) helper and passing isTestOTP to sendEmail.

Design

  • MailerConfiguration.TestOTP map[string]string and TestOTPValidUntil conf.Time, plus GetTestOTP(email, now), copied from SmsProviderConfiguration.
  • (*API).generateEmailOtp(email) (otp string, isTestOTP bool) in internal/api/mail.go.
  • sendEmailParams.isTestOTP; sendEmail returns nil right after the authorization checks when it is set.

Tests

  • internal/conf: MailerConfiguration.GetTestOTP (nil map, hit, case-insensitive hit, miss, valid-until future, expired) and ApplyDefaults key normalization.
  • internal/api/email_test_otp_test.go: direct calls to sendMagicLink and sendConfirmation with and without a test OTP (implicit and PKCE), asserting the mock mailer receives nothing and the stored hash equals the expected test hash; authorized-address enforcement; and an HTTP flow: POST /otp for a new email sends nothing, a wrong code is rejected, the configured code returns a session, the confirmed user's next /otp goes through the magic link sender, and an expired TestOTPValidUntil falls back to a real OTP that is sent.

Additional context

Docs: example.env, hack/test.env, and a README entry under the mailer settings. Follow-ups outside this repo: a config.toml key in supabase/cli and a docs section in supabase/supabase next to the SMS test OTP text.

Add GOTRUE_MAILER_TEST_OTP and GOTRUE_MAILER_TEST_OTP_VALID_UNTIL, the
email counterpart of GOTRUE_SMS_TEST_OTP. When a magic link or signup
confirmation is requested for a listed email, the configured code is used
instead of a random one and no email is sent. The token hash is stored
exactly like a regular OTP, so verification is unchanged.

Closes supabase#901
@itsybitsci
itsybitsci requested a review from a team as a code owner September 7, 2026 03:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for test email OTPs

1 participant