Supabase Email Confirmation Not Sending: 5 Fixes (2026)
Email confirmations not sending from Supabase? Learn the exact causes and fixes for SMTP, template, and configuration issues in 10 minutes.
Photo by Sasun Bughdaryan on Unsplash
Supabase Email Confirmation Not Sending Troubleshooting Guide#
The fix, in order of likelihood: Supabase's built-in SMTP is rate-limited to a handful of emails per hour and only reaches accounts on your project's team — the moment you test with a real user address, it silently stops sending. Configure a custom SMTP provider (Resend, Postmark, SES) in Dashboard → Authentication → Email Templates → SMTP Settings first; that alone fixes it for most people. If the email still doesn't arrive, the 4 next-most-common causes — broken template links, missing SPF/DKIM records, an already-confirmed user, and dev-mode rate limits — are covered below in the order to check them.
This article is part of our comprehensive Supabase Authentication & Authorization Patterns guide.
Why Supabase Email Confirmations Fail#
There are 5 main reasons why confirmation emails don't send:
- Development Mode Limitations - Supabase free tier has email rate limits
- SMTP Not Configured - Default email service is unreliable
- Email Template Issues - Broken confirmation links or template errors
- Domain/DNS Problems - SPF/DKIM records not configured
- User Already Confirmed - Email was already verified
Quick Diagnostic Checklist#
Before diving into fixes, run through this checklist:
## Check Supabase logs
## Go to: Dashboard > Logs > Auth Logs
## Look for: "email_confirmation_sent" or error messages
## Check user status in database
SELECT email, email_confirmed_at, confirmation_token
FROM auth.users
WHERE email = 'user@example.com';
## Verify SMTP settings
## Go to: Dashboard > Authentication > Email Templates
## Check: SMTP settings are configuredSolution 1: Configure Custom SMTP (Recommended)#
The default Supabase email service is unreliable. Use a custom SMTP provider:
Step 1: Choose an SMTP Provider#
Best options for production:
- SendGrid - 100 emails/day free, reliable
- Resend - Modern API, great DX
- AWS SES - Cheapest for high volume
- Mailgun - Good deliverability
Step 2: Configure SMTP in Supabase#
// 1. Go to Supabase Dashboard > Project Settings > Auth
// 2. Scroll to "SMTP Settings"
// 3. Enable "Enable Custom SMTP"
// Example SendGrid Configuration:
Host: smtp.sendgrid.net
Port: 587
Username: apikey
Password: YOUR_SENDGRID_API_KEY
Sender email: noreply@iloveblogs.blog
Sender name: Your App NameStep 3: Verify SMTP Connection#
// Test email sending from Supabase Dashboard
// Go to: Authentication > Email Templates
// Click: "Send test email"
// Check: Email arrives in inbox (not spam)Step 4: Update Environment Variables#
## .env.local
NEXT_PUBLIC_SUPABASE_URL=your-project-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
## For custom email templates
SMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_USER=apikey
SMTP_PASS=your-api-keySolution 2: Fix Email Template Configuration#
Check Confirmation URL#
The confirmation link must point to your application:
// Supabase Dashboard > Authentication > URL Configuration
// Site URL: https://www.iloveblogs.blog
// Redirect URLs: https://www.iloveblogs.blog/auth/callback
// Email template should use: {{ .ConfirmationURL }}
// NOT: {{ .SiteURL }}/auth/confirm?token={{ .Token }}Update Email Template#
<!-- Go to: Dashboard > Authentication > Email Templates > Confirm signup -->
<h2>Confirm your signup</h2>
<p>Follow this link to confirm your account:</p>
<p><a href="{{ .ConfirmationURL }}">Confirm your email</a></p>
<p>Or copy and paste this URL into your browser:</p>
<p>{{ .ConfirmationURL }}</p>Handle Confirmation in Next.js#
@supabase/auth-helpers-nextjsis archived — do not start new code with it. Use@supabase/ssrinstead (createServerClientfor Route Handlers, Server Components, and Server Actions;createBrowserClientfor Client Components). In Next.js 15+cookies()is async, so youawaitit before building the client.
// app/auth/callback/route.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
const requestUrl = new URL(request.url)
const token_hash = requestUrl.searchParams.get('token_hash')
const type = requestUrl.searchParams.get('type')
const next = requestUrl.searchParams.get('next') ?? '/'
if (token_hash && type) {
const cookieStore = await cookies()
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll()
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
},
},
}
)
const { error } = await supabase.auth.verifyOtp({
type: type as any,
token_hash,
})
if (!error) {
return NextResponse.redirect(new URL(next, request.url))
}
}
// Return error page
return NextResponse.redirect(new URL('/auth/error', request.url))
}Solution 3: Configure DNS Records (Production)#
For production, configure SPF and DKIM records:
SPF Record#
## Add TXT record to your domain:
Type: TXT
Name: @
Value: v=spf1 include:sendgrid.net ~all
## For SendGrid, check their documentation for exact valueDKIM Record#
## SendGrid provides DKIM records in their dashboard
## Add 3 CNAME records they provide
## Example:
Type: CNAME
Name: s1._domainkey
Value: s1.domainkey.u12345.wl.sendgrid.netVerify DNS Configuration#
## Check SPF record
nslookup -type=TXT iloveblogs.blog
## Check DKIM record
nslookup -type=CNAME s1._domainkey.iloveblogs.blog
## Test email deliverability
## Use: mail-tester.comSolution 4: Handle Rate Limits#
Supabase has email rate limits on free tier:
// Implement retry logic for email sending (client component)
import { createBrowserClient } from '@supabase/ssr'
async function sendConfirmationEmail(email: string) {
const supabase = createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
try {
const { error } = await supabase.auth.signUp({
email,
password: 'user-password',
options: {
emailRedirectTo: `${window.location.origin}/auth/callback`,
},
})
if (error) {
// Check if rate limit error
if (error.message.includes('rate limit')) {
// Wait and retry
await new Promise(resolve => setTimeout(resolve, 5000))
return sendConfirmationEmail(email)
}
throw error
}
return { success: true }
} catch (error) {
console.error('Email send failed:', error)
return { success: false, error }
}
}Solution 5: Resend Confirmation Email#
Allow users to resend confirmation:
// app/api/resend-confirmation/route.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'
export async function POST(request: Request) {
const { email } = await request.json()
const cookieStore = await cookies()
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll()
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
},
},
}
)
const { error } = await supabase.auth.resend({
type: 'signup',
email,
options: {
emailRedirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`,
},
})
if (error) {
return NextResponse.json({ error: error.message }, { status: 400 })
}
return NextResponse.json({ message: 'Confirmation email sent' })
}Frontend Component#
'use client'
import { useState } from 'react'
export function ResendConfirmation({ email }: { email: string }) {
const [loading, setLoading] = useState(false)
const [message, setMessage] = useState('')
async function handleResend() {
setLoading(true)
const response = await fetch('/api/resend-confirmation', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
})
const data = await response.json()
setMessage(data.message || data.error)
setLoading(false)
}
return (
<div>
<p>Didn't receive the email?</p>
<button onClick={handleResend} disabled={loading}>
{loading ? 'Sending...' : 'Resend confirmation email'}
</button>
{message && <p>{message}</p>}
</div>
)
}Common Mistakes#
-
Mistake #1: Not checking spam folder - Always check spam/junk folders first
-
Mistake #2: Using default Supabase email - Configure custom SMTP for reliability
-
Mistake #3: Wrong redirect URL - Ensure Site URL matches your domain exactly
-
Mistake #4: Missing DNS records - SPF/DKIM required for production
-
Mistake #5: Not handling errors - Always show user-friendly error messages
Related Articles#
- Fix Supabase Auth Session Not Persisting After Refresh Next.js 14
- Supabase Auth Redirect Not Working Next.js App Router Solution
- Handle Supabase Auth Errors in Next.js Middleware
- Fix Foreign Key Constraint Violation in Supabase (23503)
- Supabase vs Firebase Authentication: Which is Better
- Supabase Auth + Middleware: The Complete Session Management Guide
- Supabase Hangs After onAuthStateChange: The Deadlock
Conclusion#
Email confirmation issues in Supabase are usually caused by SMTP configuration, template problems, or DNS settings. The most reliable solution is configuring custom SMTP with a provider like SendGrid or Resend, along with proper DNS records for production.
Follow the solutions above in order: configure SMTP first, then fix templates, add DNS records, and implement resend functionality. Test thoroughly in development before deploying to production.
Monitor your email deliverability using services like mail-tester.com and always provide users with a way to resend confirmation emails if needed.
Frequently Asked Questions
One email a month — no fluff
RLS gotchas, Next.js cache debugging, and the one Supabase setting that bit me last month.
Continue Reading
Supabase Auth Session Disappears on Refresh? Fix (2026)
Supabase auth sessions mysteriously disappearing after page refresh? Learn the exact cause and fix it in 5 minutes with this tested solution.
Supabase AuthSessionMissingError in Middleware: 5 Fixes
Auth errors crashing your Next.js middleware? Learn how to handle Supabase auth errors gracefully with proper error handling patterns.
Supabase Auth Callback Redirect Not Working? Next.js Fix
Auth redirect not working after Supabase sign-in? Here are the three root causes and the exact fixes — callback route, redirect URL allowlist, and router.refresh().
Browse by Topic
Find stories that matter to you.
