ScanMyPassKhatdocs
HomeSign in

Get started

  • Setup
  • DNS records

Reference

  • Sending mail
  • Receiving mail
  • Delivery & webhooks
  • Errors & limits
  • Security

For agents

  • llms.txt

Reference

Errors & limits

Every failure has the same shape, and the code is the part to write logic against. The message is written for a human and may change.

json
{
  "error": {
    "code": "DOMAIN_NOT_VERIFIED",
    "message": "example.com has not passed DNS verification. SPF and DKIM must both verify before it can send.",
    "details": {}
  }
}

Codes#

CodeHTTPWhat it means
INVALID_API_KEY401Key is unknown or revoked.
UNAUTHORIZED401Missing or malformed Authorization header.
FORBIDDEN403Authenticated, but not allowed to do this. An API key cannot reach dashboard-only endpoints.
ACCOUNT_SUSPENDED403Sending is blocked pending review.
DOMAIN_NOT_FOUND404That domain is not on this account.
DOMAIN_NOT_VERIFIED403The domain exists but SPF and DKIM have not both passed.
INVALID_FROM_ADDRESS422Malformed, or the domain restricts senders and this address is not on its list.
INVALID_RECIPIENT422A recipient address was rejected. details.recipients says which.
SUPPRESSED_RECIPIENT403One or more recipients are suppressed and will not be mailed.
TOO_MANY_RECIPIENTS422Over 50 across to, cc and bcc.
EMAIL_TOO_LARGE413Bodies plus decoded attachments exceed the size limit.
VALIDATION_ERROR422The body failed validation. details lists each field and what was wrong with it.
RATE_LIMIT_EXCEEDED429Too many HTTP requests. Nothing was created — safe to retry.
SENDING_LIMIT_EXCEEDED429Hourly or daily allowance spent. Safe to retry later.
EMAIL_QUEUE_FAILED503Accepted but could not be queued; the record was rolled back. Safe to retry.
INTERNAL_ERROR500Unexpected, and logged on our side. Do not retry a send — see below.

Which failures are safe to retry

Sending is not idempotent, so a blind retry can send the message twice. Retry only where nothing can have been created: no response at all, 429, or 503 EMAIL_QUEUE_FAILED.

A 500 is the one that catches people. The message may have been queued before the failure, so retrying can duplicate it — and a duplicate email is worse than a failed one. Surface it instead.

Reading a validation error#

details names the field, so you can point a user at the right input:

json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request body failed validation.",
    "details": [
      { "path": "email", "message": "Must be a valid email address" },
      { "path": "password", "message": "Password must be at least 12 characters" }
    ]
  }
}

Limits#

Two independent layers, and they fail with different codes so you can tell them apart.

HTTP requests#

Per IP, per minute. Exceeding it returns RATE_LIMIT_EXCEEDED. Credential endpoints allow far fewer attempts, per IP, per fifteen minutes — that limit exists to make password guessing impractical.

Every response carries the current state, so you can back off before being refused:

http
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 597
X-RateLimit-Reset: 47

Messages#

Counted per recipient, not per call

A message to fifty people spends fifty of your allowance, not one. This is the thing that surprises people when a bulk send stops halfway.

The allowance is enforced against your account, the sending domain and the API key simultaneously — whichever runs out first refuses the request with SENDING_LIMIT_EXCEEDED. A rejected request consumes nothing.

New accounts start lower for the first day. Beyond that the allowance rises as your sending stays clean, because a sending IP with no history gets filtered if it suddenly sends thousands of messages — see what to watch while you ramp.

Your current usage against both windows is on the Deliverability page.

NextSecurityHow your keys and data are kept separate from everyone else's.