API Reference
Error Codes
Check the HTTP status and response body together. When present, branch on the machine-readable error field; log or surface detail for humans.
Error response shape
Sending refusals include success, error and detail:
{
"success": false,
"error": "domain_not_verified",
"detail": "Domain 'acme.com' is not yet verified. Add both TXT ownership records, then press Verify all records at https://sendfleet.net/dashboard/domains/"
}HTTP 400: Bad Request
Field validation failures return a map of field names to messages. A malformed JSON body can instead return a detail message. Check the status code and body before reaching for data.error.
{
"to": ["Enter a valid email address."],
"attachments": ["File type '.exe' is not permitted for security reasons."]
}Fix the listed fields. Do not retry without changing the request.
HTTP 403: API key rejected
The X-API-Key header is missing, or the key is invalid, revoked, or past its expiry date. This response carries no error code, so a bare 403 with no error field means the key was the problem rather than your account configuration.
{
"detail": "Authentication credentials were not provided."
}| Cause | Fix |
|---|---|
| Header missing entirely | Add X-API-Key: YOUR_KEY to every request. |
| Wrong header name | It must be X-API-Key. HTTP header names are case-insensitive, so x-api-key is fine, but Authorization is not. |
| Key revoked or deleted | Generate a new key in Dashboard → API Keys. |
| Key expired | Generate a replacement — an expiry date cannot be extended after the fact. |
| Key truncated | Check you copied the whole key including the part after the dot. The prefix alone will not authenticate. |
HTTP 403: Forbidden
The key is valid, but the account or domain configuration does not permit this send. Resolve the stated cause before retrying. A temporary pause or a plan change still being applied may clear without a configuration change.
| error | Cause | Fix |
|---|---|---|
| unauthorized | The account email is unverified, the subscription required for this sending mode is inactive, sending is unavailable, your AWS account is not connected yet, SendFleet can no longer access your AWS account (the role was deleted or its trust policy changed), a SendFleet test address was sent from a BYOC domain, or a Free plan Shared smoke test is addressed to anyone but your own verified address or breaks its reply_to, from_name or subject rules. | Verify your account email, check your subscription on the billing page, and connect or reconnect your AWS account in Dashboard → AWS. To test a BYOC integration, send to the Amazon SES mailbox simulator or to a real address you control; see Testing. For a smoke test, send it to yourself; the rules are on the Shared SES page. |
| recipient_limit_exceeded | The recipient limits depend on the sending mode. BYOC supports up to 50 recipients across to, cc and bcc. Shared SES supports exactly one address in to and up to 5 recipients in total. Nothing is sent or counted against your monthly allowance when this error is returned. | On Shared SES, put one address in to and move the rest to cc or bcc. To address more people in one message, send through your own AWS SES account — there the limit really is 50 and the quota is yours. |
| domain_not_registered | The domain in from_email has not been added to your account. | Go to Dashboard → Domains → Add domain, then complete DNS verification. |
| domain_not_verified | The domain is registered, but ownership has not been confirmed — one of the two TXT records is missing or has not propagated. | Add both ownership TXT records at your DNS provider, allow time to propagate, then choose Verify in the domain detail page. |
| dkim_not_verified | Ownership is verified but the three DKIM CNAME records are not confirmed. | Add all three CNAME records from the domain detail page, wait for propagation, then choose Verify DKIM. |
| domain_mode_mismatch | The domain's sending mode (BYOC or shared) does not match your current plan. | Change plan to match the domain, or add a domain in the mode your plan supports. |
| domain_not_allowed | The API key is restricted to specific domains and the sender domain is not among them. | Use a from_email on an allowed domain, or create a key with the right domain scope. |
| sending_suspended | Account sending is temporarily paused or suspended — or, on BYOC, paused by the reputation protection you chose. Briefly after a plan change, it can also mean Shared sending is still being re-enabled; the detail then says so. | A temporary pause lifts automatically and leaves your subscription active. A BYOC protection pause lifts when you press Resume in Dashboard → AWS. If sending is being re-enabled after a plan change, retry in a minute. A suspension ends your subscription and requires a support review for reinstatement. See Suppression & Suspension. |
HTTP 422: Unprocessable Entity
On Shared SES, the outcome arrives as an email.suppressed webhook and a suppressed status in your email logs. Treat that the same way you would have treated a 422: mark the address undeliverable on your side and stop sending to it. See Webhooks.
HTTP 429: Too Many Requests
Two different limits produce a 429, and they need opposite responses:
| error | Cause | Retry strategy |
|---|---|---|
| usage_limit_exceeded | Monthly email quota exhausted: 3,000 on Free BYOC (BYOC sends and Shared smoke tests together), on BYOC the monthly cap you set (default 100,000, up to 1,000,000), 5,000 on Managed Starter, 25,000 on Managed Growth, 100,000 on Managed Pro. Every recipient in to, cc and bcc counts as one send, so a send with more recipients than you have sends left is refused whole. | Do not retry. Upgrade in Dashboard → Billing, raise your BYOC monthly cap in Dashboard → AWS, or wait for the quota to reset at the start of the next calendar month (UTC). On BYOC, the largest plan, email support@sendfleet.net for a higher limit. |
| (no error field) | Per-key rate limit exceeded — 60 requests per minute. | Retry with exponential backoff starting at one second, and spread bursts over several seconds. |
HTTP 500: Internal Server Error
An unexpected error on our side, returned with "error": "internal_error". These are rare. Retry once after a short backoff; if it persists, contact support with the approximate timestamp and the request you sent.
HTTP 502: Bad Gateway
| error | Cause | Retry strategy |
|---|---|---|
| queue_error | The message could not be accepted for delivery. Nothing was sent or counted against your quota. | Safe to retry. Back off exponentially: 1s, 2s, 4s, 8s. After four attempts, treat it as a failure and alert. |
| attachment_upload_failed | Your attachments could not be staged for delivery. Nothing was sent. | Safe to retry. If it repeats, check you are within the 25 MB per-file and 37 MB per-message limits. |
HTTP 503: Service Unavailable
| error | Cause | Fix |
|---|---|---|
| byoc_not_configured | A BYOC configuration problem on our side, not yours. Your IAM role and domain settings are fine. | Contact support. Do not retry in a loop, this will not clear on its own. |
Error handling example
import time
import requests
# Fix-the-request errors. Retrying these unchanged will always fail again.
TERMINAL = {400, 403, 422, 503}
# Transient errors. Safe to retry with backoff.
RETRYABLE = {429, 500, 502}
def send_email(payload, retries=4):
for attempt in range(retries):
resp = requests.post(
"https://api.sendfleet.net/v1/send",
headers={"X-API-Key": "YOUR_KEY"},
json=payload,
timeout=10,
)
if resp.status_code == 200:
return resp.json()
data = resp.json()
error_code = data.get("error")
# A monthly quota 429 is terminal - no amount of waiting helps.
if error_code == "usage_limit_exceeded":
raise RuntimeError("Monthly quota exhausted - upgrade the plan.")
if resp.status_code in TERMINAL:
raise ValueError(f"[{error_code}] {data.get('detail')}")
if resp.status_code in RETRYABLE:
time.sleep(2 ** attempt)
continue
raise RuntimeError(f"Unexpected status {resp.status_code}")
raise RuntimeError("Max retries exceeded")Frequently asked questions
The error cases developers hit most often, and exactly which of them are safe to retry.
Does the SendFleet API still return a 422 recipient_suppressed error?
What causes a 403 sending_suspended error?
Is a 502 queue_error from SendFleet safe to retry?
What does 403 domain_not_verified mean?
What is the SendFleet API rate limit error?
Retry strategy reference
| HTTP status | Retryable? | Strategy |
|---|---|---|
| 400 | No | Fix the request payload. |
403 (no error field) | No | Fix the API key. |
403 (with an error field) | No | Fix the account or domain configuration. |
| 429 (quota) | No | Upgrade the plan or wait for the monthly reset. |
| 429 (rate limit) | Yes | Exponential backoff starting at one second. |
| 500 | Yes | One retry after 2–5 seconds, then alert. |
| 502 | Yes | Exponential backoff, maximum four attempts. |
| 503 | No | Contact support. |