Skip to content
couryo

Blog

Supabase email rate limit exceeded: the fix

Getting email rate limit exceeded in Supabase Auth? The real limits, where to change them, how to set up custom SMTP and a production checklist.

How-to9 min read

View as Markdown

Your app is ready, a friend signs up to try it, and the confirmation email never shows up. You try again and Supabase answers email rate limit exceeded. By the third attempt, not even your own inbox gets anything.

It is not a bug in your code. Supabase Auth ships with a built-in email sender meant for testing, with a very low limit, plus a few more limits that still apply after you switch to your own SMTP. This post walks through each one, where to change it, how to set up SMTP properly and what to check before you open the app to the public.

Why do you get "email rate limit exceeded"?#

The error comes with the code over_email_send_rate_limit and HTTP status 429. According to Supabase Auth's error code list, it means too many emails have been sent and the user should wait a while before trying again.

In practice, it shows up in three situations:

  1. You are still on the built-in sender. It has a project-wide hourly limit that runs out after a handful of tests.
  2. You turned on custom SMTP but left the limit alone. When you save SMTP settings, Supabase applies a low starting limit that you need to raise.
  3. The same person asked for the email twice within a minute. There is a per-user window between requests.

The limits of the built-in sender#

Supabase's custom SMTP page is blunt: the default service is not for production. Checked on October 20, 2026:

What How the built-in sender works
Who it sends to only pre-authorized addresses, the members of your Supabase organization. Everyone else gets email_address_not_authorized
How many per hour 2 messages per hour, and Supabase notes the number can change without notice
Guarantee no SLA on delivery or uptime

At 2 per hour, one sign-up plus one password reset uses up the quota. That is why the error shows up so early: your app is not sending too much, the limit was built for testing.

The limits that stay with custom SMTP#

Custom SMTP removes the recipient restriction and lets you raise the email limit. The other Auth limits still apply. These are the defaults from Supabase Auth's rate limits page, checked on October 20, 2026:

Limit Default Counted per
Emails sent by Auth 2 per hour on the built-in sender; once you save custom SMTP it starts at 30 per hour (per the SMTP page) whole project
New magic link or OTP request (/otp) 60 seconds between requests user
New sign-up confirmation (/signup) 60 seconds user
New password reset (/recover) 60 seconds user
Sign-ups and sign-ins 30 requests per 5 minutes IP address
Verifications (/verify) 30 requests per 5 minutes IP address

Two practical notes:

  • The email limit belongs to the project, not the person. If 40 people sign up in the same hour and the limit is 30, the last 10 get a 429.
  • The per-IP limit catches offices and classrooms. Many people behind one IP (a company network, a room full of people at a demo) hit the sign-up limit faster than you would expect.

Where to change the limits in the dashboard#

In the Supabase dashboard, open the project and go to Authentication > Rate Limits. The adjustable limits live there, including emails per hour. The email field can only be changed when custom SMTP or the Send Email Hook is on; on the built-in sender it stays locked.

You can also change it through the Supabase Management API with the rate_limit_email_sent key:

Terminal
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
  -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "rate_limit_email_sent": 120 }'

The 120 is only an example. To find your own number, plug in your data:

hourly limit = sign-ups in your peak hour × emails per sign-up (confirmation, welcome, password reset) × headroom (for example, 2)

And remember that the Supabase limit cannot go above what your SMTP provider accepts. If Supabase lets 500 per hour through and your provider takes fewer, the rest come back as errors in the log.

A real example: the limit that shows up even with a hook#

Not every 429 goes away with the first tweak. Issue 45743 in the Supabase repository, opened on May 9, 2026, reports over_email_send_rate_limit on sign-up even with the Auth Email Hook configured (the hook that sends the email through your own function instead of SMTP). The reporter expected the limit not to apply in that case. Other people confirmed the same behavior in the comments, and one of them said they only got past it by turning on custom SMTP.

As of October 20, 2026, the issue was still open with no reply from the Supabase team. The practical takeaway: if you use the hook and still get 429, check the email field under Authentication > Rate Limits, and test with the same address more than once a minute to tell the project limit apart from the 60-second per-user window.

How to set up custom SMTP in Supabase#

Any authenticated SMTP service works. The steps below use Couryo's SMTP; with another provider, only the host, username and password change.

Before you start#

  1. Verify the domain your emails will come from with your sending provider, with SPF, DKIM and DMARC. Without that, the email goes out but lands in spam. In Couryo, this lives under Domains, with live status for each record and one-click setup if your DNS is on Cloudflare; see Domains and DNS and the SPF, DKIM and DMARC guide.
  2. Create an API key with the send scope. In Couryo, it is the SMTP password.

In the Supabase dashboard#

In Authentication > Emails > SMTP Settings, turn on Enable Custom SMTP and fill in:

Field Value
Sender email an address on the verified domain, such as no-reply@auth.example.com
Sender name your app's name
Host smtp.couryo.com
Port number 587 (STARTTLS) or 465 (implicit TLS)
Username couryo
Password your API key (ck_live_...)

Save, then go straight to Authentication > Rate Limits and set the emails per hour. This is the step most people skip: without it, you trade the 2 per hour limit for 30 per hour and conclude that SMTP "did not fix it".

