MepMail Docs

Errors

Every error MepMail returns — names, status codes, what they mean, and what is safe to retry.

Every API error uses the same JSON shape, matching the Resend wire protocol:

{"statusCode": 422, "name": "validation_error", "message": "…"}

name is a stable machine-readable code (and the key the SDKs branch on); message is human-readable and may change. Branch on statusCode and name, never on message.

Error codes

Every code this deployment emits, by status. The same conditions reach the MCP server as tool results carrying this body, and the SMTP relay as its own protocol status — the names are the API's.

nameHTTPWhat it meansWhat to do
invalid_parameter400A parameter value is not allowed (e.g. editing a sent broadcast)Fix the request
invalid_payload400The request body could not be parsedSend valid JSON
missing_api_key401No Authorization headerSend Authorization: Bearer ms_...
invalid_api_key401The key is unknown or was revokedRe-read the key once (rotation?); if it still fails, stop — do not retry
restricted_api_key403The key is valid but not allowed for this resource (permission level, or a sender domain outside the key's scope)Use a key whose scope covers the call, or ask the operator for one
plan_limit_reached403A plan cap was hit (contacts, domains, teams)Free space within the plan's limits, or upgrade
forbidden403The caller's team role does not allow this actionCheck the role behind the credential
team_suspended403The instance operator suspended the team. Keys still authenticate so the caller learns why; nothing leavesStop sending and talk to the instance operator — retrying only repeats it
sending_paused403The team's own hard-bounce or complaint rate crossed the sending guardrailFix the list before sending again — see sending pauses
broadcasts_paused403Broadcast sending is paused for this team by the operator, or in the sender's SES region while the platform's rate recoversTransactional email is unaffected; retry the broadcast later
not_found404The resource does not exist (or belongs to another team)Verify the id and the endpoint
conflict409The resource is in a conflicting stateRead the current state, then retry the operation
invalid_idempotent_request409The Idempotency-Key was already used with a different payloadTerminal — send the new payload under a new key, or resend the original body with the original key
concurrent_idempotent_requests409A request with the same idempotency key is still in flightWait, then retry with the same key
payload_too_large413The request body exceeds the deployment's 25 MiB ceiling — attachments travel inline (base64), so they are part of the bodyShrink the message, or send the file as a link instead
validation_error422The payload failed schema validation — or a send was refused by a rule no field names: an unverified from domain, a from that is not a single address, the onboarding sender reaching outside the team, a suppressed batch item, attachments over the plan's ceiling (1 MB on Free and Starter, 5 MB on Pro, 10 MB on Scale), a sender name or subject disguised with look-alike lettersRead message; fix what it names
all_recipients_suppressed422Every recipient is on the suppression listCheck suppressions before resending
broadcast_too_large422The audience needs more sending capacity than the deployment's horizon allows (over 24 days)Split it into smaller segments, or ask the operator for more capacity
rate_limit_exceeded429Too many requests in a short windowBack off with exponential delay — see rate limits
daily_quota_exceeded429The plan's daily send allowance is spent and the queued backlog is full (Free, Starter)Retry after the UTC day rolls over; a higher plan buys headroom
monthly_quota_exceeded429The plan's included monthly volume is exhausted and overage is off (Pro, Scale) — or its overage hard cap was reachedWait for the period to renew, turn on overage in Billing, or upgrade
internal_server_error500A bug on our sideRetry with backoff; report it if it persists

Sending pauses and holds

Three refusals mean "not now, and not because of your request". All three are 403 and deterministic — no retry loop clears them:

  • sending_paused — the team's own hard-bounce or complaint rate over the trailing window is at or above the guardrail, so a send is refused before it reaches the queue. The message names the metric, the rate, the window and the limit. Clean the list and slow down until the rate falls back under the line; deliverability insights is the report that says which addresses caused it.
  • broadcasts_paused — broadcast sending only (POST /broadcasts/{id}/send). Either the instance operator paused broadcasts for this team, or the platform's aggregate bounce/complaint rate in the sender's SES region is recovering and that whole region waits. Transactional email is unaffected, and it clears on its own.
  • team_suspended — the instance operator suspended the team. Nothing sends until it is reinstated; only the operator can lift it.

Two protections run before mail reaches SES, on MepMail Cloud:

  • A disguised sender is refused with 422 validation_error: a sender name or subject that mixes look-alike letters from different alphabets in one word (a Cyrillic а inside a Latin word), spells a word wholly in look-alike letters beside Latin ones, or hides invisible or direction-override characters. Write it in one alphabet and send again.
  • A review hold keeps mail instead of sending it. A team in its first 30 days whose sender name or subject reads as a bank, a carrier, a tax office or an account-security notice, or whose payment was blocked by Stripe's fraud screening, is held for a short review. The API still answers 200 with the email id; the mail waits as queued and goes out when the review ends, and the dashboard shows the hold. Nothing is lost.

Retrying

  • 4xx: retry only 409 concurrent_idempotent_requests (same idempotency key) and 429 (with backoff). Everything else in the 4xx range is deterministic — fix the request or the credential. 403 sending_paused, 403 broadcasts_paused and 403 team_suspended are deterministic too: waiting and retrying in a loop will not clear them.
  • 5xx: retry with exponential backoff and jitter. Sends carrying an idempotency key are safe to retry; without one, a retried send can deliver twice.

Idempotency

Sending endpoints accept an Idempotency-Key header. Two requests with the same key run once; while the first is still executing, the second answers 409 concurrent_idempotent_requests. Reuse the same key when retrying after a timeout — that is what makes a retried send safe. Reusing a key with a different payload is 409 invalid_idempotent_request: give the new payload its own key instead of retrying.

Batch sends

POST /emails/batch accepts up to 100 emails. With the x-batch-validation: permissive header, invalid items are returned in errors by index while the valid subset is accepted; by default (strict), one invalid item rejects the whole batch. An over-cap array is a 422. A refusal that is not about one item — quota, a pause, the plan's contact cap — answers once for the whole call, in either validation mode.

MCP

The MCP server surfaces the same conditions: 429 rate_limit_exceeded on the endpoint when the account's call budget is exhausted, and tool-level failures as tool results with isError — read the text content for the code and message.

On this page