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

How to Fix Rate Limit Issues in Supabase

Supabase rate limits fall into two categories: auth email limits (2 emails per hour on default SMTP) and API request limits (varies by plan). Fix email rate limits by configuring a custom SMTP provider like Resend or SendGrid in Dashboard > Authentication > Settings. Fix API rate limits by implementing client-side throttling, caching responses, and using connection pooling. Monitor your usage in Dashboard > Settings > Usage to stay within your plan's limits.

What you'll learn

  • How to identify which Supabase rate limit you are hitting
  • How to fix the default 2 emails/hour SMTP limit with custom SMTP
  • How to implement client-side throttling to stay within API limits
  • How to monitor usage and plan for scaling
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner9 min read10-15 minSupabase (all plans), @supabase/supabase-js v2+March 2026RapidDev Engineering Team
TL;DR

Supabase rate limits fall into two categories: auth email limits (2 emails per hour on default SMTP) and API request limits (varies by plan). Fix email rate limits by configuring a custom SMTP provider like Resend or SendGrid in Dashboard > Authentication > Settings. Fix API rate limits by implementing client-side throttling, caching responses, and using connection pooling. Monitor your usage in Dashboard > Settings > Usage to stay within your plan's limits.

Understanding and Fixing Rate Limit Issues in Supabase

Supabase applies rate limits to protect infrastructure and prevent abuse. The most commonly encountered limit is the default SMTP rate limit of 2 auth emails per hour, which affects signups, password resets, and magic links. API request limits vary by plan and can be hit by high-traffic applications or inefficient client-side code. This tutorial explains each type of rate limit, how to identify which one you are hitting, and how to fix or work around each.

Prerequisites

  • A Supabase project experiencing rate limit errors
  • Access to the Supabase Dashboard
  • Basic understanding of HTTP status codes (429 = rate limited)

Step-by-step guide

1

Identify which rate limit you are hitting

Supabase has several rate limits that produce different error messages. Check your error message or HTTP status code to determine which limit is triggered. The most common is the auth email rate limit, followed by API request limits. Check Dashboard > Logs > Auth or API Gateway to see the exact error messages from the server side.

typescript
1// Common rate limit error messages:
2
3// 1. Auth email rate limit (default SMTP)
4// Error: 'Email rate limit exceeded'
5// HTTP 429 on /auth/v1/signup or /auth/v1/recover
6// Cause: Default SMTP allows only 2 emails/hour
7
8// 2. Auth signup rate limit
9// Error: 'For security purposes, you can only request this once every 60 seconds'
10// Cause: Same email requested too quickly
11
12// 3. API request rate limit
13// Error: 'Too many requests'
14// HTTP 429 on /rest/v1/* endpoints
15// Cause: Exceeded plan's API request quota
16
17// 4. Realtime connection limit
18// Error: 'Too many connections'
19// Cause: Exceeded max concurrent WebSocket connections
20
21// Check which limit:
22console.log('Status:', error?.status) // 429 = rate limited
23console.log('Message:', error?.message)

Expected result: You identify the specific rate limit that is causing errors in your application.

2

Fix email rate limits with custom SMTP

The default Supabase SMTP allows only 2 auth emails per hour. This is the most common rate limit issue. Fix it by configuring a custom SMTP provider in the Dashboard. Resend, SendGrid, and Mailgun all offer free tiers with generous limits. After configuring custom SMTP, email rate limits are governed by your SMTP provider, not Supabase.

typescript
1# Dashboard > Authentication > Settings > SMTP Settings
2# Enable Custom SMTP: ON
3
4# For Resend (recommended, free tier: 100 emails/day):
5# Host: smtp.resend.com
6# Port: 465
7# Username: resend
8# Password: re_your_api_key
9# Sender email: noreply@yourdomain.com
10
11# For SendGrid (free tier: 100 emails/day):
12# Host: smtp.sendgrid.net
13# Port: 465
14# Username: apikey
15# Password: SG.your_api_key
16# Sender email: noreply@yourdomain.com
17
18# For Mailgun:
19# Host: smtp.mailgun.org
20# Port: 465
21# Username: your_mailgun_username
22# Password: your_mailgun_password
23# Sender email: noreply@yourdomain.com
24
25# After saving, test by triggering a signup or password reset

Expected result: Auth emails are sent through your custom SMTP provider with no more 2/hour rate limit.

3

Implement client-side throttling for API requests

If you are hitting API request rate limits, the cause is usually inefficient client-side code making too many requests. Common patterns include: fetching data on every keystroke, re-fetching on every render, or polling without intervals. Implement debouncing for search inputs, caching for repeated queries, and proper useEffect dependencies to prevent unnecessary requests.