With the CLI, for versioned projects#

supabase/config.toml
[auth.email.smtp]
enabled = true
host = "smtp.couryo.com"
port = 587
user = "couryo"
pass = "env(COURYO_API_KEY)"
admin_email = "no-reply@auth.example.com"
sender_name = "Example"

[auth.rate_limit]
email_sent = 120

In the CLI configuration reference, email_sent defaults to 2 and the minimum gap between requests (max_frequency, under [auth.email]) defaults to 1m. Keep the key in an environment variable, never in the file.

More details, including the Management API, on the Supabase Auth with custom SMTP page.

How to test without burning the limit#

  1. Test with a test key. In Couryo, a ck_test_ key works in the same password field but never delivers: the confirmation email is captured and shows up in the dashboard with a clickable link. You can repeat the flow as often as you like without bothering anyone or using up sends. See Test mode.
  2. Sign up a test user in your app, or use Authentication > Users > Add user > Send invitation.
  3. Check both sides. In Supabase, Logs > Auth shows whether the send went out and any SMTP error. In Couryo, Emails shows the message timeline: accepted, sent and delivered, with the receiving server's response and a plain-language explanation.
  4. Switch to the ck_live_ key once the flow works.

If a code shows up in the Supabase log:

Code What it means
535 5.7.8 username other than couryo, or a key that is invalid, revoked or missing the send scope
550 5.7.1 the Sender email is not on a verified domain, or the account is still on level 0
452 4.7.0 Couryo's level or plan limit; the message includes the reason and the numbers
over_email_send_rate_limit Supabase's limit, under Authentication > Rate Limits

An honest note about Couryo: new accounts also start at level 0 (sandbox), with 25 emails a day to the account's own addresses only. Once your domain is verified and there is a sign that a real person is behind it (a card on file, a checked Brazilian company ID or a domain older than 30 days), the account moves up on its own to level 1, at 100 a day. The Free plan includes 3,000 emails a month. The numbers for each level are in Limits and levels.

Show the error to the user, calmly#

Even with everything set up, the 60-second per-user window stays. Handle the 429 in the UI instead of leaving people clicking "resend":

signup.ts
import { createClient } from "@supabase/supabase-js";

const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!);

export async function signUp(email: string, password: string) {
  const { error } = await supabase.auth.signUp({ email, password });
  if (!error) return { ok: true, message: "We sent a link to your email." };

  if (error.code === "over_email_send_rate_limit" || error.status === 429) {
    return {
      ok: false,
      message: "We just sent you an email. Check your inbox and spam folder; you can ask again in a minute.",
    };
  }
  return { ok: false, message: error.message };
}

A 60-second countdown on the "resend" button prevents the second request and the frustration that comes with it.

Production checklist#

  1. Custom SMTP on, under Authentication > Emails > SMTP Settings.
  2. Emails per hour set under Authentication > Rate Limits, sized from your peak hour.
  3. Sender domain verified, with SPF, DKIM and DMARC aligned.
  4. A subdomain just for Auth (for example, auth.example.com), verified on its own and kept apart from marketing. Supabase itself recommends not mixing Auth emails with marketing.
  5. Site URL and Redirect URLs correct under Authentication > URL Configuration (no localhost in production).
  6. Templates under Authentication > Emails > Templates short and free of promotion, with the app name in the subject and no link shorteners.
  7. The 6-digit code ({{ .Token }}) offered on mobile: some corporate filters open links before the person does and use up single-use links.
  8. CAPTCHA on sign-up, as Supabase recommends, so bots do not eat your limit.
  9. A way to see every send: in Couryo, the timeline under Emails and webhooks for bounces and complaints.

If the email goes out and the person still does not see it, the limit is no longer the problem. Follow Confirmation email not arriving and Why emails go to spam.

FAQ#

What is the Supabase Auth email rate limit?#

On the built-in sender, 2 emails per hour for the whole project, and only to members of your organization. With custom SMTP, it starts at 30 per hour and you change it under Authentication > Rate Limits. There is also a 60-second per-user window between requests (figures checked in the Supabase docs on October 20, 2026).

I turned on custom SMTP and still get the error. Why?#

Almost always because the emails-per-hour limit stayed at its starting value. Open Authentication > Rate Limits and raise it. If the error only shows up when the same person asks twice in a row, that is the 60-second per-user window, and it is intentional.

Does the Supabase limit replace my SMTP provider's limit?#

No, both apply. Supabase decides how many emails it asks for per hour, and your SMTP provider decides how many it accepts. In Couryo, the level and plan limits show up in the dashboard and in the 452 4.7.0 response, with the reason and the numbers.

Can I test the sign-up flow without sending real email?#

Yes. In Couryo, use a ck_test_ key as the SMTP password: the email is captured and shows up in the dashboard with a clickable link, and nothing is delivered. Locally, the Supabase CLI already captures emails in a test inbox.

Does the Auth Email Hook get around the limit?#

According to the docs, the email limit becomes adjustable when you use custom SMTP or the hook. But issue 45743 reports 429 errors even with the hook, and as of October 20, 2026 it was still open. If that is your case, check the field under Authentication > Rate Limits or use custom SMTP.