Docs

Errors and limits.

What each error means, what usually causes it and what to do next. Then every limit the API enforces, the rules for webhook URLs, and when a retry is safe.

OverviewQuickstartConceptsErrors and limits

Error shape

Every error from /v0 is JSON with two fields: name, a short machine-readable code to branch on, and message, a sentence for people and logs.

HTTP/2 403
Content-Type: application/json

{
  "name": "forbidden",
  "message": "Recipient jordan@partnerco.com is not in this key's allow_recipients"
}

A bot should decide what to do from the status and name, and log message.

Status codes

StatusnameCommon causesWhat to do
401unauthorizedNo Authorization header, a header not in the form Bearer <key>, a mistyped key, or a revoked key.Check the key and the header. Do not retry in a loop: wrong keys count toward the lockout.
403forbiddenThe key lacks the scope for this call; an admin call made with a non-admin or inbox-limited key; a users call made without the owner's console session; a recipient outside allow_recipients on a key without an approval gate; a key approving its own parked send.Read message: it names the missing scope or the refused address. Use a key with the right scope, add the recipient to the allowlist, set approval: "outside", or have a different key or a person approve.
403csrf_failedConsole only: a signed-in browser write without the right Origin or X-Emlbot-CSRF header.Reload the console. API keys never see this.
404not_foundA wrong path or method (No such endpoint); an inbox, message, thread, attachment, key, webhook, user or approval that does not exist; an inbox outside an inbox-limited key's list; an id that was not URL-encoded.Check the path and that ids are URL-encoded. For a limited key, check its inbox_ids with GET /me.
405method_not_allowedAn OPTIONS request, usually a browser CORS preflight.Call the API from your server, not from a web page. There is no CORS.
409already_existsCreating an inbox whose address is taken (including a deleted inbox's address); inviting a user who already exists.Pick another username, or create inboxes with a client_id so a retry returns the existing inbox.
409conflictApproving or rejecting an approval that was already decided; approving after the inbox was deleted or the requesting key revoked; demoting or disabling the last owner.Refresh the item. For approvals, check its status with GET /approvals.
413payload_too_largeA request body over 30 MB. Base64 attachments are the usual reason: they are about a third larger than the files.Send fewer or smaller attachments. Attachments are capped at 25 MB per message anyway.
422validation_errorA body that is not a JSON object; a missing or malformed field; a value over a limit; a bad limit, after or page_token.Fix the request. message names the field and the rule. Retrying unchanged gives the same answer.
429rate_limitedTwo different causes; see below.Wait for Retry-After seconds before trying again.
500internal_errorSomething failed on our side. The message is always Internal error; details go to our logs, never to the response.Retry reads with backoff. For sends, check before retrying (see retries). Tell info@emlbot.com if it persists.
502send_failedThe outbound email provider refused the message. Nothing was sent and the send was not counted against the daily cap.Retry with backoff. If it keeps failing for the same message, check the recipients and attachments.

The two kinds of 429

Both use name: "rate_limited" and a Retry-After header in seconds. Tell them apart by the call and the headers:

  • Daily send cap spent. Only on send, reply and approve. The message says Daily send cap of 100 reached for this key (with your cap), and the response also carries X-RateLimit-Limit, X-RateLimit-Remaining: 0 and X-RateLimit-Reset. The cap resets at midnight UTC. Queue the send until then, or raise the cap with a new key if the bot genuinely needs more.
  • Too many failed sign-ins from your IP address. Any call. After 20 requests with a wrong key from one IP address within 10 minutes, that address is locked out, even for valid keys, until the oldest failure is more than 10 minutes old. Retry-After says how long. Find the process using a bad or revoked key and stop it. Successful calls and calls with no key at all do not count.
Shared IP addressesThe lockout is per IP address, so machines behind the same NAT or office connection share it. One misconfigured job can lock out its neighbours for up to 10 minutes.

Limits

Requests

WhatLimitOver the limit
JSON request body30 MB413 payload_too_large
Failed authentications per IP address20 per 10 minutes429 rate_limited with Retry-After
Page size (limit) for inboxes, messages, threads, approvals, auditDefault 50, maximum 500Larger values are lowered to 500
Page size for eventsDefault 100, maximum 1000Larger values are lowered to 1000

There is no per-key request rate limit today beyond these. Please poll at a sensible interval; a webhook removes the need to poll at all.

Sending

WhatLimit
Recipients per send50 in total across to, cc and bcc
reply_to addresses10
Attachments20 per message, 25 MB in total (decoded); file names up to 255 characters
Subject500 characters
Custom headers20 per message; names up to 64 characters from A-Z a-z 0-9 -; values up to 998 characters
Daily sends per keydaily_send_cap: 1 to 100,000, default 200, per UTC day
AddressesA plain address or Name <address>. Names containing @ , ; " must be fully quoted. No control characters.

Custom headers cannot override how a message is routed or authenticated. These names are reserved, and a custom header using one is left out of the message: From, To, Cc, Bcc, Subject, Message-ID, Date, MIME-Version, Reply-To, In-Reply-To, References, Sender, Return-Path, Received, DKIM-Signature, Authentication-Results, List-Unsubscribe-Post, anything starting with Content-, and anything starting with X-SES-.

Receiving

WhatLimit
Inbound message size25 MB, including attachments. Larger mail is refused during SMTP delivery, so the sending server reports the failure to the sender.
Unknown addressMail to an address with no inbox is refused with "Unknown mailbox".

Names and labels

WhatRule
Inbox username1 to 64 characters from a-z 0-9 . _ -, starting with a letter or digit, no ..
Inbox display_nameUp to 100 characters
Inbox client_idUp to 128 characters
Labels1 to 64 characters from a-z 0-9 . _ : -; up to 20 in add_labels, 20 in remove_labels and 20 in labels on a send
Key nameUp to 100 characters
Key inbox_idsUp to 100 inboxes
Key allow_recipientsUp to 200 entries, each up to 254 characters

Webhooks

WhatLimit
Response time10 seconds for a 2xx, or the attempt fails
Retries8, over about 28 hours (5 s, 30 s, 5 min, 30 min, 2 h, 5 h, 10 h, 10 h)
Consecutive failures before the webhook is disabled50
URL length2048 characters

Webhook destination rules

Webhook URLs are checked when you create the webhook and again before every delivery, so EMLbot can never be pointed at a private network. A URL is refused with 422 validation_error unless all of these hold:

  • It uses https: on the default port, 443.
  • It contains no username or password.
  • Its host is a name, not an IP address in any form (dotted, decimal, hex, octal, short or IPv6). This rules out loopback, private ranges, link-local and cloud metadata addresses.
  • Its host has at least one dot, is not localhost, metadata or instance-data, and does not end in .local, .localhost, .internal, .localdomain, .lan, .home.arpa, .intranet or .corp.

At delivery time, redirects are not followed (a 3xx counts as a failure), the request times out after 10 seconds, and your response body is discarded unread. Point the webhook at the final URL, and answer with a plain 2xx.

Retry guidance

Safe to retry as they are

  • Every GET.
  • PATCH on labels: adding a label twice or removing one that is gone changes nothing.
  • POST /inboxes with a client_id.
  • DELETE on keys (revoking twice keeps the first time).
  • Approve and reject: a second attempt answers 409 conflict instead of acting twice.

Retry with care

  • Send and reply. There is no idempotency key for sends. If a send times out on your side, it may have gone through. Before sending again, list the inbox with labels=sent (or read the event log for message.sent) and check whether it is there.
  • Creating keys and webhooks. A retry after a lost response creates a second one. List them, keep one, and revoke or delete the extra.

When to back off and when to stop

  • 429: wait at least Retry-After seconds.
  • 500 and 502, and network errors: retry with exponential backoff and jitter, for example 1 s, 2 s, 4 s, 8 s, then give up and alert.
  • Other 4xx errors: do not retry unchanged. The same request gets the same answer. Fix the key, the path or the body first.

Read next