Rate limits & idempotency
Request limits, send caps, per-project throttling, and safe retries.
Request rate limits
Management endpoints — templates, automations, segments, topics, senders, domains, metrics and settings — are rate-limited per API key, in a rolling 60-second window:
| Kind | Limit |
|---|---|
Reads (GET) | 120 / minute |
Writes (POST, PATCH, PUT, DELETE) | 30 / minute |
Responses carry RateLimit-Limit, RateLimit-Remaining and RateLimit-Policy.
Exceeding a limit returns 429 with code rate_limited and a
retry_after_ms field telling you exactly how long to wait.
These are request limits, separate from the send limits below. Sending itself
is governed by the per-project throttle and the daily/monthly caps rather than
by this per-key request window, so don't rely on a 429 here to pace your
sending — use the caps.
Limits are enforced per running instance, so a deployment behind several instances may allow proportionally more in aggregate. Treat them as a runaway-loop backstop, not a quota to pace against.
Send caps
Separately from request limits, every send is checked against a daily and a
monthly cap. Both count your own messages in the current UTC calendar day /
month — so the daily window resets at 00:00 UTC and the monthly one on the
first of the month.
The ceiling comes from your provider plan (Resend or SendGrid), resolved
from what the provider reports plus its published plan limits. On top of that,
a project can set an optional daily_send_cap that only ever tightens the
daily limit further — the tightest applicable limit wins:
| Cap | Ceiling | Tightened by | Error |
|---|---|---|---|
| Daily | Provider plan's daily limit | daily_send_cap project setting | 429 daily_cap |
| Monthly | Provider plan's monthly limit | — (no operator override) | 429 monthly_cap |
0 means unlimited for either cap. Leaving daily_send_cap unset means
"rely on the provider plan" rather than imposing a separate ceiling.
Caps are a hard stop enforced inside the send path, so they apply to
automations and scheduled sends too — not just direct API calls. Read the
current values and today's usage with
GET /api/v1/settings.
Throttling
Sends are throttled per project (best effort, ~8/second). When a burst exceeds
the rate, the extra sends don't fail — they fall back to the worker queue and
return 202 with status queued. You don't need to implement client-side
pacing for normal traffic.
The kill switch
A project has a sendsPaused kill switch. While it's on, every send returns
409 with code sends_paused. It's re-checked on the worker path too, so
queued and scheduled sends respect it as well.
Idempotency
Pass an Idempotency-Key header on POST /api/v1/emails to make retries safe:
curl https://sendandretain.com/api/v1/emails \
-H "Authorization: Bearer aem_your_key" \
-H "Idempotency-Key: welcome-jane-1" \
-H "Content-Type: application/json" \
-d '{ "to": "[email protected]", "template": "welcome" }'The key is unique per project (≤256 characters) and is also forwarded to the
provider. A repeat with the same key returns the original result with
deduplicated: true instead of sending again:
{ "id": "msg_3aF9c1", "status": "sent", "deduplicated": true }Events support the same idea with dedupe_key on
POST /api/v1/events.
If a request fails with 500/internal or times out, retry with the same Idempotency-Key.
Retrying without one risks sending twice.