Skip to main content
A send is asynchronous. The API returns a requestId, and the message moves through a status lifecycle you track in the logs.

How it works

The status lifecycle

A message’s status only advances, never backward. Two independent systems drive it. Provider confirmation produces DELIVERED. Courier tracking produces OPENED and CLICKED, so engagement can arrive before or without a delivery confirmation. Open a message in the to see its provider response.

Send API responses

A successful send returns a requestId you use to track the message:
A failed request returns an HTTP error with a message and a type: The most common 400 on /send is Either 'content' or 'template' must be defined. Send one, not both. Next most common are a missing to, or a list_id/audience_id that doesn’t exist. A 413 means the request exceeded the 6 MB payload limit. Host attachments externally and pass URLs in data rather than base64.

Limits

is not rate-limited by request count, so you can send at volume. Its request body caps at 6 MB, as does . Every other endpoint caps at 1 MB. A few management endpoints are rate-limited per workspace. Their responses carry X-RateLimit-Limit and X-RateLimit-Remaining so you can pace requests. Exceeding a limit returns 429 Too Many Requests: Endpoints not listed here aren’t request-count limited today.

Send limits

Send limits cap how many messages a user, topic, or tenant receives in a window. They are a separate feature from API rate limits. A message blocked by one shows as THROTTLED in the log instead of delivering. The Developer plan’s 10,000-sends-per-period cap produces the same THROTTLED status. It applies after the API returns 202, so the status shows up in the message log rather than the response. A THROTTLED message you didn’t set a limit for usually means the plan cap.

Why a message is UNROUTABLE

UNROUTABLE is one status with several causes. Open the message in and read its reason: The timeline then shows one attempt per provider, each with its own reason for being skipped: To audit in bulk, list the failures over the API:

Debug other statuses

  • Stuck at SENT: delivery tracking isn’t configured for that channel, not a failure. See delivery tracking.
  • FILTERED: preferences, send conditions, or routing removed the message. Check the recipient’s .
  • UNDELIVERABLE: the provider rejected it. The timeline shows the provider’s error.
For real-time status without polling, receive the message:updated .

Delivery tracking is per provider

DELIVERED only appears when the provider tells Courier the message landed. That reporting is off until you set it up, per provider. Until then a healthy send stops at SENT. This is the most common reason a working integration looks broken. A provider reports in one of four ways: Providers absent from this table send no delivery confirmation, so their messages stop at SENT by design. Where both a webhook and polling are offered, prefer the webhook. It is real time and needs no extra provider permissions.

FAQ

Courier found no valid route: the recipient had no address for the routed channel, or no channel was configured for the send. Check the recipient’s profile and the send’s routing.
Usually not. SENT means Courier handed it to the provider and got no delivery confirmation back. That usually means delivery tracking isn’t set up for that channel.
Not by request count. You can send at volume. A few management endpoints are limited (see the table above). Send limits (per user, topic, or tenant) govern message volume and mark blocked messages THROTTLED.
The request exceeded the body-size cap: 6 MB on /send and /automations/invoke, 1 MB on every other endpoint. Host attachments externally and pass their URLs in data. Base64 inflates size by about a third.