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:

Error body
{
  "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.

400: Validation errors
{
  "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.

403: No usable credentials
{
  "detail": "Authentication credentials were not provided."
}
Common causes of a 403
CauseFix
Header missing entirelyAdd X-API-Key: YOUR_KEY to every request.
Wrong header nameIt must be X-API-Key. HTTP header names are case-insensitive, so x-api-key is fine, but Authorization is not.
Key revoked or deletedGenerate a new key in Dashboard → API Keys.
Key expiredGenerate a replacement — an expiry date cannot be extended after the fact.
Key truncatedCheck 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.

403 error codes
errorCauseFix
unauthorizedThe 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_exceededThe 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_registeredThe domain in from_email has not been added to your account.Go to Dashboard → Domains → Add domain, then complete DNS verification.
domain_not_verifiedThe 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_verifiedOwnership 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_mismatchThe 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_allowedThe 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_suspendedAccount 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:

The two kinds of 429
errorCauseRetry strategy
usage_limit_exceededMonthly 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

502 error codes
errorCauseRetry strategy
queue_errorThe 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_failedYour 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

503 error codes
errorCauseFix
byoc_not_configuredA 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

Python
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?
No. Suppression is enforced at delivery time rather than at request time. A send to a suppressed address is accepted with HTTP 200 and then resolves to a suppressed status, reported through the email.suppressed webhook and your email logs. The recipient_suppressed code stays reserved for contract stability but is no longer returned by the send endpoint.
What causes a 403 sending_suspended error?
Sending is temporarily paused or suspended. A temporary pause normally lasts 24 hours, lifts automatically and leaves your subscription active. A suspension ends the subscription and requires a support review for reinstatement; email support@sendfleet.net with the cause and corrective action. On BYOC, the same code can mean the reputation protection you chose has paused sending to keep your AWS account in good standing; it resumes when you press Resume in Dashboard → AWS. Briefly after a plan change, the same code can mean Shared sending is still being re-enabled. Read the detail and account notice to identify the appropriate action.
Is a 502 queue_error from SendFleet safe to retry?
Yes. A 502 queue_error means the message could not be accepted for delivery. Nothing was sent or counted against your quota. Use exponential backoff: 1s, 2s, 4s, 8s. After four attempts, treat it as a failure and alert. A timeout or missing response is different: the message may already have been accepted, so do not retry automatically without checking delivery state.
What does 403 domain_not_verified mean?
The domain in your from_email is registered but ownership has not been confirmed. Add both TXT ownership records (_amazonses and _sendfleet) at your DNS provider, wait for propagation, which can take up to 48 hours, then click Verify on the domain detail page. A domain only becomes send-ready once both the ownership and the DKIM checks pass.
What is the SendFleet API rate limit error?
HTTP 429 with no error field is the per-key rate limit of 60 requests per minute, and is worth retrying with exponential backoff. HTTP 429 with the error code usage_limit_exceeded means your monthly email quota, or on BYOC the monthly cap you set, is exhausted; retrying will not help, so upgrade your plan, raise your BYOC cap in Dashboard → AWS, or wait for the reset at the start of the next calendar month in UTC.

Retry strategy reference

Which statuses are worth retrying
HTTP statusRetryable?Strategy
400NoFix the request payload.
403 (no error field)NoFix the API key.
403 (with an error field)NoFix the account or domain configuration.
429 (quota)NoUpgrade the plan or wait for the monthly reset.
429 (rate limit)YesExponential backoff starting at one second.
500YesOne retry after 2–5 seconds, then alert.
502YesExponential backoff, maximum four attempts.
503NoContact support.