typescript
1import { createClient } from '@supabase/supabase-js'
2
3const supabase = createClient(
4 'https://your-project.supabase.co',
5 'your-anon-key'
6)
7
8// BAD: Fetches on every keystroke
9function handleSearch(query: string) {
10 supabase.from('products').select('*').ilike('name', `%${query}%`)
11}
12
13// GOOD: Debounced search (only fires after 300ms pause)
14function debounce<T extends (...args: any[]) => any>(fn: T, delay: number) {
15 let timer: ReturnType<typeof setTimeout>
16 return (...args: Parameters<T>) => {
17 clearTimeout(timer)
18 timer = setTimeout(() => fn(...args), delay)
19 }
20}
21
22const debouncedSearch = debounce(async (query: string) => {
23 const { data } = await supabase
24 .from('products')
25 .select('*')
26 .ilike('name', `%${query}%`)
27 .limit(20)
28 // Update state with results
29}, 300)
30
31// Use in input handler:
32// <input onChange={(e) => debouncedSearch(e.target.value)} />

Expected result: API requests are reduced by debouncing, caching, and limiting query results.

4

Add retry logic with exponential backoff

When you receive a 429 rate limit response, do not retry immediately. Implement exponential backoff that waits longer between each retry attempt. This prevents overwhelming the server with retry storms and gives the rate limit window time to reset. Most rate limits reset within 60 seconds.

typescript
1async function fetchWithRetry<T>(
2 fn: () => Promise<{ data: T | null; error: any }>,
3 maxRetries = 3
4): Promise<{ data: T | null; error: any }> {
5 let lastError: any = null
6
7 for (let attempt = 0; attempt <= maxRetries; attempt++) {
8 const { data, error } = await fn()
9
10 if (!error) {
11 return { data, error: null }
12 }
13
14 // Only retry on rate limit errors (429)
15 if (error.status !== 429 || attempt === maxRetries) {
16 return { data: null, error }
17 }
18
19 lastError = error
20
21 // Exponential backoff: 1s, 2s, 4s
22 const delay = Math.pow(2, attempt) * 1000
23 console.log(`Rate limited, retrying in ${delay}ms (attempt ${attempt + 1})`)
24 await new Promise((resolve) => setTimeout(resolve, delay))
25 }
26
27 return { data: null, error: lastError }
28}
29
30// Usage:
31const { data, error } = await fetchWithRetry(() =>
32 supabase.from('todos').select('*')
33)

Expected result: Rate-limited requests are retried with increasing delays, giving the rate limit time to reset.

5

Monitor usage and plan for scaling

Check Dashboard > Settings > Usage to see your current API request counts, database size, storage usage, and bandwidth consumption relative to your plan limits. If you are consistently hitting limits, consider upgrading your plan or optimizing your queries. Set up monitoring to get alerted before you hit limits in production.

typescript
1# Dashboard > Settings > Usage
2# Monitor these metrics:
3# - API requests: total requests per month
4# - Database size: storage used
5# - Bandwidth: data transferred
6# - Edge Function invocations: function calls
7# - Realtime connections: concurrent WebSocket connections
8
9# Plan limits (approximate):
10# Free: 500K API requests/month, 500MB database, 1GB bandwidth
11# Pro: Unlimited API requests, 8GB database, 50GB bandwidth
12# Team: Unlimited API requests, 8GB database, 50GB bandwidth
13
14# If hitting limits on Free plan:
15# 1. Optimize queries (add .limit(), use indexes)
16# 2. Cache responses client-side
17# 3. Upgrade to Pro ($25/month) for unlimited API requests
18
19# If hitting limits on Pro plan:
20# Contact Supabase support for custom limits
21# Or implement your own rate limiting with Edge Functions

Expected result: You understand your current usage and can plan for scaling or optimization.

Complete working example

