API reference
Errors
All API errors use a consistent JSON shape. Every error has a machine-readable type you can switch on and a human-readable message safe to display.
Why use this
For example
When you're sending too fast you get a 429 with a Retry-After value — so your code can wait that many seconds and retry, instead of guessing or giving up.
Response shape
// Every error response body has this shape:
{
"error": {
"type": "rate_limited",
"message": "Rate limit exceeded. Retry after 2 seconds."
}
}
// On 429 rate_limited, the response also includes:
// Retry-After: 2 (seconds until the rate bucket refills)Handling errors with the SDK
The SDK throws a YourMailError for any non-2xx response. It exposes status, type, message, and — when the type is rate_limited — retryAfter (seconds, from the Retry-After response header).
import { YourMail, YourMailError } from "yourmail";
const yourmail = new YourMail(process.env.YOURMAIL_API_KEY!, {
baseUrl: process.env.YOURMAIL_BASE_URL!,
});
try {
await yourmail.send({ ... });
} catch (err) {
if (err instanceof YourMailError) {
console.error(err.status); // HTTP status, e.g. 429
console.error(err.type); // machine-readable type, e.g. "rate_limited"
console.error(err.message); // human-readable description
// Only present on rate_limited (from the Retry-After response header)
if (err.retryAfter !== undefined) {
console.log(`Retry after ${err.retryAfter}s`);
}
}
}Error reference
All 10 error types and their typical HTTP status codes:
validation_error(400, 413, 422)Missing or invalid field; body, attachment, or total size too large; batch too large.
What to do — Fix the request. Check the message for the specific field or limit.
authentication_error(401, 403)401: API key is absent, malformed, or not recognised. 403: the key is valid but send-only and this endpoint needs full access, or sending is paused for the account.
What to do — For a 401, check you're sending Authorization: Bearer <key> with a valid key from the dashboard. For a 403, use a full-access key — or, if sending is paused, see deliverability_blocked below.
domain_errorThe from address uses a domain that is not verified or is not owned by this account — or the domain is verified but suspended because your plan allows fewer domains than your account holds.
What to do — Verify the domain in the dashboard before sending from it. If it's suspended by plan, upgrade or reactivate it in the dashboard (swapping it for another domain on your current plan).
quota_exceededDaily or monthly send quota is exhausted.
What to do — Wait until the quota period resets. retryAfter is not set — retry after midnight UTC or the monthly reset.
rate_limitedPer-second send rate exceeded, or a daily cap was hit. The rate bucket is metered per message, not per request, so a batch of 50 spends 50 tokens: Free is 2 messages/sec with a burst of 10, all paid tiers 14/sec (the SES account rate) with a burst of 50. This is a reputation guard, not a billing limit — it's separate from your monthly quota, which returns quota_exceeded. Two daily caps also return this type: the 20/day test-mode cap, and the account-wide SES daily capacity guard.
What to do — Read the Retry-After response header (or err.retryAfter in the SDK) and wait that many seconds before retrying. The SDK retries a per-second rate limit for you by default; a daily cap answers with the seconds until UTC midnight, which it surfaces rather than waiting out.
deliverability_blockedYour account's sending has been paused by the deliverability circuit breaker due to a high bounce or complaint rate.
What to do — Check the Deliverability page in the dashboard. Reduce bounces and complaints; sending resumes automatically when rates recover. Contact support if you need urgent assistance.
sandbox_recipientYou sent from the sandbox sender (no-reply@yourmail.dev, which puts you in test mode) to a recipient other than your signup email or an AWS SES simulator address. This is narrower than it sounds: the server normally rewrites a disallowed recipient to your signup inbox rather than rejecting it, so this specific error only fires when your account has no signup email on file to rewrite to. The separate 20/day test-mode cap is a 429 rate_limited, not this error.
What to do — Add a signup email to your account (or use the dashboard's "Send test email" button, which already has one), or send to an SES simulator address instead. To go live, send from an address on your verified domain instead of the sandbox sender.
suppressed_recipientThe recipient is on this account's suppression list (prior hard bounce or complaint from your account).
What to do — Remove the address from your sends. Check the suppressions section in the dashboard.
not_foundNo email with the given ID exists in this account.
What to do — Check the ID. IDs are account-scoped — a valid ID from another account returns 404.
internal_errorUnexpected server error.
What to do — Retry with exponential backoff. If it persists, contact support.
Retry-After header — only present on rate_limited responses. The value is the number of seconds until the rate-limit bucket refills. The SDK surfaces this as err.retryAfter (a number). It is not set for quota_exceeded.