Errors
Every Send API error uses one envelope:
{ "error": { "type": "validation_error", "code": "routing_provider_not_configured", "message": "Pinned provider(s) not configured on this project: postmark-api. Attach them or drop the routing pin." }}Branch on code — it’s stable. message is for humans and may change.
Field-validation failures carry the same envelope plus error.details, one
entry per failing field; error.code mirrors the first, so a single-mistake
request stays branchable on code alone:
{ "error": { "type": "validation_error", "code": "from:required", "message": "Validation failed — `details` lists each failing field.", "details": [ { "field": "from", "code": "from:required" }, { "field": "subject", "code": "subject:required" } ] }}Batch requests respond 207 Multi-Status: each element of data is either a
send object or one of these envelopes, so
check per element. A persistence failure on one
element surfaces as api_error / server_error — retry just that element.
If a Free-plan cap runs out mid-batch, the elements past the cap surface the
same monthly_quota_exceeded / daily_send_cap_exceeded envelopes a single
send would get — the accepted elements before them still send.
The full contract, including these envelopes, is machine-readable at
mailway.net/openapi.yaml.
At a glance
Section titled “At a glance”| Status | type |
code |
|---|---|---|
| 401 | authentication_error |
missing_api_key · invalid_api_key · key_expired · mode_mismatch |
| 402 | quota_error |
monthly_quota_exceeded · throughput_quota_exceeded |
| 422 | permission_error |
no_providers_configured |
| 422 | validation_error |
routing_provider_not_configured · suppressed_recipient · sender_domain:limit_reached · category:limit_reached · idempotency_key:* · scheduled_at:* · attachments:too_large · attachments:total_too_large · field errors |
| 429 | rate_limit_error |
daily_send_cap_exceeded · rate_limited |
Three rules cover almost every case:
- 402 is a billing state, not a bug. Surface it to whoever owns the plan. Retrying won’t help until the month rolls over or the plan changes.
- 422 means the request will never succeed as-is. Fix the payload; don’t retry blindly.
- Retry 429 and 5xx with backoff and an
Idempotency-Key, so retries can’t double-send.
401 — authentication
Section titled “401 — authentication”missing_api_key
Section titled “missing_api_key”Cause. No Authorization header, or one that isn’t
Bearer mw_live_… / Bearer mw_test_….
Fix. Send Authorization: Bearer mw_live_…. Keys are 40 characters after
the prefix — see the credential grammar.
invalid_api_key
Section titled “invalid_api_key”Cause. The key doesn’t match any active key: mistyped, revoked, or belonging to a deleted project.
Fix. Mint a fresh key in the dashboard. Keys are shown once at creation and stored hashed, so a lost key can’t be recovered — only replaced.
key_expired
Section titled “key_expired”Cause. The key had an expiry date and it has passed.
Fix. Create a new key. If you rotate on a schedule, note that expiry is per key, so overlap the new key before the old one lapses.
mode_mismatch
Section titled “mode_mismatch”Cause. The key’s mode doesn’t match the host. A mw_test_… key was used
on api.mailway.net, or a mw_live_… key on the sandbox host.
Fix. Match them: live keys on api.mailway.net, test keys on the
sandbox. This binding is deliberate — it’s what stops a
misconfigured environment variable from sending real mail from a test suite.
402 — quota
Section titled “402 — quota”monthly_quota_exceeded
Section titled “monthly_quota_exceeded”Cause. The plan’s monthly message quota is spent. Free tier only — paid plans soft-overage instead of blocking.
Fix. Upgrade the plan, or wait for the calendar month to roll over. A plan change lifts the limit immediately.
throughput_quota_exceeded
Section titled “throughput_quota_exceeded”Cause. The plan’s monthly data-volume cap is spent, including the grace margin paid tiers get. Large attachments reach this before the message count.
Fix. Add a throughput add-on or upgrade. Also worth checking whether the volume is intended — see the limits reference for each plan’s cap.
422 — the request can’t succeed as sent
Section titled “422 — the request can’t succeed as sent”no_providers_configured
Section titled “no_providers_configured”Cause. The project has no attached provider, so there is nothing to send
through. Typed permission_error rather than validation_error because the
payload is fine — the project isn’t ready.
Fix. Attach a provider to the project. Mailway is BYOK: it never sends through an account of its own, so a project with no provider genuinely cannot deliver.
routing_provider_not_configured
Section titled “routing_provider_not_configured”Cause. routing.provider pinned a provider that isn’t attached to this
project. The message names the offending slugs.
Fix. Attach that provider, or drop the pin and let the project’s routing strategy choose. A pin is never silently rerouted — if you asked for a specific provider, Mailway would rather fail loudly than quietly send through a different one.
suppressed_recipient
Section titled “suppressed_recipient”Cause. At least one recipient is on the project’s
suppression list, from a hard bounce or a complaint. The
message names the addresses.
Fix. Drop those recipients, or un-suppress the address in the console if you know the bounce is stale. Sending anyway is the wrong instinct: repeat hard bounces are exactly what damages a sending reputation, which is why the gate exists.
sender_domain:limit_reached
Section titled “sender_domain:limit_reached”Cause. The from domain is new to this project and the plan’s
sender-domain limit is already reached. Domains register on first use, so this
fires on the first send from a new domain, not on setup.
Fix. Send from an already-registered domain, remove an unused one in the console, or upgrade. Note that subdomains count separately.
category:limit_reached
Section titled “category:limit_reached”Cause. The project already has its maximum number of distinct
category values. Categories are created on first use, so an unbounded or
templated category value (an order ID, a timestamp) exhausts the cap quickly.
Fix. Reuse an existing category or drop the field. Categories are meant to be a small, stable vocabulary — a per-message identifier belongs in your own metadata, not here.
idempotency_key:*
Section titled “idempotency_key:*”idempotency_key:invalid · idempotency_key:invalid_length
Cause. The Idempotency-Key header is malformed. It must be 12–40
characters of [A-Za-z0-9_-].
Fix. Use a UUID or a similar opaque token. Note the key scopes to the project and the first body wins: replaying the same key returns the original send rather than sending again. See idempotency.
scheduled_at:*
Section titled “scheduled_at:*”scheduled_at:invalid · scheduled_at:too_soon · scheduled_at:too_far
Cause. scheduled_at must be a valid ISO 8601 instant, in the future, and
no more than 72 hours out.
Fix. Send an absolute timestamp with an offset (2026-08-04T09:00:00Z).
For anything beyond 72 hours, hold the message in your own scheduler and
submit it when it’s due.
attachments:too_large
Section titled “attachments:too_large”Cause. One attachment exceeds 10 MB decoded. The cap is on decoded bytes, so base64 in the request body is ~33% larger than the number being checked.
Fix. Compress it, or host the file and send a link. Large attachments also hurt deliverability independently of this limit.
attachments:total_too_large
Section titled “attachments:total_too_large”Cause. All attachments together exceed 25 MB decoded.
Fix. Same as above. Note that up to 20 attachments are allowed, and inline images count toward both caps.
Field errors
Section titled “Field errors”Cause. Ordinary body validation: a missing from, a malformed address, no
content, more than 50 recipients across to/cc/bcc, an over-long
filename. error.details lists every offending field as
{ "field", "code" } — field is the dotted path (subject, to.1,
attachments.0.filename), and error.code mirrors the first entry.
Fix. Read error.details and correct the payload. The full set of caps is
in the limits reference. If you legitimately
need more than 50 recipients per message, send in batches — and see
the SES recipient cap for what
providers do with large recipient sets.
429 — slow down
Section titled “429 — slow down”daily_send_cap_exceeded
Section titled “daily_send_cap_exceeded”Cause. The Free tier’s daily send ceiling. Typed rate_limit_error rather
than a 402 on purpose: it’s a ceiling that resets, not a plan you’ve outgrown.
Fix. Wait — it resets at 00:00 UTC — or upgrade for a higher limit.
Rate limited
Section titled “Rate limited”rate_limited
Cause. Too many requests. The budgets are per key:
| Endpoint | Limit |
|---|---|
POST /v1/send |
600 requests/min per key |
POST /v1/send/batch |
6 requests/min per key (100 messages each, so the same 600 messages/min) |
Requests without a valid key fall back to a much tighter per-IP bucket, so a
malformed Authorization header can look like a rate-limit problem when it’s
really an auth problem.
Fix. Honor the Retry-After header and back off. If you’re sending in
bulk, POST /v1/send/batch moves 100 messages per request and is the cheaper
path against the same message budget.
Cookie consent
We run ads. Knowing whether they work needs your OK for one cookie each from Google and Reddit, used only to tie a signup back to the ad that brought it. Analytics here is cookieless either way, and declining changes nothing else.
Change your mind any time with "Cookie choices" in the footer. Details in the privacy policy.