Supabase email rate limit exceeded is the 429 error Supabase Auth returns when a project on the built-in sender passes 2 emails an hour. Its code, over_email_send_rate_limit, also marks a second limit, a 60-second wait on one user’s repeat request, under its own message. Custom SMTP lifts only the hourly cap, and starts it at 30 an hour.

Supabase email rate limit exceeded: two limits behind one error code

The email rate limit exceeded error in Supabase is one of two documented limits that share the 429 status and the over_email_send_rate_limit code: a project-wide cap of 2 emails an hour from the built-in sender, and a 60-second window per user on signup, reset and magic-link requests. The message text tells you which one fired.

The email rate limit exceeded message Supabase Auth returns belongs to the project cap. The user window answers with the same status and code but other words, For security purposes, you can only request this after N seconds, where N is the number of seconds left. So the status code alone settles nothing, and the message settles most of it. The words a user sees depend on the app: a signup form that prints the raw error shows a line such as Sign up failed: email rate limit exceeded, where the first half is the app’s own text and the second half is Supabase’s message. When the refusal comes from a provider other than Supabase, such as Resend, SendGrid or Gmail, the cause and the fix are different, and email rate limit exceeded on other platforms is the place to start.

SymptomLikely causeFirst check
Signup or a magic link fails for an address outside the project team, on a project that has sent almost nothingThe built-in sender’s team-only rule: a 400 with the code email_address_not_authorized, not a 429The Authentication settings page: is custom SMTP on?
The third email in an hour fails, across different usersThe project-wide cap: 2 emails an hour, built-in sender onlyThe same settings page; this cap counts the whole project, not one user
The error carries over_email_send_rate_limitOne of the two email limits; they share the codeThe message: email rate limit exceeded is the project cap, a wait of some seconds is the user window
One user taps resend twice and the second request failsThe 60-second window per user; the message asks for a wait of some secondsAuthentication > Rate Limits shows the window
The account is created and the first database write is refusedA different error: a row level security policy, not emailThe error text; a database refusal needs a database fix

That last row looks like an email problem because it lands at the same moment, right after signup, but the RLS error after signup comes from a table policy and no email setting changes it. Three dashboard pages settle the other four rows before anyone edits a line of code:

  1. 01 Authentication settings: is custom SMTP on, or is the project still on Supabase's built-in sender?
  2. 02 The Team tab of the organization's settings: the only addresses the built-in sender will deliver to.
  3. 03 The Rate Limits page under Authentication: the windows this project has configured.

Which Supabase emails stop, and why that matters at launch

The email row in Supabase’s table covers every email-sending Auth endpoint, not just signup. That means the confirmation email after signup, the magic link and email OTP, invitations sent from the Users page or the admin API, the password reset email, and the messages sent when a user changes their address all draw on one Supabase rate limit for sending emails. The Supabase magic link rate limit is therefore two limits at once: the shared hourly allowance, and the 60-second window each user has on /auth/v1/otp. The same holds for the confirmation message, so the Supabase email verification limit is that shared cap plus the 60-second user window.

Apps built with Lovable, Bolt, v0, Cursor or Claude Code that use Supabase Auth call these same endpoints, so the limits apply to them unchanged. A signup screen tested during the build, with every email going to the builder’s own address, passes because that address is on the project team. The first stranger is refused by the team-only rule, and once the project has sent two emails in an hour, the next request to anyone fails because the Supabase email limit is exceeded for that hour. Supabase is plain about the built-in sender: it is “provided as best-effort only”, with no SLA on delivery or uptime, and is meant for exploring Auth, testing templates with the project’s team, and “toy projects, demos or any non-mission-critical application”. An app that sends the mail from its own code instead takes another route, explained in sending verification email yourself.

We audited 21 third-party apps in June and July 2026, and 17 of them had no error tracking or alerting: when a user hit an error, nothing recorded it. Those 21 were a selected set picked for review, not a random sample, so the count describes that group and is not a rate for AI-built apps in general. A signup email that never arrives is that kind of silent error: the visitor waits, gives up, and the app keeps no trace of why.

If signups are the front door of a launch or of a first paying customer, and nobody can say which limit the app keeps hitting, start with a free audit: a video call where we go over the signup and reset flow with you and agree what to fix first. In the Production Hardening Sprint, our engineers configure a dedicated transactional provider with delivery tracking for account and transaction messages, verify and configure SPF, DKIM and DMARC for the transactional sending domain, and implement rate limits and brute-force protections on authentication and recovery endpoints. Hosting, paid tools and API usage are paid through your accounts, so the email provider’s plan stays on your bill.

