To resend a confirmation email in Supabase, use supabase.auth.resend() with the type set to 'signup' and the user's email address. This re-triggers the confirmation email for users who did not receive the initial one. Handle rate limits by showing a cooldown timer, and always configure a custom SMTP provider to avoid the default 2 emails per hour limit that blocks re-sends.
Resending Email Confirmation in Supabase Auth
Users sometimes do not receive the initial signup confirmation email due to spam filters, incorrect email addresses, or SMTP delivery issues. Supabase provides the resend() method to re-trigger confirmation emails. This tutorial covers how to implement a resend flow, handle rate limits gracefully, and troubleshoot delivery issues.
Prerequisites
- A Supabase project with email confirmation enabled (Authentication > Providers > Email)
- The Supabase JS client installed (@supabase/supabase-js v2+)
- A custom SMTP provider configured for reliable email delivery
- Understanding of the email verification flow
Step-by-step guide
Call the resend method to re-trigger the confirmation email
Call the resend method to re-trigger the confirmation email
Use supabase.auth.resend() with the type parameter set to 'signup' and the email address of the user who needs a new confirmation email. You can also pass an emailRedirectTo option to specify where the user should be redirected after clicking the confirmation link. The resend method only works for users who have signed up but not yet confirmed their email — calling it for a confirmed user returns an error.
1import { createClient } from '@supabase/supabase-js'23const supabase = createClient(4 process.env.NEXT_PUBLIC_SUPABASE_URL!,5 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!6)78async function resendConfirmation(email: string) {9 const { error } = await supabase.auth.resend({10 type: 'signup',11 email,12 options: {13 emailRedirectTo: `${window.location.origin}/auth/callback`,14 },15 })1617 if (error) {18 console.error('Resend error:', error.message)19 return false20 }2122 console.log('Confirmation email resent successfully')23 return true24}Expected result: A new confirmation email is sent to the user with a fresh confirmation link.
Handle rate limits with a cooldown timer
Handle rate limits with a cooldown timer
Supabase enforces rate limits on auth emails to prevent abuse. If the user clicks the resend button too quickly, the API returns a rate limit error. Implement a client-side cooldown timer (typically 60 seconds) that disables the resend button after each attempt. This prevents unnecessary API calls and provides a better user experience than showing error messages.
1import { useState, useEffect } from 'react'23function ResendButton({ email }: { email: string }) {4 const [cooldown, setCooldown] = useState(0)5 const [status, setStatus] = useState<'idle' | 'sending' | 'sent' | 'error'>('idle')67 useEffect(() => {8 if (cooldown <= 0) return9 const timer = setTimeout(() => setCooldown(cooldown - 1), 1000)10 return () => clearTimeout(timer)11 }, [cooldown])1213 async function handleResend() {14 setStatus('sending')15 const { error } = await supabase.auth.resend({16 type: 'signup',17 email,18 })1920 if (error) {21 setStatus('error')22 } else {23 setStatus('sent')24 setCooldown(60)25 }26 }2728 return (29 <button30 onClick={handleResend}31 disabled={cooldown > 0 || status === 'sending'}32 >33 {cooldown > 034 ? `Resend in ${cooldown}s`35 : status === 'sending'36 ? 'Sending...'37 : 'Resend confirmation email'}38 </button>39 )40}Expected result: The resend button shows a 60-second cooldown timer after each attempt, preventing rapid re-sends.
Troubleshoot emails not arriving
Troubleshoot emails not arriving
If confirmation emails are not arriving after a resend, check these items in order: (1) Verify the user exists in auth.users in the Dashboard and email_confirmed_at is null. (2) Check if you are using the default SMTP — it has a strict 2 emails per hour limit per recipient. Configure a custom SMTP provider if you have not already. (3) Ask the user to check their spam and junk folders. (4) Verify your SMTP provider's sending logs to confirm the email was dispatched. (5) Check the Supabase project logs under Logs > Auth for delivery errors.
1-- Check if user exists and is unconfirmed2select id, email, email_confirmed_at, created_at3from auth.users4where email = 'user@example.com';Expected result: You can identify whether the issue is with the user record, SMTP configuration, or email delivery.
Configure custom SMTP to ensure delivery
Configure custom SMTP to ensure delivery
The built-in Supabase email service is limited to 2 emails per hour per recipient. For production, configure a custom SMTP provider. Go to the Supabase Dashboard > Project Settings > Authentication > SMTP Settings. Toggle on Enable Custom SMTP and enter your provider's credentials. After saving, all auth emails including resends will be sent through your SMTP provider with no rate limit. Verify the configuration by resending a confirmation email and checking your SMTP provider's delivery logs.
Expected result: Custom SMTP is configured and confirmation email resends are delivered reliably without rate limits.
Build a complete resend confirmation page
Build a complete resend confirmation page
Create a dedicated page or modal that users see after signing up, with instructions to check their email and a resend button. Include the user's email address (partially masked for privacy), a clear message explaining what to do, the resend button with cooldown, and a link to go back to the login page. This page should be accessible even without a session since the user has not yet confirmed their email.
1function ConfirmationPending({ email }: { email: string }) {2 const masked = email.replace(3 /(.{2})(.*)(@.*)/,4 (_, a, b, c) => a + '*'.repeat(b.length) + c5 )67 return (8 <div style={{ maxWidth: 400, margin: '0 auto', textAlign: 'center' }}>9 <h2>Check your email</h2>10 <p>We sent a confirmation link to <strong>{masked}</strong></p>11 <p>Click the link in the email to verify your account.</p>12 <ResendButton email={email} />13 <p style={{ marginTop: 16, fontSize: 14, color: '#666' }}>14 Didn't receive it? Check your spam folder or try resending.15 </p>16 </div>17 )18}Expected result: A user-friendly confirmation pending page with resend functionality is displayed after signup.
Complete working example
1import { useState, useEffect } from 'react'2import { createClient } from '@supabase/supabase-js'34const supabase = createClient(5 process.env.NEXT_PUBLIC_SUPABASE_URL!,6 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!7)89// Resend confirmation email with error handling10async function resendConfirmationEmail(email: string): Promise<{ success: boolean; error?: string }> {11 const { error } = await supabase.auth.resend({12 type: 'signup',13 email,14 options: {15 emailRedirectTo: `${window.location.origin}/auth/callback`,16 },17 })1819 if (error) {20 if (error.message.includes('rate')) {21 return { success: false, error: 'Too many requests. Please wait before trying again.' }22 }23 return { success: false, error: error.message }24 }2526 return { success: true }27}2829// React component with cooldown timer30function ResendConfirmationPage({ email }: { email: string }) {31 const [cooldown, setCooldown] = useState(0)32 const [message, setMessage] = useState('')33 const [isError, setIsError] = useState(false)3435 useEffect(() => {36 if (cooldown <= 0) return37 const timer = setTimeout(() => setCooldown((c) => c - 1), 1000)38 return () => clearTimeout(timer)39 }, [cooldown])4041 const maskedEmail = email.replace(42 /(.{2})(.*)(@.*)/,43 (_, a, b, c) => a + '*'.repeat(b.length) + c44 )4546 async function handleResend() {47 setMessage('')48 const result = await resendConfirmationEmail(email)4950 if (result.success) {51 setMessage('Confirmation email sent! Check your inbox.')52 setIsError(false)53 setCooldown(60)54 } else {55 setMessage(result.error || 'Failed to resend email.')56 setIsError(true)57 }58 }5960 return (61 <div style={{ maxWidth: 420, margin: '80px auto', textAlign: 'center', padding: 24 }}>62 <h2>Verify your email</h2>63 <p>We sent a confirmation link to <strong>{maskedEmail}</strong></p>64 <p>Click the link in the email to activate your account.</p>65 <button66 onClick={handleResend}67 disabled={cooldown > 0}68 style={{ padding: '10px 20px', marginTop: 16, cursor: cooldown > 0 ? 'not-allowed' : 'pointer' }}69 >70 {cooldown > 0 ? `Resend in ${cooldown}s` : 'Resend confirmation email'}71 </button>72 {message && (73 <p style={{ color: isError ? '#ef4444' : '#22c55e', marginTop: 12 }}>{message}</p>74 )}75 <p style={{ marginTop: 24, fontSize: 14, color: '#888' }}>76 Check your spam folder if you don't see the email.77 </p>78 </div>79 )80}8182export default ResendConfirmationPageCommon mistakes when resending Email Confirmation in Supabase
Why it's a problem: Calling resend() for a user who has already confirmed their email
How to avoid: The resend method only works for unconfirmed users. Check email_confirmed_at before showing the resend option. If the user is already confirmed, redirect them to sign in instead.
Why it's a problem: Not handling the rate limit error from the resend API
How to avoid: Implement a client-side cooldown timer (60 seconds) that disables the resend button after each attempt. Also catch rate limit errors from the API and display a user-friendly message.
Why it's a problem: Using the default SMTP and wondering why resends are not delivered
How to avoid: The default Supabase SMTP has a 2 emails per hour per recipient limit. If the initial email plus a resend exceed this, the resend is silently dropped. Configure a custom SMTP provider.
Best practices
- Always configure a custom SMTP provider before relying on email resend functionality
- Implement a 60-second cooldown timer between resend attempts to prevent abuse
- Partially mask the email address on the confirmation page for privacy
- Store cooldown state in localStorage to persist across page refreshes
- Show clear instructions to check the spam folder alongside the resend button
- Log resend attempts in your analytics to identify delivery issues early
- Provide a way for users to update their email address if they entered it incorrectly
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I need to implement a resend confirmation email feature in my Supabase app. Show me how to use supabase.auth.resend(), handle rate limits with a cooldown timer, and build a React component for the confirmation pending page with a resend button.
Create a React component for a Supabase email confirmation pending page. Include a resend button that calls supabase.auth.resend() with type 'signup', a 60-second cooldown timer, error handling for rate limits, and a masked email display. Also show how to troubleshoot when emails are not arriving.
Frequently asked questions
How many times can I resend a confirmation email?
There is no hard limit on the number of resends, but Supabase enforces rate limits to prevent abuse. With the default SMTP, the limit is 2 emails per hour per recipient. With a custom SMTP provider, the rate limits depend on your provider's policies.
What types does the resend method support?
The resend method supports three types: 'signup' for email confirmation, 'email_change' for email address change confirmation, and 'sms' for phone number OTP codes.
Can I resend a confirmation email from the Dashboard?
The Supabase Dashboard does not have a built-in resend button. You can manually delete the user from auth.users and ask them to sign up again, or use the resend() method in your application code.
What happens if the user's email address is incorrect?
The resend goes to whatever email address is stored in auth.users. If the address is wrong, the email will not be delivered. Provide a way for users to update their email address before they confirm, or allow them to sign up again with the correct address.
Does resending generate a new confirmation link?
Yes, each resend generates a new confirmation token and link. The previous link becomes invalid, so the user must use the latest email they received.
Why does my resend return a success but no email arrives?
This usually means the default SMTP rate limit was reached (2 per hour per recipient). The API returns success because the request was accepted, but the email is not sent. Configure a custom SMTP provider to fix this.
Can RapidDev help set up reliable email delivery for my Supabase project?
Yes, RapidDev can configure your SMTP provider, customize email templates, implement resend functionality with rate limiting, and ensure reliable email delivery for all auth flows.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation