How to fix email_not_confirmed: Email has not been confirmed in Supabase
Supabase returns "email_not_confirmed: Email has not been confirmed" when you attempt to sign in with an account whose email address has not been verified. The fix is to either disable email confirmation in your auth settings for development, resend the confirmation email to the user, or ensure they have clicked the confirmation link in their inbox.
Quick fix
Fixes email_not_confirmed
const { data, error } = await supabase.auth.signUp({
email: 'test@example.com',
password: 'password'
})
// Session is now available immediately (no confirmation needed)
console.log('Session:', data.session)- Disable email confirmation for development (if testing)
- Verify the user's email confirmation status
- Resend the confirmation email to the user
- Configure a custom SMTP provider for production
const { data, error } = await supabase.auth.signUp({email: 'test@example.com',password: 'password'})// Session is now available immediately (no confirmation needed)console.log('Session:', data.session)email_not_confirmed: Email has not been confirmed
What this error means
By default, Supabase requires users to verify their email address before they can establish a session. When "Confirm email" is enabled in your Authentication settings, new users receive a confirmation email with a link or one-time password (OTP). Until they click that link or enter the OTP, the user's `email_confirmed_at` field remains null. When you attempt to sign in with an unconfirmed email, Supabase checks the `email_confirmed_at` timestamp and rejects the login with "email_not_confirmed: Email has not been confirmed". This is a security measure to ensure that the email address is valid and that the user has access to their inbox. The error can occur in these contexts: - Production environments where email verification is strictly enforced - Development or testing where confirmation emails are not being delivered - Users who have not yet clicked the confirmation link after signing up - Confirmation links that have expired (tokens are typically valid for 24 hours)
Typical symptoms
- After calling signUp() or signInWithPassword(), the returned session is null even though the credentials match a user in auth.users.
- The response error contains the exact message 'email_not_confirmed: Email has not been confirmed'.
- The browser console shows HTTP 403 Forbidden from the /token endpoint when the user attempts to sign in.
- Queries to auth.users show the user exists but the `email_confirmed_at` column is null for this user.
- Users report not receiving a confirmation email after signing up, or the link in the email does not work.
- Test accounts work when created with email_confirm: true via the Management API, but user self-signup accounts fail at login.
Common causes
- The 'Confirm email' setting is enabled in Authentication > Providers > Email in the Supabase dashboard, requiring users to verify before login.
- The user received a confirmation email but has not clicked the verification link yet.
- The confirmation link in the email has expired (tokens are typically valid for 24 hours).
- Email delivery is failing due to SMTP misconfiguration, rate limiting (default Supabase email provider only allows 2 emails/hour), or using the Supabase demo email provider in production.
- An email security feature (such as Microsoft Defender Safe Links) automatically fetched the confirmation URL, consuming the token and marking it as invalid.
- The user is being tested in a local Supabase instance where email delivery is not configured.
- A custom email template was modified in a way that breaks the confirmation link.
How to fix "email_not_confirmed: Email has not been confirmed"
If you are testing your application and do not need strict email verification, disable "Confirm email" in the Supabase dashboard:
1. Go to Authentication > Providers > Email
2. Toggle off the Confirm email switch
3. Click Save
With this setting disabled, new users are implicitly confirmed and can log in immediately. This is suitable for local development and testing environments:
const { data, error } = await supabase.auth.signUp({
email: 'test@example.com',
password: 'password'
})
// Session is now available immediately (no confirmation needed)
console.log('Session:', data.session)Note: Keep "Confirm email" enabled in production to protect against typos and unauthorized signups.
Check whether the user has actually confirmed their email by querying auth.users via the Management API on your server:
import { createClient } from '@supabase/supabase-js'
const supabaseAdmin = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
)
const { data: user, error } = await supabaseAdmin.auth.admin.getUserByEmail(
'user@example.com'
)
if (!user) {
console.log('User does not exist')
} else if (user.email_confirmed_at) {
console.log('Email is confirmed at:', user.email_confirmed_at)
} else {
console.log('Email has NOT been confirmed yet')
}If email_confirmed_at is null, the user has not completed the confirmation flow.
If the user's email is unconfirmed, implement a "Resend confirmation email" feature on your sign-up or login page:
const { error } = await supabase.auth.resendIdentityConfirmation({
email: 'user@example.com',
type: 'signup'
})
if (error) {
console.error('Failed to resend confirmation email:', error)
} else {
console.log('Confirmation email resent successfully')
}The user will receive a new confirmation email with a fresh link. Ensure the user checks their spam folder and allows time for email delivery (the default Supabase email provider can be slow).
The default Supabase email provider has a rate limit of only 2 emails per hour and is intended for testing only. For production, configure a custom SMTP server:
1. Go to Authentication > Email in the Supabase dashboard
2. Scroll to Custom SMTP settings
3. Enter your SMTP provider details (e.g., SendGrid, Mailgun, AWS SES)
4. Click Save
Example with SendGrid:
Host: smtp.sendgrid.net
Port: 587
Username: apikey
Password: [your SendGrid API key]With a proper SMTP provider, confirmation emails will be delivered reliably and immediately, allowing users to confirm their accounts without delays.
Some email providers (Microsoft Defender for Office 365, Proofpoint, Mimecast) automatically scan links in emails, which can consume the confirmation token before the user clicks it. If this is happening:
1. Ask the user to check if their email provider enables "Safe Links" or "URL rewriting"
2. If they do, they should disable or whitelist Supabase emails
3. Alternatively, provide a resend option so they can get a fresh confirmation link
You can also check the Supabase Auth logs to see if the confirmation token has already been used:
- Go to Authentication > Logs in the Supabase dashboard
- Look for multiple uses of the same token, which indicates it was prefetched
If prefetching is confirmed, ask the user to request a new confirmation email.
On the server side using the Management API, you can manually mark a user as confirmed:
const { data: updatedUser, error } = await supabaseAdmin.auth.admin.updateUserById(
userId,
{ email_confirm: true }
)
if (error) {
console.error('Failed to confirm user:', error)
} else {
console.log('User email confirmed:', updatedUser.email_confirmed_at)
}This is useful for seeding test accounts or fixing issues in development. Do not use this in production unless you have a specific reason to bypass email confirmation for a user.
Advanced notes
### Email confirmation in local development
When running Supabase locally with supabase start, email confirmation is not set up by default. Emails are not sent, and the confirmation flow will not work. To test email confirmation locally, either:
- Disable "Confirm email" in your local auth settings (simpler)
- Configure the local Supabase to use an email testing service like Mailtrap or Mailhog
### Email templates and custom links
The confirmation link in the email is generated from the email template. If you customize the Email Templates in Authentication, ensure the {{ .ConfirmationURL }} or custom confirmation link variable is correctly included. A broken template will result in users receiving emails with non-functional links.
### Token expiration
Confirmation tokens are valid for 24 hours by default. If a user waits longer than 24 hours before clicking the link, they will receive a "Token has expired or is invalid" error. Provide a "Resend confirmation email" option so users can request a fresh token.
### Email tracking interference
If you use an external email service that enables email tracking, the service may rewrite Supabase confirmation links, breaking the verification flow. Disable email tracking or whitelist Supabase domains in your email provider to prevent this.
### Checking email_confirmed_at programmatically
Always check the email_confirmed_at timestamp on the server side to determine confirmation status, rather than relying on client-side state. The actual source of truth is the auth.users table in PostgreSQL.
Frequently asked questions
- What does "email_not_confirmed" mean?
- By default, Supabase requires users to verify their email address before they can establish a session. When "Confirm email" is enabled in your Authentication settings, new users receive a confirmation email with a link or one-time password (OTP). Until they click that link or enter the OTP, the user's email_confirmed_at field remains null.
- How do I fix "email_not_confirmed"?
- Supabase returns "email_not_confirmed: Email has not been confirmed" when you attempt to sign in with an account whose email address has not been verified. The fix is to either disable email confirmation in your auth settings for development, resend the confirmation email to the user, or ensure they have clicked the confirmation link in their inbox.
- What causes "email_not_confirmed"?
- The 'Confirm email' setting is enabled in Authentication > Providers > Email in the Supabase dashboard, requiring users to verify before login. The user received a confirmation email but has not clicked the verification link yet. The confirmation link in the email has expired (tokens are typically valid for 24 hours).
- Is the "email_not_confirmed" error serious?
- It is a medium-severity error โ more of a blocker than a risk. The steps above resolve it.
Official resources & further reading
- ๐Supabase Official Documentation
- ๐Supabase Password-based Auth documentation(Official Docs)
- ๐Supabase Email Templates guide(Official Docs)
- ๐Supabase General Auth Configuration(Official Docs)
- ๐Supabase Auth Error Codes(Official Docs)
- ๐Supabase Troubleshooting - Not Receiving Auth Emails(Official Docs)
- ๐
- ๐