How do rate limits work in Supabase?

Supabase rate limits count in three ways: per project for emails and SMS, per user for repeat signup, reset and magic-link requests, and per IP address for sign-ups, sign-ins, verification and token calls. Every one returns 429 when exceeded, and most can be changed at Authentication, Rate Limits.

The full list sits on Supabase’s rate-limits page. The table below keeps its rows and adds one column of ours, “sends email”, which says whether the operation can spend the project’s email allowance.

OperationPathLimited byDefault limitCustomizableSends email
Emails sent by Supabase AuthAll email-sending Auth endpointsProject2 emails per hour on the built-in providerOnly with custom SMTP or the Send Email hookYes: this is the email counter
SMS messages sent by Supabase AuthAll SMS-sending Auth endpointsProject30 SMS messages per hourYesNo
Sign-ups and sign-ins/auth/v1/signup, /auth/v1/recover, /auth/v1/resend, /auth/v1/magiclink, /auth/v1/otp, /auth/v1/userIP address30 requests per 5 minutes, bursts up to 30; excludes anonymous sign-insYesSome paths do (signup, recover, resend, magiclink, otp, and user on an address change, or on a password change when security notices are on)
Send OTPs or magic links/auth/v1/otpUser60-second window before a new request for the same userYesYes
Signup confirmation request/auth/v1/signupUser60 seconds before the same user may ask againYesYes
Password reset request/auth/v1/recoverUser60-second wait per user between requestsYesYes
Verification requests/auth/v1/verifyIP address30 per 5 minutes, with bursts up to 30YesOnly security notices, if turned on (such as email address changed)
Token endpoint requests/auth/v1/tokenIP address150 requests per 5 minutes, bursts up to 30YesNo
Create or verify an MFA challenge/auth/v1/factors/:id/challenge, /auth/v1/factors/:id/verifyIP address15 requests per minute, bursts up to 30NoOnly a security notice when a new factor is first verified, if turned on
Anonymous sign-ins/auth/v1/signupIP address30 requests per hour, burst equal to the configured limitYesNo
Web3 sign-ups and sign-ins/auth/v1/tokenIP address30 requests in 5 minutes, burst of up to 30YesNo

Limits checked October 20, 2026 on Supabase’s rate-limits page. Supabase’s guide says the built-in sender’s number “can change without notice”, so treat the first row as dated.

Read the “limited by” column before the numbers. A project limit cannot be dodged by trying another address, and a user limit never blocks a second user. Supabase rate limiting on the IP rows works as a token bucket: an idle client can send a short burst, and once the bucket is empty, requests are refused until it refills at the listed rate. Every Supabase Auth rate limit in the table answers with the same 429 Too Many Requests, which is why the symptom matters more than the status. To rate limit Supabase Auth harder or softer than the defaults, change the window at Authentication > Rate Limits, or send new values such as rate_limit_email_sent to the Management API’s auth config endpoint.

Supabase sets the IP limits on sign-in to prevent abuse, and they are a floor rather than a full defense: lockouts, credential stuffing and abused reset endpoints belong to brute force login protection. A limiter on your own API routes is a separate job, set out in rate limiting in API and web app endpoints. Two other ceilings can stop a working project without any mail involved: too many database connections at once and what is eating your Supabase egress.

The project limit: 2 emails an hour on the built-in sender

The email row is the one most apps hit first. It reads “2 emails per hour with the built-in email provider”, limited by Project, and customizable only through “Custom SMTP or Send Email hook”. The custom SMTP guide states the same number from the other side: “Currently this value is set to 2 messages per hour.” So the Supabase email rate limit is a project allowance, not a per-user one. One developer testing signup with three addresses has spent the hour for every visitor too. A September 2024 dev.to walkthrough still gives the Supabase email limit as 3 emails an hour; Supabase’s own table says 2, and the table is the one to trust.

The user limit: 60 seconds between repeat requests

Signup confirmation, password reset and OTP or magic-link requests each default to “a 60 seconds window before a new request is allowed for the same user”. A double tap on a resend button is the usual way to hit it. The refusal carries the same 429 and the same over_email_send_rate_limit code as the project cap, but its message asks the user to wait a number of seconds instead of saying the email rate limit was exceeded. Supabase’s error reference, in its entry for the per-IP request limit, names “a badly written useEffect React hook” as a bug that can send more requests than the app means to, so check the code that calls resend if the refusals come without anyone tapping. Custom SMTP does not touch this window; it is a separate row, set at Authentication > Rate Limits.

The team-only rule behind Email address not authorized

