Sending

Message lifecycle & status

Every message carries a status that tracks it from accepted to a terminal outcome. Read it on demand, or let webhooks tell you the moment it changes.

Each message Sendara accepts has a single status field that always reflects its most recent event. The status moves through a small, predictable sequence. Sendara queues the message and hands it to a provider. The provider delivers the message, and the recipient engages with it. The sequence normally ends at a terminalstate. This page is the reference for the full status set, which states are terminal, the caveats around open tracking, and the two ways to learn a message's status: poll it, or subscribe to webhooks.

status is a convenience snapshot. The event timeline is the source of truth, and it carries the full history with per-event detail. This page covers the status field and how to look it up. The Events & the message timeline page covers the event objects and their payload schemas.

The happy path

A transactional email that lands cleanly moves through this sequence. Sendara records an event at each step:

queuedsentdelivered openedclicked

Not every message visits every state. opened and clicked only appear when you enable tracking and the recipient engages. A message can settle on delivered indefinitely when nobody opens it. Any of the failure states below can interrupt the sequence at any point.

Status reference

A terminal status is the normal end of the sequence. A non-terminal status always changes again.

A terminal status is not locked. Sendara writes the status of the most recent event and applies no terminal guard, so a later provider callback overwrites an earlier terminal status. When an SES delivery callback follows a soft bounce (Transient), the message moves from bounced back to delivered. Read the event timeline, and not the status alone, when you need the full history.
StatusTerminal?Meaning
queuedNoSendara accepted the message and queued it for delivery. No provider has it yet.
sentNoSendara handed the message to the upstream mail provider and awaits a delivery signal.
deliveredNoThe receiving mail server accepted the message. An open, a click, or a late complaint can still follow.
openedNoThe recipient opened the email (open tracking). Sendara can record this more than once.
clickedNoThe recipient clicked a tracked link (click tracking). Sendara can record this more than once.
delivery_delayedNoThe provider could not deliver the message yet and still retries it, for example because a mailbox is temporarily unreachable. The status can still become delivered or bounced.
bouncedYesDelivery failed. A hard (Permanent) bounce suppresses the recipient automatically. A soft (Transient) bounce does not.
complainedYesThe recipient marked the message as spam. Sendara suppresses the recipient automatically.
failedYesThe provider rejected the message, or a delivery attempt failed irrecoverably. This usually means the message never left the pipeline.
suppressedYesSendara blocked the send before it reached a provider, because the recipient is on your suppression list (a prior hard bounce or spam complaint). Sendara enqueued nothing and does not bill you for it.
unsubscribedYesSendara blocked a marketing send, because the recipient opted out of marketing email. This is distinct from suppressed. Sendara enqueued nothing and does not bill you for it.
statusstringRequired
The message's current status. It is a snapshot of its most recent event. Treat it as free-text. Sendara can add new values over time, so handle an unknown status gracefully rather than switch exhaustively on the set below.
Treat status as an open set. Sendara may introduce new status values, so map the ones you care about explicitly and use a default branch for the rest. Do not assume the list above is exhaustive forever.

Suppressed sends

A hard bounce, a complaint, or your own manual entry puts an address on your suppression list. When you send to that address, Sendara records the message with status suppressed and does not attempt delivery. Sendara never enqueues the send and never bills you for it. The suppressed record exists only so that you can audit the attempt. The accompanying event carries the block reason in its payload. Remove a suppressed address from your audience before you send, if you do not want it to surface at all.

Open- & click-tracking caveats

opened and clicked are best-effort signals, not delivery guarantees. Treat them as at-least-engaged hints rather than precise counts:

  • Opens require a loaded tracking pixel. Many mail clients block remote images by default, and plain-text readers never load one, so a genuine open can go unrecorded. Absence of opened does not mean the recipient did not read the message.
  • Privacy proxies inflate opens. Apple Mail Privacy Protection and similar proxies pre-fetch the pixel, which can record an opened the recipient never triggered, often within seconds of delivery.
  • Both can fire repeatedly. A reopen or a re-click records another event, so the same message can show multiple opened / clicked entries. Deduplicate by event id if you only want to count the first.
  • You must enable tracking. If open tracking or click tracking is off for the send, those events never fire regardless of recipient behavior.

Look up the status of a message

There are two ways to learn a message's current status. You pull it when you need it, or Sendara pushes it to you the moment it changes.

Poll by idempotency key

You do not need to store the msg_… id to check a send. Pass the same idempotency_key you used on POST /v1/send as a query parameter to GET /v1/messages. Sendara returns that single message with its full event timeline. The key is unique per account, so the lookup is unambiguous.

lookup.sh
curl "https://api.sendara.dev/v1/messages?idempotency_key=order_9f21" \
  -H "Authorization: Bearer sk_live_xxx"
Use the idempotency key you already generate for your send as the handle for status checks. There is no need to persist Sendara's message id separately. A lookup for a key that you never used returns a not_found (404).

To poll a status until it settles, fetch on an interval. Stop once you see a terminal status (bounced, complained, failed, or suppressed). You can also stop once delivered is good enough for your use case. For lower latency and no polling overhead, prefer webhooks.

To find messages without an id or key, pass ?search= to GET /v1/messages. It runs a full-text match over the recipient address, the From address, the subject, and the body. It combines with the other filters (status, channel, the date range, and pagination). A search for a local-part such as alice matches [email protected].

search.sh
curl "https://api.sendara.dev/v1/[email protected]&status=delivered" \
  -H "Authorization: Bearer sk_live_xxx"

Subscribe to webhooks

To react the moment a status changes, register a webhook subscription instead of a poll. Sendara records the status transition and POSTs each matching event to your endpoint. Sendara signs the request, so you can verify it. This is the recommended approach for anything time-sensitive. You get the new status with zero polling and no rate-limit pressure.

Webhooks are at-least-once: the same event may arrive more than once, and events can arrive out of lifecycle order. Make your handler idempotent on the event id, and reconcile against the message timeline if exact ordering matters. See Ordering & delivery guarantees for the full model.

A pragmatic pattern combines both. Subscribe to webhooks for real-time updates. Then poll by idempotency key (or read the timeline) to reconcile, if your endpoint was down when an event fired.