Resend API error · 429
Resend 429 rate_limit_exceeded: what it means and how to fix it
Resend returns 429 rate_limit_exceeded when your team sends more than its request rate, 10 requests per second by default, shared by every API key on the team. Fix it by waiting the number of seconds in the retry-after header and sending the same request again with the same idempotency key, and by batching: one POST /emails/batch carries up to 100 emails and counts as one request.
The error
| Status | 429 |
|---|---|
| Name | rate_limit_exceeded |
| Message | Too many requests. Please limit the number of requests per second. Or contact support to increase rate limit. |
Name, status and message as Resend documents them, last verified 28 September 2026.
Why it happens
- The limit is per team, not per API key. A backfill script, a cron job and your production app on three different keys all draw on the same 10 requests per second.
- There is no burst allowance. An eleventh request inside the same second is refused.
- A loop that calls
POST /emailsonce per recipient reaches the limit at the eleventh recipient. - The Node SDK does not retry. A 429 comes back as the error in
{ data, error }, so a caller that only checks data drops the email without noticing.
How to fix it
Retry on retry-after, with the same idempotency key
Resend sends
retry-afteron a 429: the seconds to wait before the next request. Wait that long and send the identical request with the same idempotency key, so a request that did get through is not sent twice. Only retryrate_limit_exceeded:daily_quota_exceededandmonthly_quota_exceededare also 429s, and retrying them only repeats the refusal until the quota resets.import { Resend } from "resend"; const resend = new Resend(process.env.RESEND_API_KEY); const sleep = (s: number) => new Promise((r) => setTimeout(r, s * 1000)); export async function sendWithRetry( email: Parameters<typeof resend.emails.send>[0], idempotencyKey: string, attempts = 5, ) { for (let i = 0; i < attempts; i++) { const { data, error, headers } = await resend.emails.send(email, { idempotencyKey }); if (!error) return data; // Two other 429s mean a quota is spent. Waiting seconds will not clear them. if (error.name !== "rate_limit_exceeded") throw new Error(`${error.name}: ${error.message}`); await sleep(Number(headers?.["retry-after"] ?? 2 ** i)); } throw new Error("Still rate limited after retries"); }Batch instead of looping
POST /emails/batchtakes up to 100 emails and counts as one request against the rate limit. A welcome email to 400 new users is four requests, not 400. Batch sends do not support attachments.const { data, error } = await resend.batch.send( users.slice(0, 100).map((u) => ({ from: "Acme <hello@acme.com>", to: [u.email], subject: "Welcome to Acme", html: welcomeHtml(u), })), );Pace everything that shares the team
Because the limit is per team, pacing inside one process is not enough when several services send. Put sends through one queue, or give each service a share of the 10 per second. The
ratelimit-remainingandratelimit-resetheaders on every response say how much of the current window is left.Ask for a higher limit
Resend raises the rate for trusted senders on request, through support. The current limit is on the Usage page in the dashboard's settings.
If you would rather not share one request limit across a whole team, SendRaven applies its limit per API key and sends Retry-After on its 429s; see SendRaven vs Resend or migrate from Resend.
Questions
What is Resend's API rate limit?
Resend's default rate limit is 10 requests per second per team, shared across all of the team's API keys, with no separate burst allowance. It can be raised for trusted senders on request.
Does the Resend Node SDK retry a 429 automatically?
No. The SDK returns the 429 as the error in { data, error }, with the response headers beside it, and leaves the retry to the caller.
Does a batch send count as one request?
Yes. One POST /emails/batch call carries up to 100 emails and counts as a single request against the rate limit.
Is it safe to retry a 429 from Resend?
Only rate_limit_exceeded, and only with the same idempotency key. The other two 429s, daily_quota_exceeded and monthly_quota_exceeded, will not clear until the quota resets, so retrying them is wasted work.
What SendRaven does here
SendRaven answers the same situation with 429 rate_limited and a Retry-After header.
The limit is 600 requests a minute per API key, not per account. A runaway backfill on its own key runs into its own limit and leaves the key your password resets use alone. Batch sends take up to 100 emails per request, and accepted sends queue for delivery rather than failing when they arrive faster than mail can leave.
A spent plan is never a 429. It is 402 plan_limit_reached, because nothing clears it in seconds: a retry loop keyed on 429 therefore never hammers an empty allowance. The one other 429 is daily_limit, a cap you put on the key yourself, and its Retry-After points at the next 00:00 UTC so a generic retry loop gives up instead of trying all afternoon.
Every error type, its status and whether to retry it is on the errors reference. The whole comparison is SendRaven vs Resend, and moving over is migrate from Resend.
The longer story is on the blog: Resend has three 429s. Only one of them should be retried.
HTTP/1.1 429 Too Many Requests
Retry-After: 12
{ "error": { "type": "rate_limited",
"message": "Too many requests. Wait for the seconds in Retry-After, then retry." } }Other Resend errors
Sources
- Resend: errors
- Resend: usage limits
- Resend: account quotas and limits
- Resend: idempotency keys
- Resend: send batch emails
- resend-node on GitHub
Last verified 28 September 2026.

Email infrastructure for AI agents.
SendRaven sends and receives email through one API, joins replies to their threads, and lets you put a daily limit, a recipient allowlist or an approval hold on the key an agent holds.
