Skip to main content
RapidDev - Software Development Agency
supabase-tutorial

How to Resend Email Confirmation in Supabase

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.

What you'll learn

  • How to use supabase.auth.resend() to re-trigger confirmation emails
  • How to handle rate limits and cooldown periods for resend requests
  • How to verify SMTP delivery when emails are not arriving
  • How to build a user-friendly resend confirmation UI
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner8 min read10-15 minSupabase (all plans), @supabase/supabase-js v2+March 2026RapidDev Engineering Team
TL;DR

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

1

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.

typescript
1import { createClient } from '@supabase/supabase-js'
2
3const supabase = createClient(
4 process.env.NEXT_PUBLIC_SUPABASE_URL!,
5 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
6)
7
8async 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 })
16
17 if (error) {
18 console.error('Resend error:', error.message)
19 return false
20 }
21
22 console.log('Confirmation email resent successfully')
23 return true
24}

Expected result: A new confirmation email is sent to the user with a fresh confirmation link.

2

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.

typescript
1import { useState, useEffect } from 'react'
2
3function ResendButton({ email }: { email: string }) {
4 const [cooldown, setCooldown] = useState(0)
5 const [status, setStatus] = useState<'idle' | 'sending' | 'sent' | 'error'>('idle')
6
7 useEffect(() => {
8 if (cooldown <= 0) return
9 const timer = setTimeout(() => setCooldown(cooldown - 1), 1000)
10 return () => clearTimeout(timer)
11 }, [cooldown])
12
13 async function handleResend() {
14 setStatus('sending')
15 const { error } = await supabase.auth.resend({
16 type: 'signup',
17 email,
18 })
19
20 if (error) {
21 setStatus('error')
22 } else {
23 setStatus('sent')
24 setCooldown(60)
25 }
26 }
27
28 return (
29 <button
30 onClick={handleResend}
31 disabled={cooldown > 0 || status === 'sending'}
32 >
33 {cooldown > 0
34 ? `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.

3

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.

typescript
1-- Check if user exists and is unconfirmed
2select id, email, email_confirmed_at, created_at
3from auth.users
4where email = 'user@example.com';

Expected result: You can identify whether the issue is with the user record, SMTP configuration, or email delivery.

4

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.

5

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.

typescript
1function ConfirmationPending({ email }: { email: string }) {
2 const masked = email.replace(
3 /(.{2})(.*)(@.*)/,
4 (_, a, b, c) => a + '*'.repeat(b.length) + c
5 )
6
7 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

resend-confirmation.tsx
1import { useState, useEffect } from 'react'
2import { createClient } from '@supabase/supabase-js'
3
4const supabase = createClient(
5 process.env.NEXT_PUBLIC_SUPABASE_URL!,
6 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
7)
8
9// Resend confirmation email with error handling
10async 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 })
18
19 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 }
25
26 return { success: true }
27}
28
29// React component with cooldown timer
30function ResendConfirmationPage({ email }: { email: string }) {
31 const [cooldown, setCooldown] = useState(0)
32 const [message, setMessage] = useState('')
33 const [isError, setIsError] = useState(false)
34
35 useEffect(() => {
36 if (cooldown <= 0) return
37 const timer = setTimeout(() => setCooldown((c) => c - 1), 1000)
38 return () => clearTimeout(timer)
39 }, [cooldown])
40
41 const maskedEmail = email.replace(
42 /(.{2})(.*)(@.*)/,
43 (_, a, b, c) => a + '*'.repeat(b.length) + c
44 )
45
46 async function handleResend() {
47 setMessage('')
48 const result = await resendConfirmationEmail(email)
49
50 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 }
59
60 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 <button
66 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}
81
82export default ResendConfirmationPage

Common 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.

ChatGPT Prompt

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.

Supabase Prompt

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.