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

Status429
Namerate_limit_exceeded
MessageToo 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 /emails once 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

  1. Retry on retry-after, with the same idempotency key

    Resend sends retry-after on 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 retry rate_limit_exceeded: daily_quota_exceeded and monthly_quota_exceeded are 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");
    }
  2. Batch instead of looping

    POST /emails/batch takes 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),
      })),
    );
  3. 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-remaining and ratelimit-reset headers on every response say how much of the current window is left.

  4. 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." } }

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.