Operations · Errors & rate limits

Errors & rate limits

The error envelope and codes, per-key rate limits, the daily sending quota with Retry-After, and the SMTP replies that go with them.

Error envelope

Every 4xx and 5xx answer of the API has the same shape:

400 Bad Requestapplication/json
{
  "error": {
    "type": "validation_error",
    "code": "invalid_request",
    "message": "Email address is malformed",
    "param": "to"
  }
}
FieldTypeDescription
errorrequiredobject
error.typerequiredvalidation_error | authentication_error | authorization_error | not_found | rate_limit | conflict | internal_error
error.coderequiredstringStable, machine-readable.
error.messagerequiredstringHuman-readable, may change.
error.paramstringThe offending input field, when known.
error.detailsobjectMore about some errors: limit, used and resetAt for daily_quota_exceeded.

type is one of validation_error, authentication_error, authorization_error, not_found, rate_limit, conflict, internal_error. Branch on code: it is stable, while message is for people and may change.

Error codes

StatusCodeWhen
400
invalid_request
The body or query failed validation (unknown query parameters included).
400
invalid_json
The body is not JSON.
401
missing_authorization
invalid_api_key_format
invalid_api_key
api_key_revoked
api_key_paused
Authentication failed.
403
insufficient_scope
The key lacks the scope the endpoint needs.
403
project_paused
The project is paused; nothing new is accepted.
403
sender_domain_restricted
The key is restricted to another sender domain.
403
sender_domain_not_registered
sender_domain_not_verified
The from domain cannot send yet.
403
domain_reserved
domain_limit_reached
postfly's own domain, or the project's domain limit.
403
global_scope_forbidden
global_suppression
suppression_limit_reached
Suppression rules.
404
message_not_found
domain_not_found
suppression_not_found
Not in this project.
404
route_not_found
No such /v1 endpoint.
405
method_not_allowed
Wrong method; the Allow header lists the right ones.
409
recipient_suppressed
The recipient is on the project or the global suppression list.
409
domain_taken
The domain is registered already, in this or another project.
429
rate_limit_exceeded
The key used up its rate-limit bucket.
429
daily_quota_exceeded
The project reached today's sending cap.
500
internal_error
Our fault.

Which endpoint returns which is in the API reference.

Rate limits

Limits count per API key, in fixed one-minute windows. Each endpoint counts against one bucket:

BucketLimit
default120 requests / minute
dns20 requests / minute

The dns bucket is for the endpoints that make DNS lookups for you. Every authenticated response carries the state of its bucket; over the limit the answer is 429 rate_limit_exceeded with Retry-After:

FieldTypeDescription
X-RateLimit-LimitintegerRequests allowed per minute in this endpoint's bucket, for this key.
X-RateLimit-RemainingintegerRequests left in the current window.
X-RateLimit-ResetintegerUnix time (seconds) when the current window ends.
Retry-AfterintegerSeconds until the window resets, or until the daily quota does.
retry
const res = await fetch(url, init);
if (res.status === 429) {
  const wait = Number(res.headers.get('Retry-After') ?? 1);
  await new Promise((r) => setTimeout(r, wait * 1000));
  // …then retry the request
}

Other bounds: at most 50 sender domains and 100,000 suppressions per project.

Daily sending quota

A project on a warmup schedule or with a daily cap (see Warmup & daily caps) gets 429 daily_quota_exceeded from POST /v1/emails once today's messages reach the cap. Retry-After counts the seconds to midnight Europe/Bratislava time, when the count starts over, and error.details carries limit, used and resetAt. Retrying sooner doesn't help; queue the message on your side.

SMTP replies

ReplyWhenRetry
451At MAIL FROM: the key's rate limit is used up. Also any temporary server fault.yes, later
452At RCPT TO: past the key's rate limit. After DATA: past the project's daily quota.yes, later
550Validation, sender domain or suppression problem; too many recipients.no
550 5.7.1At MAIL FROM: the project is paused.no, until resumed
A mail server relaying to postfly (Postfix, Exim) retries 4xx replies by itself. A library that talks SMTP from your app, such as Nodemailer, reports them as errors: retry those later yourself. A 5xx is final. More in Replies & limits.