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.
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)
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: '[email protected]',
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(
'[email protected]'
)
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: '[email protected]',
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.
### 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.
email_conflict_identity_not_deletable: Cannot delete identity because of email conflict
How to fix "Cannot delete identity because of email conflict" in Supabase
mfa_challenge_expired: MFA challenge has expired
How to fix "mfa_challenge_expired: MFA challenge has expired" in Supabase
conflict: Database conflict, usually related to concurrent requests
How to fix "database conflict usually related to concurrent requests" in Supabase
phone_exists: Phone number already exists
How to fix "phone_exists" in Supabase
StorageApiError: resource_already_exists
StorageApiError: Resource already exists