DEV Community

Cover image for AuthApiError: Email not confirmed in Supabase: why new users can't sign in
VibeFix
VibeFix

Posted on Originally published at vibe-fixer.com

AuthApiError: Email not confirmed in Supabase: why new users can't sign in

A new person signs up, tries to sign in, and your app says "Email not confirmed". In code it comes back as an AuthApiError with the code email_not_confirmed; public reports show the raw response as {"code":"email_not_confirmed","message":"Email not confirmed"} (Supabase issue #29632). The account exists, but the person is locked out of it.

Supabase's error reference describes it as: "Signing in is not allowed for this user as the email address is not confirmed." The project is doing what it was told. With Confirm email on, users "will need to confirm their email address before signing in for the first time", and on hosted Supabase projects it is on by default. On self-hosted projects and in local development it is off by default, which is why an app can behave differently on a laptop than live.

So the real question is why this person is still unconfirmed. Supabase also advises identifying errors by error.code, not by matching the message text. Here are six reasons, in the order to check them.

1. The confirmation email was never sent or never arrived

Supabase's default email service only delivers to addresses on your project's team, at 2 messages per hour, and "is not meant for production use".

Fix:

  • Open your project's Auth logs and look for an error at the time of the sign-up. Supabase names this as the first step for undelivered auth emails.
  • If you are on the default email service, set up your own SMTP provider and enable custom SMTP in the Authentication settings. Supabase lists Resend, AWS SES, Postmark, Twilio SendGrid, ZeptoMail and Brevo as examples.
  • Set up SPF, DKIM and DMARC for the sending domain. Supabase recommends this for deliverability.

Sources: Supabase: Send emails with custom SMTP · Supabase troubleshooting: Not receiving Auth emails

2. The email arrived, but the link was already used up

Supabase documents that some email providers' security features prefetch links in incoming emails. The one-time link is consumed before the person clicks it; they see "Token has expired or is invalid" and stay unconfirmed.

Fix:

  • Open the Email Templates page in the dashboard and edit the Confirm signup template.
  • Choose one of Supabase's two documented options: send a one-time code by putting {{ .Token }} in the template and verify it with supabase.auth.verifyOtp, or link to a page in your app with a button that opens the real confirmation link.
  • Send the affected person a new confirmation email (see cause 4).

Source: Supabase: Email templates

3. The link sent them to localhost or a page that does not finish the job

Supabase's Site URL defaults to http://localhost:3000, and it is used whenever the app does not name an approved redirect. With the PKCE flow, Supabase's template sends the person to {{ .SiteURL }}/auth/confirm?token_hash=..., so confirmation only completes if that page exists on your live app.

Fix:

  • Open URL Configuration in the dashboard and change the Site URL from localhost to your live address.
  • Add your live address to the Redirect URLs list, for example https://yourapp.com/**.
  • In the sign-up call, pass options: { emailRedirectTo: 'https://yourapp.com/welcome' }. The address must be on the Redirect URLs list.
  • If your template uses a token-hash link, make sure your live app has the page it points to.

Sources: Supabase: Redirect URLs · Supabase: Password-based auth

4. They tried to sign in again, expecting a new email

Signing in does not send a new confirmation email. Someone who keeps trying only gets the same refusal. Lovable's documentation says it plainly: "signing in again does not resend it".

Fix:

  • Catch email_not_confirmed in your sign-in screen and show a "Resend confirmation email" button instead of a generic error.
  • Have the button call supabase.auth.resend({ type: 'signup', email, options: { emailRedirectTo } }).
  • Disable the button for at least 60 seconds after each send. Supabase's default allows one signup confirmation request per user per 60 seconds.
  • On an app on Lovable's built-in backend without such a button, Lovable's documented route is for the person to sign up again with the same address.

Sources: Supabase JavaScript reference: resend() · Supabase: Rate limits · Lovable: Email authentication

5. The account was created outside the normal sign-up

Users created with the admin API (auth.admin.createUser) without email_confirm: true can be left unconfirmed, and then hit the same error. Public reports describe this even after Confirm email was switched off (#29632).

Fix:

  • Confirm the account on the server with supabase.auth.admin.updateUserById(id, { email_confirm: true }). Supabase says this function "should only be called on a server", and never with the service_role key in the browser.
  • On Lovable's built-in backend, accounts you create under More, Users, Add user, Create new user are confirmed automatically.

Sources: Supabase: auth.admin.updateUserById() · Lovable: Email authentication

6. The app assumes people are signed in right after sign-up

Confirmation is one switch for the whole project. If your screens send a new person straight into the app after sign-up, the next sign-in attempt meets this error.

Fix:

  • Decide whether you want confirmation. Supabase keeps it on by default for hosted projects. In Lovable the matching setting is Auto-confirm email, and Lovable recommends keeping confirmation on for live apps.
  • If confirmation stays on, show a "check your email" screen after sign-up instead of opening the app.
  • Test the full path with a brand-new address, not your own account.

Sources: Supabase: General configuration · Supabase: Password-based auth

Why you can't see it yourself

You're signed in, your customers aren't. You confirmed your own account long ago, from an address that is on the team. You never sit where a new person sits: signed up, unconfirmed, waiting for an email that may never come.

FAQ

I switched off "Confirm email", but users still get "Email not confirmed". Why? People report this on Supabase's GitHub, for example #29632 and #5113. For an account that is already stuck, confirm that account: send a new confirmation email with resend(), or confirm it on the server with updateUserById.

Can people use the app before confirming their email? Only by switching confirmation off for the whole project. A request for a middle ground (supabase/auth #1494) was closed in 2024, with anonymous sign-in suggested as the alternative.

Fixed it? Prove a real stranger gets in

The test that matters is a brand-new address signing up, getting the email, clicking the link and signing in on your live app. Your own account cannot show that, because it is not new. Your AI reads your code. VibeFix signs up like a stranger.

Free check. Paste your live app's link at vibe-fixer.com: an AI agent signs up to your live app once as a brand-new person, with a real inbox of its own, on a phone and on a computer. You get the problem count, what each problem is, and one screenshot. It never changes your app and never emails your users.

$9 for one app. Shows which cause is breaking your app and where, a fix prompt written for your app for every problem, plus 3 re-checks so you can see whether the fix holds for a stranger. It does not promise a fix; it shows what the agent saw.

Related: Supabase "Error sending confirmation email" (500) · Supabase confirmation email not sending · Supabase redirect URL localhost

VibeFix is independent. Supabase and Lovable are trademarks of their owners.

Does your sign-in screen tell an unconfirmed user what to do next, or does it only show the error?

Originally published at vibe-fixer.com. I'm building VibeFix, an AI agent that signs up to your live app like a brand-new person and shows what breaks.

Top comments (0)