Without custom SMTP, the built-in sender delivers only to people on the project’s team. The guide says Supabase Auth “will refuse to deliver messages to addresses that are not part of the project’s team”, managed in the organization’s Team tab, and that “All other addresses will fail with the error message Email address not authorized.” The error reference lists the matching code, email_address_not_authorized. Whether a refused request also counts against the hourly cap is not stated in Supabase’s docs. In the open-source code of Supabase’s Auth server, as of October 2026, the address check runs first and returns a 400 before the email counter is reached, with the words Email address "<address>" cannot be used as it is not authorized. One record of the confusion is issue 34209 in Supabase’s repository: a Free-plan project returned 429 while its owner read the Auth logs as showing no recent sends. The thread was closed after the cases seen with the local CLI were traced to a CLI bug that was fixed; the hosted report was never explained in it.

What over_email_send_rate_limit tells you, and what it does not

Supabase’s Auth error codes reference describes over_email_send_rate_limit as “Too many emails have been sent to this email address. Ask the user to wait a while before trying again.” That wording fits the user window, which counts per user, but the same code is also returned when the project cap fires, and the project cap counts every address. So the code alone does not prove that another address would go through; the message beside it does the sorting. In supabase-js the failure arrives as an AuthApiError; the reference says errors of that class “always have a code property” for identifying them, and for the project cap the AuthApiError message reads email rate limit exceeded. The response body in one GitHub report on Supabase’s repository has this shape: {"code":"over_email_send_rate_limit","message":"email rate limit exceeded"}. A Flutter app in issue 34209 printed it as AuthApiException: email rate limit exceeded (429, over_email_send_rate_limit).

The reference advises reading error.code rather than matching on the message text. The snippet below follows that advice as the reference and the signUp docs write it; it was not run for this page.

const { data, error } = await supabase.auth.signUp({ email, password });
if (error?.code === 'over_email_send_rate_limit') {
  // check the project and user limits before retrying
}

Free tier or Pro: the same 2 an hour

The email row ties the cap to the built-in provider and names no plan. Upgrading to Pro therefore changes nothing: a Supabase email rate limit exceeded error on the free tier comes from the same row as on Pro, and the cap goes only when the mail stops going through the built-in sender. The marked answer in GitHub discussion 35286, opened by someone still blocked after the upgrade, says the same: “There is no difference in Pro plan and Free on email rate limit for the built in email provider.” The plan prices themselves are in what the Supabase plans cost.

How to increase the Supabase email rate limit: custom SMTP or a Send Email hook

The Supabase email rate limit is raised by moving Auth mail off the built-in sender: custom SMTP or a Send Email hook. With custom SMTP set, Supabase starts the project at 30 messages an hour and lets you raise it on the Rate Limits page. The 60-second per-user windows stay.

Both routes need a transactional email provider: a service built to send one-to-one account mail, such as confirmations, resets and receipts, and to keep a log of each message it accepted or refused. Supabase Auth works with any service that speaks SMTP, and Supabase’s custom SMTP guide walks through the settings. The method below follows that guide.

  1. 01 Pick a provider. Supabase's guide names Resend, AWS SES, Postmark, Twilio SendGrid, ZeptoMail and Brevo in a list it calls non-exhaustive, and recommends none of them over the others.
  2. 02 Verify your sending domain with that provider and publish the DNS records it gives you.
  3. 03 Open the Authentication settings page, turn on custom SMTP, and enter the host, port, user and password from the provider, plus a From address such as no-reply@ on your domain.
  4. 04 Save. The project now sends to every address, and Supabase imposes a low limit of 30 messages an hour to protect the new sender's reputation.
  5. 05 Raise the email limit on the Rate Limits page under Authentication, or set rate_limit_email_sent through the Management API.

The DNS records in step two are the SPF, DKIM and DMARC entries, and SPF, DKIM and DMARC setup covers them record by record. Step four is where the next surprise sits: the guide says “a low rate-limit of 30 messages per hour is imposed”, so the Supabase SMTP rate limit starts at 30, not unlimited, and stays there until someone changes it.

The other route is a Send Email hook. The hook “replaces Supabase’s built-in email sending”: Auth hands each message to your code, which sends it through any provider’s API and can fall back to a second provider if the first one fails. The email limit becomes configurable with the hook as well.

Two things do not change. The 60-second user windows keep running, whichever route you pick. And opening the project to every address opens it to bots: Supabase’s guide warns that bots use lists of known addresses to sign up users, names hurting your email sending reputation as one usual goal, and calls CAPTCHA “the most effective way to control bots in this scenario”. Supabase supports hCaptcha and Cloudflare Turnstile, switched on under Settings > Authentication > Bot and Abuse Protection. Choosing the provider, deciding which messages must arrive and proving each one did are set out in transactional email best practices.

