Rate Limits

Per-minute request limits, monthly email limits, probation caps for new accounts, and per-domain daily limits

Four different limits apply, and they fail in different ways. Knowing which one you hit tells you what to do about it.

LimitScopeResponse when exceeded
Requests per minuteSend endpoints, per API key429 rate_limited, retry after Retry-After seconds
Emails per monthWhole account429 monthly_limit_reached, resets on the first of the month or when you upgrade
Probation caps (hourly, daily, recipient domains)New accounts, for their first 72 hours429 probation_limit_reached, retry at resetsAt
Emails per day per domainEach sending domain or senderThe email is queued but held until the day rolls over; see below

Requests per minute

Applies to POST /v1/send/emails/send and POST /v1/send/emails/batch only. Reading emails, managing domains, and the other endpoints are not rate limited in this way.

PlanSend requests per minute
Hobby10
Pro60
Enterprise600 by default; custom on request

A batch request counts as one request regardless of how many emails it contains, so batching is the way to send more than the per-minute limit allows.

Every response from a send endpoint includes:

HeaderMeaning
X-RateLimit-LimitYour per-minute limit.
X-RateLimit-RemainingRequests left in the current minute.

When you exceed the limit the response is 429 with code rate_limited and an additional header:

HeaderMeaning
Retry-AfterSeconds to wait before retrying.
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded: 10 requests per minute"
  }
}

Handling 429

Wait Retry-After seconds and retry the same request. Sending was not attempted, so retrying does not risk a duplicate email. A minimal loop:

async function sendWithRetry(body, attempts = 5) {
  for (let i = 0; i < attempts; i++) {
    const res = await fetch("https://api.do.dev/v1/send/emails/send", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.SEND_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    });
    if (res.status !== 429) return res;
    const wait = Number(res.headers.get("Retry-After") ?? 5);
    await new Promise((r) => setTimeout(r, wait * 1000));
  }
  throw new Error("rate limited after retries");
}

Only retry quickly on 429 with code rate_limited. A 429 with code probation_limit_reached clears at resetsAt, which may be an hour away; one with code monthly_limit_reached will not clear by waiting at all.

Monthly email limits

Each plan includes a number of emails per calendar month. Every email queued by a send or batch request counts as one, whether it is later delivered or bounces.

PlanEmails per month
Hobby3,000
Pro50,000
EnterpriseUnlimited

When the limit is reached, send and batch requests return 429 with code monthly_limit_reached until the first of the next month (UTC) or until you upgrade in the dashboard. Nothing is queued. Your current usage is shown in the dashboard. See Pricing.

{
  "error": {
    "code": "monthly_limit_reached",
    "message": "Monthly email limit reached",
    "limit": 3000,
    "used": 3000,
    "resetsAt": "2026-10-01T00:00:00.000Z",
    "upgradeUrl": "https://send.dev/dashboard/billing/overview"
  }
}

resetsAt is when the counter clears. For a batch request, the check is applied to the whole batch: if accepting every item would cross the limit, nothing is queued.

Probation limits for new accounts

New accounts are not reviewed by a person before they can send. Instead, every new account spends its first 72 hours on probation with low caps, while send.dev watches how the first messages land. The caps apply on top of your plan's limits, on both API hosts, and count every email in a send or batch request.

Trust stageEmails per hourEmails per dayDistinct recipient domains per hourHow long
probation (standard)502002572 hours
probation (higher-risk signup)20100107 days
establishedPlan limits onlyPlan limits only, and the per-domain ladder belowUntil 14 further clean days
trustedPlan limits onlyPlan limits only, and the per-domain ladder below

"Higher-risk" is decided by automated signals at signup and domain creation (for example a free-mail account paired with a freshly registered domain). Nothing else about the two probation flavors differs.

The hourly and daily windows are rolling. The recipient-domain cap counts distinct domains after the @ across all recipients in the last hour; it exists because a legitimate new transactional sender writes to a handful of domains, while a purchased list hits hundreds.

Exceeding any cap returns:

{
  "error": {
    "code": "probation_limit_reached",
    "message": "Your account is in its probation period and is limited to 50 emails per hour. Limits lift automatically once the probation period ends with healthy delivery.",
    "limit": 50,
    "window": "hour",
    "metric": "sends",
    "resetsAt": "2026-09-05T15:00:00.000Z"
  }
}
FieldMeaning
limitThe cap that was hit.
windowhour or day.
metricsends for the hourly or daily email cap, recipient_domains for the distinct-domain cap.
resetsAtISO-8601 time the window clears. Also sent as Retry-After in seconds.

The check runs before the email is recorded, so a rejected request does not count against the window or your reputation. A batch that would cross a cap is rejected whole.

Graduating

At the end of the probation window, an account moves to established if its 24-hour bounce rate is under 2% and it has had no spam complaints; otherwise it stays on probation until a window passes cleanly. established becomes trusted after 14 further days of clean sending. Promotion is checked hourly; nothing needs to be requested. GET /v1/send/tenants returns the current trustStage and, while on probation, probationEndsAt. See Domains.

Automatic pauses during probation

Sending is paused (403 sending_paused) without waiting for the window to end when the early signals look like abuse:

  • Three or more bounces within the account's first 25 emails.
  • Any spam complaint within the first 100 emails.
  • A sudden spike: 100 or more emails in an hour and more than five times the account's trailing hourly average.
  • Repeated risky content, five or more signals in 24 hours: links to domains other than the sending domain, URL-shortener links, or "verify your account"-style subject lines. Individual signals never block a send on their own.

A pause resets probation, so the caps start over once it is lifted. The notification email explains what tripped; reply to it or contact support.

Daily limits per domain

Independently of your plan and of account probation, each sending domain (and each single-address sender) has a daily cap that grows with its track record:

TierEmails per dayHow a domain gets here
New200Every domain starts here after verification.
Warming2,000Automatically, after at least 100 emails and 3 days with a bounce rate under 1% and complaint rate under 0.05%.
Trusted50,000Automatically, after at least 5,000 emails and 14 days with a bounce rate under 0.5% and complaint rate under 0.03%.

This is abuse protection. A brand-new domain sending thousands of messages on day one is exactly what a compromised account or a purchased list looks like, both to send.dev and to Gmail, Outlook, and Yahoo, and it hurts deliverability for every send.dev customer. The ladder makes a new domain ramp up the way mailbox providers expect.

If you have a legitimate launch that needs more volume than the ladder allows on day one, contact support ahead of time with the domain and the expected daily volume. Enterprise plans can have custom limits set at onboarding.

Other limits

WhatLimit
Emails per batch request100
Attachments per email10
Request body size, including base64 attachmentsabout 236 KB (241,664 bytes); use path URLs for larger files
Subject length500 characters
Sender verification emailsOne per address per 5 minutes