Stop retrying the send and copy the exact error text first, because retries can count against the same window. Email rate limit exceeded is one message used by 4 layers: your auth service’s built-in mailer, your email provider’s plan quota, a mailbox host such as Gmail or Microsoft 365, and the receiving server. The wording tells you which.
Email rate limit exceeded: what to do right now
An email rate limit exceeded error is handled in 5 steps: stop every retry, save the full error with its status code and time, identify which layer refused the send, count the users who are stuck, and get them signed in another way while the window runs. Change providers after that, not before.
- 01 Stop the retries. Turn off any loop, scheduled job, test script or repeated clicking that is sending again, because a retry can count against the same window.
- 02 Save the evidence before it scrolls away: the full error object, the HTTP status, the response headers (a Retry-After header if there is one) and the time, from the browser's network tab or the server log.
- 03 Identify the layer from the wording and the status code, using the table in the next section.
- 04 Count who is stuck: unconfirmed users created in the last few hours in the auth provider's user list, or refused and queued sends in the email provider's activity log. Write the number down.
- 05 Get the stuck users through while the window runs, where your auth provider lets a server confirm a user or generate a sign-in or reset link that you send another way. That works for a handful of people, not for a thousand.
A refused send is one branch of how to improve email deliverability, and the branch where the fix depends most on reading the message before touching anything.
The fifth step has a real route on some stacks. Supabase’s admin API has generateLink, which “Generates email links and OTPs to be sent via a custom email provider”, and updateUserById, whose changes “are applied directly without confirmation flows”; both need the secret key and belong on a trusted server. Firebase’s Admin SDK generates password reset, verification and sign-in links that you can send through your own delivery service. For a user stuck unconfirmed, Lovable’s docs say you can create the account for them with Add user → Create new user in More → Cloud → Users, and users created that way are confirmed automatically; a way to copy a link is not stated.
I put them in this order because switching provider feels like the fix, and it throws away the error text that says which limit fired. If you are a user of someone else’s app and see this at signup, only that app’s owner can fix it. Wait, then try once.
What the message means: the four layers that send it
Email rate limit exceeded means a sender or receiver refused a message because too many were sent in its window. Four layers use the wording: an auth service’s built-in mailer, an email provider’s plan quota, a mailbox host such as Gmail or Microsoft 365, and the receiving server. An HTTP 429 points at the first two.
Which of them printed it decides what to do next, so start from the text and the status code you already have.
| Layer | The wording and code you see | Where you see it | The window | Who can raise it |
|---|---|---|---|---|
| Auth service’s built-in mailer | Supabase: HTTP 429 with an error code that names email sends. Lovable: new emails rejected “until the hour resets” | Browser console or the auth response, at signup or password reset | Per hour. Lovable per workspace: Pro 100 app and 500 authentication emails, Business 300 and 3,000, Enterprise 1,000 and 10,000 | Supabase: you, by moving to custom SMTP or a Send Email hook. Lovable: Lovable Support; authentication emails also have a per-project limit you can adjust yourself |
| Email provider’s plan | HTTP 429 with the provider’s error type, such as Resend’s rate_limit_exceeded or daily_quota_exceeded; Amazon SES returns ThrottlingException | Your server logs and the provider’s log | Per second, per day or per month, by plan (figures in the provider table below) | You, by upgrading, or the provider’s support where its docs offer a rate increase |
| Mailbox host used as SMTP | Google Workspace: “You have reached a limit for sending email.”; Microsoft 365: “the sender’s submission quota was exceeded” | A bounce in that mailbox, or the SMTP reply in your app’s log | Per day: Google Workspace blocks sending “for up to 24 hours”; Microsoft counts the past 24 hours | Microsoft: nobody (“can’t be increased”). Google: a raise on request is not stated in Google’s docs |
| Receiving server | SMTP 421 4.7.28 (“Gmail has detected an unusual rate of email”) or 450 4.2.1 | Only in the provider’s activity log, as deferred or delayed | Set by the receiver; no figure is stated in Google’s SMTP error reference | Only the receiver; your side can slow down and check authentication |
| Your own code | Whatever message your resend button prints | Your app | Whatever you set | You. It is working as intended |
Reading the code takes two rules. A 429 is HTTP’s “Too Many Requests”, which RFC 6585’s 429 status code defines as “the user has sent too many requests in a given amount of time”, so it came from an API sitting in front of the mail. SMTP replies that start with 4 are transient, meaning “the action may be requested again”, and replies that start with 5 are permanent; Google’s SMTP error reference lists its rate deferrals under 421 and 450.
The fourth layer is the one people misread, because nothing in their own app changed. On 4 September 2026, Clerk, an authentication service, posted on its status page that emails to Microsoft-hosted mailboxes “are being rate limited on the receiving side”. Its update added that the emails “are still accepted but some are delayed by several minutes while they’re retried”, and it reported delivery normal again “since 12:40 UTC” the same day. The refusal names its layer if you keep the exact text, and a fix only works on the layer that refused. As I read it, no plan upgrade or setting on the sender’s side would have moved that one.
Why this happens
These are the causes as I read them, most common first for an app built on a builder platform. The first is the app still sending through the auth service’s built-in mailer, which Supabase’s own docs say “is not meant for production use”. Then come a developer testing signup over and over from one machine, a launch, import or invite blast that sends a day’s volume in a minute, and a retry loop with no backoff that turns one refusal into dozens.
The last two are less obvious. A signup form with no abuse protection lets a stranger enter other people’s addresses, so the attacker spends your quota and your reputation. A new sending domain or a shared IP that the receiver does not trust yet gets deferred even at modest volume.
| Cause | The layer it hits | How you confirm it |
|---|---|---|
| Built-in mailer still in use | Auth service’s built-in mailer | The auth settings show no custom SMTP or provider connected |
| Repeated test signups from one machine | Auth service’s built-in mailer | The auth logs show the requests coming from one IP or one address |
| Launch, import or invite blast | Built-in mailer or provider plan | The provider’s log shows that hour’s send count near the plan’s limit |
| Retry loop with no backoff | Any layer with an API | Your server log shows the same request repeated seconds apart |
| Signup form used by a stranger | Built-in mailer or provider plan | New unconfirmed users with addresses nobody on your team recognizes |
| New domain or untrusted shared IP | Receiving server | Deferrals at one mailbox provider only, with 421 replies in the provider’s log |
Supabase, Firebase and builder platforms: the built-in mailer’s hourly cap
Built-in auth mailers are capped per hour or per day. Supabase caps its built-in email service for the whole project, Firebase Authentication sets daily limits per email type that rise on the Blaze plan, and Lovable limits how many emails a workspace sends per hour, with separate limits for app emails and authentication emails.
Supabase allows “2 emails per hour with the built-in email provider” for the whole project, plus a separate 60-second window per user that its table lists as its own limit; the two Supabase email rate limits, the error code and what upgrading does and does not change have their own walk-through. The numbers sit on Supabase’s Auth rate limits page.
Firebase Authentication’s limits are daily. On the Spark plan it allows 1000 address verification emails, 150 password reset emails and 5 email link sign-in emails a day; on the Blaze plan those become 100,000, 10,000 and 25,000. For higher limits, Firebase says to “contact Firebase support at least two weeks in advance”. Lovable’s per-plan hourly figures are in the table above.
The pattern, as I read it: a built-in mailer you did not choose is there so you can try the product, and an app with real signups belongs on its own provider, set up along the lines of transactional email best practices. Supabase says its default server exists “To get you started”.
Resend, SendGrid, Postmark and Amazon SES: plan quotas and per-second limits
Transactional email providers usually set two limits: a quota by plan and a speed per second. Resend’s free plan prints 100 emails a day and 3,000 a month. A refusal arrives as an HTTP 429 or the provider’s own error code, and the error type says which limit it was.
| Provider | The quota the docs print | The per-second limit | What the refusal looks like | How to raise it |
|---|---|---|---|---|
| Resend | Free: 100 emails a day, 3,000 a month; paid plans have no daily quota | 10 requests per second per team | HTTP 429 with rate_limit_exceeded, daily_quota_exceeded or monthly_quota_exceeded | Upgrade the plan; contact support for a rate increase |
| SendGrid | Free trial: 100 emails a day for 60 days | A fixed number of requests per refresh period per endpoint; up to 10,000 requests per second to v3 Mail Send | HTTP 429 with “too many requests” and an X-RateLimit-Reset header | Upgrade to a paid plan |
| Postmark | Free Developer plan: 100 emails every month | Not stated in Postmark’s docs as a number; the API answers 429 at “a rate that exceeds acceptable use” | HTTP 429 “Rate Limit Exceeded” | Move to a paid plan; paid tiers start at 10,000 emails a month |
| Amazon SES | Sandbox: 200 messages per rolling 24-hour period, per Region | Sandbox: 1 message per second | ThrottlingException with “Daily message quota exceeded” or “Maximum sending rate exceeded”; over SMTP, “454 Throttling failure” | Request production access, then quota increases through the AWS Support Center |
Limits checked on 4 October 2026, on each provider’s own docs and pricing pages: Resend’s rate limit docs, SendGrid’s rate limits, Postmark’s API overview and support center, and Amazon SES sending quotas.
Two details change what a retry does. Amazon SES says that when a quota is reached it “drops the message and doesn’t attempt to redeliver it”, so a refused password reset is gone and has to be sent again later. In the sandbox, SES also sends only to verified addresses and domains or its mailbox simulator, so a launch from a sandboxed account fails for real customers before any quota is reached. Which provider fits an app is a separate question; this table carries only the limits.
Gmail and Microsoft 365 as the app’s SMTP: the mailbox limit
A Gmail or Microsoft 365 mailbox used as an app’s SMTP server has a daily limit meant for a person, which a busy app day can pass. Google Workspace then blocks sending for up to 24 hours, and Microsoft 365 until the past 24 hours’ recipients drop below the limit. Move app mail to a transactional provider.
Google Workspace sending limits give a user account 2,000 messages a day (500 on a trial account) and 100 recipients per message for mail sent over SMTP, counted over “a rolling 24-hour period”; Google’s SMTP relay service has limits of its own. Past a limit, Google says “users can’t send new messages for up to 24 hours”. A personal Gmail account has a lower limit with its own message, which may appear past 500 emails sent in a day; its wording and wait are in the last question below.
Exchange Online limits are 10,000 recipients per day and 30 messages per minute per mailbox, and Microsoft calls both “hard limits enforced at the service level” that “can’t be increased”. Messages submitted over SMTP faster than the message rate “will be rejected and the client will need to retry”.
I would not fix it with a bigger mailbox or a second account, and the mailbox stays with the people who read it.
How to fix it, by layer, and how to confirm it worked
The fix for an email rate limit depends on the layer: your own provider for a built-in mailer, a plan or rate increase for a provider quota, moving app mail off a mailbox host, and slower sending for receiver deferrals. The proof is one delivered message of each critical type, found in the provider’s log with its time.
| Layer | The fix | The proof it worked |
|---|---|---|
| Built-in mailer | Connect your own provider by SMTP or API in the auth service’s settings, after verifying the sending domain, then check the auth service’s own hourly email setting | A real signup email shows as delivered in the new provider’s log |
| Provider plan | Upgrade the plan or request a higher rate; on Amazon SES, request production access before launch | The send that failed now appears in the provider’s log as delivered |
| Mailbox host | Move app mail to a transactional provider | The provider’s log shows the app’s messages delivered, sent from the provider, not the mailbox |
| Receiving server | Slow sending to that domain, let the provider’s retries run, check authentication and reputation | Deferrals for that domain stop and the delayed messages show as delivered |
| Your own code | Show the user the wait time instead of a raw error | A second click inside the window shows the wait, not an error |
For a built-in mailer, verify the sending domain before you switch; SPF, DKIM and DMARC setup comes first, or the new provider’s mail lands somewhere worse. Then open the auth service’s own emails-per-hour setting. On Supabase it stays low after you connect custom SMTP until you change it, which is part of the Supabase walk-through above.
For Amazon SES, the production access request is made from the account dashboard, where you choose “Marketing or Transactional”, give a website URL and agree “to only send email to individuals who’ve explicitly requested it”. AWS says its support team gives “an initial response to your request within 24 hours”, which is the reason to file it before a launch, not on the day.
For receiver deferrals, the provider’s retries usually clear the backlog, as they did in the incident above. If deferrals keep coming at one mailbox provider, the problem is reputation or authentication, and mail that is deferred and then filtered is a different failure: transactional emails landing in spam.
The proof has to pass when the limit is cleared and fail while it is live, so run it on the deployed production app, not on a local dev server with its own mail settings. Send one real message of each critical type (signup, password reset, receipt) to a mailbox you control. Find each send in the provider’s log and check that its status is delivered, not only sent: Resend’s email.sent means “the API request was successful”, while email.delivered means it “successfully delivered the email to the recipient’s mail server”. On SES in the sandbox, the mailbox must be a verified address. Save the log entry and the received message with their times. Then repeat the step that failed, the signup that threw the error, once and not in a loop. If the provider log shows the message delivered and the inbox does not, the limit is cleared and the problem has changed to delivery, the spam-folder failure above.
For an API that answers 429 with a Retry-After header, as Resend does, a sender can wait the stated seconds instead of hammering the endpoint. Here send() is your own call to the provider’s API:
let res = await send();
for (let i = 1; i <= 3 && res.status === 429; i++) {
const secs = Number(res.headers.get('retry-after')) || 2 ** i;
await new Promise((r) => setTimeout(r, secs * 1000));
res = await send();
}
// still 429 after three waits: stop, keep the error, find the layer
Amazon SES asks for something slower: on a ThrottlingException, its docs say to “wait for an interval of up to 10 minutes, and then retry”.
How to stop it happening again
These five controls are the ones I rely on, and each keeps one of the causes above from coming back.
- 01 Send app mail through a dedicated transactional email provider with delivery tracking, never the auth service's built-in mailer or a person's mailbox.
- 02 Put every send through a queue that honors Retry-After and backs off, so one refusal never becomes a hundred attempts.
- 03 Throttle the resend button and the signup form per user and per IP address, so a stranger cannot spend your quota.
- 04 Alert on the first 429 or the first spike in deferrals from the provider, not on the first customer complaint, and test the alert once with the provider's own test path or a copy of the alert with its threshold lowered until it fires.
- 05 Before a launch, an import or an invite campaign, compare the expected sends per hour with your plan's limit and ask for the raise well ahead.
The third control sits on the resend button, which belongs to the larger job of how to send a verification email; the wording of the message is a matter for an email verification email template. If the quota was spent by an abuser, the stuck users are the small problem: check what else that form lets a stranger do.
Common questions about email sending limits
How many emails can I send in 24 hours?
A Google Workspace user can send up to 2,000 messages in 24 hours, a Microsoft 365 mailbox can send to 10,000 recipients, and an email provider sets its own figure by plan, such as 100 a day on Resend’s free plan. A personal Gmail account may show its limit message past 500 emails sent in a day.
How many emails can I send with Office 365 per day?
An Office 365 mailbox can send to 10,000 recipients per day, counted over the past 24 hours, and no more than 30 messages a minute. The daily limit counts recipients, not messages, so one message to several people uses several, and Microsoft says it can’t be increased.
What does the “365 email sending limit exceeded” message mean?
It means the Microsoft 365 mailbox passed its recipient rate limit of 10,000 recipients in 24 hours, and Microsoft’s bounce reads “The message can’t be submitted because the sender’s submission quota was exceeded.” Sending resumes once the recipients from the past 24 hours drop below the limit. If you didn’t send those messages and suspect the account has been compromised, Microsoft says to reset the password. My position is that an app should not be sending from a person’s mailbox in the first place.
How to bypass rate limit exceeded?
There is no bypass worth having. Rotating accounts, addresses or IPs to dodge a limit puts the sending domain’s reputation at risk, as I read it, and the receivers that defer you will notice. The legitimate routes are a higher plan, a rate increase request where the provider offers one, or your own email provider in place of a built-in mailer.
How to solve you have reached a limit for sending mail?
Wait, then move app mail off the mailbox. That wording is personal Gmail’s, which Google says you may see past 500 recipients in a single email or 500 emails sent in a day, and after it you “should be able to send emails again within 1 to 24 hours”.
Fixing the bug in this guide gets you past today. If you would rather have the whole foundation checked and built in one go, that is what the sprint below is for.
Built it with AI. Now it has to hold up for real customers.
The Production Hardening Sprint takes the app you already have and builds the production foundation underneath it. Authentication and access rules, payments that stay consistent, error handling, monitoring, backups, automated tests and a documented handover. Our engineers work inside your existing codebase for ten working days. All 123 deliverables are included, and you get the evidence for each one.
See the Production Hardening Sprint →
$2,500 fixed price · 10 working days · One codebase