Errors
Every error response has the same JSON shape, identical to Resend’s:
{ "statusCode": 422, "name": "validation_error", "message": "`from`: Invalid email address"}Branch on name, which is stable. message is for humans and may change. In the Node SDK, errors come back as { data: null, error } with the same three fields; network failures use name: "application_error" and statusCode: null.
import { SpacePost } from "@spacepost/node";
const spacepost = new SpacePost();
const { data, error } = await spacepost.emails.send({ from: "Acme <hello@acme.com>", to: "user@example.com", subject: "Hello", html: "<p>Hello</p>",});
if (error) { switch (error.name) { case "rate_limit_exceeded": // wait about a second, then retry with the same idempotency key break; case "daily_quota_exceeded": case "monthly_quota_exceeded": // alert someone: you hit a limit break; case "validation_error": case "invalid_from_address": // a bug in your request: do not retry throw new Error(`${error.name}: ${error.message}`); default: console.error(error.statusCode, error.name, error.message); }} else { console.log(data.id);}Error table
Section titled “Error table”| Status | name |
Meaning and fix |
|---|---|---|
| 400 | invalid_idempotency_key |
The Idempotency-Key header is not 1 to 256 characters. |
| 401 | missing_api_key |
No Authorization: Bearer sp_live_... header. |
| 401 | restricted_api_key |
The key may not do this: a sending_access key used on a non-sending endpoint, or a domain-restricted key sending from another domain. |
| 403 | invalid_api_key |
The key is unknown, revoked, or not a SpacePost key (for example a Resend re_ key). |
| 403 | invalid_from_address |
The from domain is not a verified domain of your team. |
| 403 | sending_paused |
Your team is paused, usually after high bounce or complaint rates, or (when you pass tenant_id or send from a tenant-owned domain) that tenant is paused. See Automatic pausing. |
| 403 | validation_error |
A request that is valid but not allowed: using test.spacepost.dev to mail recipients other than your team or test addresses, or exceeding your plan’s domain limit. |
| 403 | invalid_access |
Dashboard session without access to the requested team, or POST /tenants/{ref}/unpause on a tenant that was paused for its bounce or complaint rate by a team that is not trusted. Contact support. |
| 404 | not_found |
The resource does not exist (or belongs to another team), or the endpoint does not exist. |
| 405 | method_not_allowed |
The HTTP method is not supported on this path. |
| 409 | concurrent_idempotent_requests |
The same idempotency key is still being processed. Retry shortly. |
| 409 | invalid_idempotent_request |
The idempotency key was already used with a different request body. |
| 422 | validation_error |
A field is missing or invalid. The message names it. Also: “Only scheduled emails can be canceled”, “Domain already registered”, invalid JSON, a tenant_id that conflicts with the domain’s tenant, a duplicate tenant external_id, deleting a tenant that still has domains. |
| 422 | invalid_parameter |
A query or path parameter is invalid. |
| 429 | rate_limit_exceeded |
Over your requests-per-second limit. Wait for retry-after seconds. |
| 429 | daily_quota_exceeded |
The daily sending limit was reached (new-account sandbox, your trust-level cap, or a tenant’s daily_limit). |
| 429 | monthly_quota_exceeded |
The Free plan’s monthly limit was reached. Upgrade or wait. |
| 451 | security_error |
The email was blocked by content filtering (executable attachment, phishing or spam indicators). Change the content; do not retry as is. |
| 500 | application_error |
Unexpected error from the API. Retry with backoff. |
| 500 | internal_server_error |
“An unexpected error occurred, please try again later”. Retry with backoff; contact support if persistent. |
Retrying
Section titled “Retrying”| Retry? | Errors |
|---|---|
Yes, after the retry-after seconds |
rate_limit_exceeded |
Yes, with exponential backoff and the same Idempotency-Key |
500s, network errors, concurrent_idempotent_requests |
| No, fix the request | 400, 401, 403, 404, 422 |
| No, wait for the limit to reset or upgrade | daily_quota_exceeded, monthly_quota_exceeded |
| No, ask support (or unpause a tenant you paused yourself) | sending_paused, security_error |
Webhooks and asynchronous failures
Section titled “Webhooks and asynchronous failures”Errors above are returned when you make a request. Problems that happen after an email is accepted (bounces, rejections) arrive as webhook events and in the email’s last_event.
Over SMTP, the same conditions are returned as SMTP reply codes: quota errors 452, rate limiting 421, invalid sender 553, other rejections 550, temporary faults 451.