@Mail Domain Check

Supabase Auth | duplicate signup | truthful UI

Supabase Signup Says Email Sent for an Existing Verified Address

If an existing confirmed address gets HTTP 200 but no new confirmation message, do not add an "account exists" banner. Supabase can deliberately return sanitized user data to hide account existence. Fix the unconditional delivery claim, keep sign-in and password reset available, and prove a controlled new signup's delivery separately.

Target incident

The hook discards signup data, mutation success becomes "We sent a confirmation link," and the protected duplicate path sends no message.

Treat four outcomes as different evidence

Signup accepted
An HTTP 200 or missing SDK error is an Auth response, not proof that a confirmation message was generated or sent.
Duplicate protected
With email confirmation required, current Supabase Auth can return a sanitized user with a random ID, current timestamps, and no identities for an existing confirmed address.
Message requested
A new unconfirmed signup reaches a separate confirmation-request and mailer path. That event still does not prove provider or receiver acceptance.
Message completed
Provider acceptance, receiver visibility, and successful use of the newest link are separate downstream states.

Browser-local signup planner

Choose the strongest truthful result

No email address, password, project reference, API key, response body, user ID, confirmation link, token, cookie, provider payload, or full log is requested or transmitted.

Why the target interface overclaims

At reviewed commit c4c9eb7, the application's useRegister hook calls supabase.auth.signUp() but retains only error. Its registration form maps every mutation success to "We sent a confirmation link to your email." The UI therefore converts a protected no-error response into a delivery claim.

The open personal-analytics issue 65 proposes detecting that the account already exists. That would explain the missing message, but exposing the distinction publicly would remove the account-enumeration protection supplied by Auth.

What current Supabase Auth proves

In reviewed Auth commit 67c55c0, a confirmed duplicate reaches UserExistsError. If mail or SMS autoconfirm is enabled, Auth returns user_already_exists. Otherwise it calls sanitizeUser and returns HTTP 200.

The sanitizer's own comment says it prevents leaking whether a user is registered. It replaces the ID and timestamps, clears confirmation fields, and returns an empty identities list. The official signUp() reference likewise warns that existing accounts can receive a response designed to hide that information.

An empty identities list can identify the sanitized branch to trusted application code, but it is not an email delivery receipt and must not become public "account exists" copy.

Use one truthful public outcome

For a confirmation-required signup form, use copy that remains true for both a new address and an existing confirmed address:

<h2>Check your next step</h2>
<p>
  If this address can be registered, check for a confirmation message.
  Otherwise, sign in or reset your password.
</p>
<Link href="/login">Sign in</Link>
<Link href="/forgot-password">Reset password</Link>

Do not condition the public sentence on data.user.identities.length. The application can retain a private aggregate category for debugging and abuse controls, while rendering the same text, actions, status code, and broadly comparable timing.

Keep delivery evidence separate

  1. Verify the deployed Confirm email setting. Autoconfirm requires a different signed-in flow and no confirmation-message promise.
  2. For one controlled fresh address, retain only the categorical signup shape and a redacted timestamp.
  3. Confirm that Auth recorded a confirmation request, then classify the provider outcome without copying its payload.
  4. Check receiver visibility and use only the newest link once. A preview scanner may consume a one-time link before the intended browser.
  5. Run the existing confirmed address as a paired privacy test. It should show the same neutral result and may correctly produce no message.

Regression contract

  1. A fresh unconfirmed address and an existing confirmed address render identical public copy and actions.
  2. No HTTP 200, missing SDK error, empty identities list, or confirmation_sent_at value alone produces "email sent" copy.
  3. The DOM, client analytics, response timing, and error styling do not label an address as registered.
  4. Sign in and password reset remain visible from the neutral outcome.
  5. One controlled fresh signup separately proves confirmation request, provider outcome, receiver visibility, and newest-link completion.
  6. Repeated signup and recovery attempts are rate-limited, while detailed audit evidence remains private and access controlled.
Does a controlled new signup also fail to arrive?

Move from the account-privacy boundary to Auth generation, provider processing, receiver visibility, and link completion.

Open Supabase delivery path

Primary references