Skip to content
Developers

Email Marketing

Limits and warm-up

A refused send always comes back with a code that names the limit that refused it. This page maps each code to the number behind it, says whether waiting helps, and shows how to read your allowance before you hit it.

The three independent limits

A send passes three gates in order. Each can refuse it on its own, and the first one that fails decides the code you receive.

API key rateRequests per minute for one key, independent of how much mail it asks for. Refuses with RATE_LIMITED before anything is queued. Resets within the minute.
Brand allowanceFree sends per calendar month (UTC) for the registrable domain your From address belongs to. Every workspace on that domain draws the same bin, so mail.acme.com and news.acme.com share one allowance. Past it, sends are billed per email instead of refused — unless there is also no funded wallet.
Sending domain stateThe exact domain in the From address (not the apex) carries its own state: warm-up stage, any platform cap, paused, frozen. A new subdomain is capped even when its brand has allowance left.

Nothing else refuses mail. Contacts are not capped, seats are not part of sending, and there is no tier to upgrade. If a send was accepted, the only thing that can still stop it is the recipient's own suppression state (a bounce or complaint), which is reported on the send row rather than as an HTTP error.

Error codes and retry semantics

CodeStatusMeaningWhat to doRetry
RATE_LIMITED429This API key exceeded its request rate.Wait for the window to reset (`X-RateLimit-Reset` / `Retry-After`).Yes
FREE_ALLOWANCE_EXHAUSTED429The brand's free sends for this calendar month are used. The allowance is shared by every workspace sending from the same registrable domain.Wait until the 1st of next month (UTC), or request an allowance increase — sends beyond the allowance are billed per email.Yes
DAILY_QUOTA_EXCEEDED429The workspace's sends for this UTC day are used.Wait until UTC midnight.Yes
MONTHLY_QUOTA_EXCEEDED429The workspace's monthly sending limit — granted by the platform — is used.Wait until the 1st of next month (UTC), or request a limit increase.Yes
DOMAIN_WARMING_UP429The sending domain is in a warm-up stage and has used its cap for today.Wait until UTC midnight. The stage advances after several days of sustained volume with a clean bounce and complaint rate.Yes
DOMAIN_WARMUP_FROZEN429A deliverability review froze this domain after repeated elevated bounce or complaint rates.Contact support. Waiting does not clear a freeze.No
DOMAIN_DAILY_QUOTA_EXCEEDED429A platform-set daily cap for this sending domain is used.Wait until UTC midnight, or contact support if the cap is wrong.Yes
DOMAIN_MONTHLY_QUOTA_EXCEEDED429A platform-set monthly cap for this sending domain is used.Wait until the 1st of next month (UTC), or contact support.Yes
DOMAIN_PAUSED429The sending domain was paused by the account owner or by the platform.Resume the domain in the app, or contact support if the platform paused it.No
ORGANIZATION_PAUSED429Sending is paused for the whole workspace.Resume sending in the app, or contact support.No
ORGANIZATION_INACTIVE429Email is not set up, or the workspace is not active.Complete email setup, or contact support about the workspace status.No
CAMPAIGN_TOO_LARGE400The campaign exceeds the largest single campaign the platform will queue.Split the campaign into smaller sends.No
INSUFFICIENT_FUNDS402Sends beyond the free allowance are billed per email, and the wallet is short.Top up Work Funds, or wait for the next month's free allowance.No

Retryable codes also send Retry-After (seconds) and X-RateLimit-Reset where a window applies. A non-retryable code means a retry will produce the same answer: surface it to a human instead of looping.

Retrying safely
# 429 with a code and a Retry-After header — wait, then retry the same request.
# Idempotency-Key makes the retry safe even if the first attempt was accepted.
curl -sS -D - -o /dev/null https://api.thealtermeai.com/api/v1/email/messages \
  -H "Authorization: Bearer emk_YOUR_LIVE_KEY" \
  -H "Idempotency-Key: order-8812-receipt" \
  -H "Content-Type: application/json" \
  -d '{ "to": "[email protected]", "subject": "Receipt", "html": "<p>Thanks</p>" }'

