Supabase Email Confirmation Not Sending: 5 Fixes (2026)
Supabase

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.

Updated
10 min read
Supabase Email Confirmation Not Sending: 5 Fixes (2026)

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:

  1. Development Mode Limitations - Supabase free tier has email rate limits
  2. SMTP Not Configured - Default email service is unreliable
  3. Email Template Issues - Broken confirmation links or template errors
  4. Domain/DNS Problems - SPF/DKIM records not configured
  5. User Already Confirmed - Email was already verified

Quick Diagnostic Checklist#

Before diving into fixes, run through this checklist:

bash
## 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 configured

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#

typescript
// 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 Name

Step 3: Verify SMTP Connection#

typescript
// 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#

bash
## .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-key

Solution 2: Fix Email Template Configuration#

Check Confirmation URL#

The confirmation link must point to your application:

typescript
// 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#

html
<!-- 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-nextjs is archived — do not start new code with it. Use @supabase/ssr instead (createServerClient for Route Handlers, Server Components, and Server Actions; createBrowserClient for Client Components). In Next.js 15+ cookies() is async, so you await it before building the client.

typescript
// 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#

dns
## Add TXT record to your domain:
Type: TXT
Name: @
Value: v=spf1 include:sendgrid.net ~all
 
## For SendGrid, check their documentation for exact value

DKIM Record#

dns
## 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.net

Verify DNS Configuration#

bash
## Check SPF record
nslookup -type=TXT iloveblogs.blog
 
## Check DKIM record
nslookup -type=CNAME s1._domainkey.iloveblogs.blog
 
## Test email deliverability
## Use: mail-tester.com

Solution 4: Handle Rate Limits#

Supabase has email rate limits on free tier:

typescript
// 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:

typescript
// 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#

typescript
'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

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

|

Have more questions? Contact us

Written by

Mahdi Br
Mahdi Br

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote

One email a month — no fluff

RLS gotchas, Next.js cache debugging, and the one Supabase setting that bit me last month.