What does not fix the error: waiting, a new address, backoff, or no confirmation

Waiting clears one refusal and nothing else. The project cap refills, the next two emails go out, and the third in that hour fails again. Supabase’s rate-limits page describes no button or call that resets a counter early; any reset control is not stated in Supabase’s docs.

Another email address does not help either. The email row is limited by Project, so a fresh address spends the same allowance. The developer who added the May 2026 comment on issue 34209 reported exactly that: different addresses still triggered the error.

A fresh Supabase project does make the first two emails pass, because it has its own hourly counter, and the original reporter in issue 34209 saw a new project work where the old one did not. The cost is two projects with the users and data split between them, and the new one hits the same cap the moment it sees real traffic.

Exponential backoff is the right tool for a limit that refills in seconds. Against an allowance of 2 an hour, a retry loop only waits out the hour while the user stares at a spinner. Where retries do belong is explained in what is exponential backoff.

Turning off email confirmation is a trade, not a repair: the error stops because accounts are created without anyone proving they own the address. Supabase’s guide names forcing a project into “disabling email confirmations” as one of the goals of signup abuse, and says plainly: “Do not disable email confirmations under pressure.” The security side is in what turning email confirmation off leaves open.

The same gap can appear without anyone choosing it. In one of the five apps we run ourselves, which went through the same June and July 2026 audits as the third-party apps, email verification and the stricter login rate limit were two of five controls that switched on only when NODE_ENV equals production, and the deploy never set it. That app is the full case in authentication best practices for login flows. A confirmation step that is off in production protects nobody, whether someone turned it off or a missing setting did.

How to check your own app: trigger each auth email and prove it arrived

A Supabase Auth email setup is proven by sending every message type to an address outside the project team and finding each one in two places: the provider’s log and the inbox. Five checks, each with the evidence to keep, cover signup, reset, magic link, invite and email change.

Run them after custom SMTP or the hook is live, with an address you control that is not on the organization’s team.

  1. 01 Sign up with an address outside the project team. Keep the provider's log line and the received confirmation.
  2. 02 Request a password reset for that account. Keep the same two pieces of evidence.
  3. 03 Request a magic link, then request another within 60 seconds. Keep the first message and the refusal of the second, which asks for a wait of some seconds: it proves the user window is still on.
  4. 04 Invite a user from the Users page. Keep the log line and the invitation in the inbox.
  5. 05 Change the account's email address. With Secure Email Change on, two messages go out, one to the old address and one to the new: keep both.

Then open Logs in the dashboard, choose the Auth log type, set the time range to the hour of the test, and filter Status for 429. The only 429 should be the deliberate refusal from the third check; any other means a limit is still lower than the app needs. Note the date, the provider and the hourly email limit you set, so the next person knows what was proven and when. Log retention depends on the Supabase plan, so save the evidence outside the dashboard.

Deliverable 11.2 of the Production Hardening Sprint is verified the same way: trigger each critical message type and confirm provider records and expected recipient delivery. This proof is one part of how to improve email deliverability, alongside the domain records and what happens to mail that bounces.

Common questions about Supabase Auth email limits

How do I turn off the rate limit?

You cannot turn off the built-in sender’s cap of 2 emails an hour; Supabase lists it as configurable only once Auth mail goes through custom SMTP or a Send Email hook. With either in place, the email limit becomes a number you set at Authentication > Rate Limits, and most of the other Auth windows there can be raised too; the MFA challenge limit is the one Supabase marks as not customizable.

Why would my IP address be rate limited?

On Supabase Auth, sign-ups and sign-ins count per IP address, by default 30 requests per 5 minutes with bursts up to 30, so a test script or many people behind one office address can reach the limit together. A server-side framework or proxy that calls Auth from its own server can hit it too, because Supabase sees the server’s address; Supabase’s docs describe forwarding the user’s address in the Sb-Forwarded-For header with a secret API key, a setting new projects must switch on.

Is being rate limited bad?

No: a rate limit exists to protect the sender’s reputation and the service, and Supabase says its built-in sender is limited “to maintain the health and reputation of the default SMTP sending service”. It becomes a problem when real users hit it, which a cap of 2 emails an hour for the whole project makes likely as soon as a few people sign up in the same hour.

When should a founder get help with Supabase signup emails?

Get help when real users are signing up and nobody on the team can say which service sends the auth mail, or whether each message arrived. At AxonBuild we weigh up with you in a free audit which provider should send your auth mail and what to change first.