Warm-up: the ladder every new domain climbs

A newly verified sending domain starts capped and earns its way up. The expected window is two weeks. The caps are the same ones the platform uses for its shared sending IPs, so there is one table to learn:

StageExpected windowSends per UTC day
1Day 1–350
2Day 4–7200
3Day 8–111,000
4Day 12–145,000
warmedafter stage 4only your own caps

The window is what the stage expects, not what advances it. A domain climbs when it has sent a real share of its current cap on several days and its bounce and complaint rates stayed clean. A domain sending five messages a day is never blocked by the ladder — 50 is more than it wants — it simply never advances.

Two behaviours surprise integrators, both deliberate:

  • A second subdomain inherits the apex's position. Adding news.acme.com when mail.acme.com is already warmed does not restart you at 50 a day. Re-claiming a domain that was deleted earlier resumes where it stopped and keeps any freeze.
  • A spike moves you down, not just sideways. A day above the regression bounce or complaint rate drops the domain a stage and restarts its clock. Repeated spikes freeze it, and DOMAIN_WARMUP_FROZEN needs a human: the freeze survives a delete and re-add.

Seeing the limits before they refuse you

GET/api/v1/email/organizationAny valid key

The organization endpoint returns the allowance and every sending domain's warm-up state, so a client can pace itself. Read allowance.monthly.remaining for the brand's free sends and each domain's warmup.capToday minus warmup.sentToday for the tighter of the two.

curl
curl -sS https://api.thealtermeai.com/api/v1/email/organization \
  -H "Authorization: Bearer emk_YOUR_LIVE_KEY"
200 response (abridged)
{
  "organization": { "name": "Acme Media", "profileId": "…" },
  "key": { "name": "server", "environment": "live", "scopes": ["messages:write"] },
  "allowance": {
    "pool": "acme.com",
    "monthly": { "limit": 3000, "used": 1760, "remaining": 1240, "sharedByWorkspaces": 2 },
    "daily": 100,
    "billing": "per_email_beyond_allowance"
  },
  "domains": [
    {
      "domain": "mail.acme.com",
      "registrableDomain": "acme.com",
      "status": "verified",
      "warmup": { "stage": 2, "capToday": 200, "sentToday": 32, "daysQualified": 2, "daysRequired": 3, "frozen": false },
      "caps": { "daily": 200, "monthly": null, "source": "warmup" }
    }
  ]
}

allowance.monthly.sharedByWorkspaces above 1 is worth surfacing in your own dashboard: when a brand has several workspaces, the allowance can be spent by a sibling and the number will move without your integration sending anything.

Three refusals, three different fixes

1. A digest to 4,000 recipients fails with 429 RATE_LIMITED. Nothing was queued. Slow the request rate down (the limit is per key, so a second key is not the fix). Retry with the same Idempotency-Key.

2. A receipt fails with 429 FREE_ALLOWANCE_EXHAUSTED. The brand has spent this month's free sends — possibly on a sibling workspace. Waiting until the 1st works; so does topping up Work Funds, after which the same send is accepted and billed per email. A transactional receipt is usually worth the second option.

3. The same receipt succeeds for mail.acme.com and fails for a brand-new promo.acme.com with 429 DOMAIN_WARMING_UP. The brand has allowance, the key is fine, and the From domain is at stage 1 with 50 sends used today. Send the receipt from the warmed domain, or wait for UTC midnight and pace the ramp: the domain needs volume on several days before it climbs, so a launch that needs 20,000 sends on day one is not going to happen from a domain created today.

Related

Authentication and key scopes: Authentication. Sending transactional mail: Transactional email. Campaign fan-out and its own bound: Campaigns. The machine-readable contract, including the code appendix, is the OpenAPI file.