supabase-with-retry.ts
1// Supabase client wrapper with rate limit handling
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// Retry helper with exponential backoff and jitter
10async function withRetry<T>(
11 operation: () => Promise<{ data: T | null; error: any }>,
12 options: { maxRetries?: number; baseDelay?: number } = {}
13): Promise<{ data: T | null; error: any }> {
14 const { maxRetries = 3, baseDelay = 1000 } = options
15
16 for (let attempt = 0; attempt <= maxRetries; attempt++) {
17 const result = await operation()
18
19 if (!result.error || result.error.status !== 429) {
20 return result
21 }
22
23 if (attempt < maxRetries) {
24 const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 1000
25 await new Promise((r) => setTimeout(r, delay))
26 }
27 }
28
29 return { data: null, error: { message: 'Rate limit exceeded after retries', status: 429 } }
30}
31
32// Debounce helper for search inputs
33function debounce<T extends (...args: any[]) => any>(fn: T, ms: number) {
34 let timer: ReturnType<typeof setTimeout>
35 return (...args: Parameters<T>) => {
36 clearTimeout(timer)
37 timer = setTimeout(() => fn(...args), ms)
38 }
39}
40
41// Simple in-memory cache
42const cache = new Map<string, { data: any; timestamp: number }>()
43const CACHE_TTL = 60_000 // 1 minute
44
45async function cachedQuery<T>(key: string, query: () => Promise<{ data: T | null; error: any }>) {
46 const cached = cache.get(key)
47 if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
48 return { data: cached.data as T, error: null }
49 }
50
51 const result = await withRetry(query)
52 if (result.data) {
53 cache.set(key, { data: result.data, timestamp: Date.now() })
54 }
55 return result
56}
57
58// Usage examples:
59
60// Cached + retry-safe query
61const todos = await cachedQuery('todos-list', () =>
62 supabase.from('todos').select('*').order('created_at', { ascending: false }).limit(50)
63)
64
65// Debounced search
66const searchProducts = debounce(async (query: string) => {
67 return withRetry(() =>
68 supabase.from('products').select('*').ilike('name', `%${query}%`).limit(20)
69 )
70}, 300)
71
72export { supabase, withRetry, debounce, cachedQuery, searchProducts }

Common mistakes when fixing Rate Limit Issues in Supabase

Why it's a problem: Not configuring custom SMTP and hitting the 2 emails/hour limit repeatedly during development

How to avoid: Configure a custom SMTP provider (Resend, SendGrid, Mailgun) in Dashboard > Authentication > Settings > SMTP before you start development. This is the number one rate limit issue.

Why it's a problem: Retrying rate-limited requests immediately without backoff, making the problem worse

How to avoid: Implement exponential backoff with jitter. Wait at least 1 second before the first retry, doubling the wait time for each subsequent attempt.

Why it's a problem: Fetching data on every render or keystroke without debouncing or caching

How to avoid: Debounce search inputs (300ms delay), cache frequently accessed data, and use proper React dependency arrays to prevent unnecessary re-fetches.

Best practices

  • Configure custom SMTP immediately when starting a new Supabase project to avoid the 2 emails/hour default limit
  • Implement exponential backoff with jitter for all API calls to handle transient rate limits gracefully
  • Debounce search inputs and other high-frequency API calls with a 300ms delay
  • Cache frequently accessed data client-side with a short TTL to reduce redundant requests
  • Add .limit() to all queries to fetch only the data you need
  • Monitor usage in Dashboard > Settings > Usage to proactively identify when you are approaching limits
  • Use connection pooling (Supavisor) for server-side applications making many concurrent database queries

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

My Supabase app is getting 429 rate limit errors on auth email endpoints. I am hitting the 2 emails per hour SMTP limit. Walk me through configuring Resend as a custom SMTP provider step by step, including creating a Resend account, getting the API key, and configuring it in the Supabase Dashboard.

Supabase Prompt

Create a Supabase client wrapper that handles rate limits automatically with exponential backoff, includes a debounce utility for search queries, and has a simple in-memory cache with TTL. Show me how to use it in a React component.

Frequently asked questions

What is the default email rate limit in Supabase?

The default SMTP allows 2 auth emails per hour. This includes signup confirmations, password resets, magic links, and email change confirmations. Configure a custom SMTP provider to remove this limit.

What are the API request limits per plan?

Free plan has approximately 500K API requests per month. Pro plan ($25/month) has unlimited API requests. These limits apply to REST API calls through PostgREST, not direct database connections.

How quickly do rate limits reset?

Email rate limits reset on a rolling 1-hour window. API rate limits vary — most reset within 60 seconds. Check the Retry-After header in the 429 response for the exact wait time.

Can I increase rate limits without upgrading my plan?

For email limits, yes — configure custom SMTP. For API limits on the free plan, you need to either optimize your code to make fewer requests or upgrade to Pro. On Pro plans, contact Supabase support for custom rate limit adjustments.

Does Supabase rate limit database connections?

Yes. The free plan allows up to 50 direct connections and 200 pooled connections via Supavisor. Pro plan allows more. Use connection pooling (port 6543) for server-side applications to stay within limits.

How do I know if my app is close to hitting rate limits?

Check Dashboard > Settings > Usage for current API request counts and database metrics. Set up monitoring with log drains (Pro plan) to get alerted before hitting limits.

Can RapidDev help optimize my app to avoid rate limits?

Yes. RapidDev can audit your Supabase usage, implement caching strategies, configure SMTP, optimize queries, and set up monitoring to keep your application within rate limits.

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.