Messages and Events
Once a message is accepted by /v3/mail/send, every delivery action against it — rendering, queueing, sending, bouncing, being opened — is recorded as an event on the message timeline. This page covers querying messages, reading their event history, and the management endpoints (cancel, retry, fail).
Base path: /v3/messages/
Auth: API key or JWT; mail.send (for retry/cancel/fail) or stats.read for plain reads — check your deployment.
The message lifecycle#
1 2 3 4 5 | |
| Status | Meaning |
|---|---|
QUEUED |
Accepted, waiting for the worker to pick up |
PROCESSING |
Worker is rendering the template and building MIME |
RENDERED |
MIME is built, waiting for SMTP service |
SENDING |
SMTP service is actively connecting to recipient MX |
SENT |
Accepted by recipient MX (awaiting async bounce confirmation if any) |
DELIVERED |
Confirmed delivered (no bounce received, or 250 OK captured) |
BOUNCED |
Permanent failure — either 5xx at send time or an async DSN |
FAILED |
Non-delivery error (template render failure, unverified domain, rate-limited at SMTP layer after retries) |
CANCELLED |
User cancelled before sending |
SANDBOX |
Test send; validated but not delivered |
Listing messages#
1 2 | |
1 2 3 4 5 6 7 8 9 10 | |
1 2 3 4 5 | |
Filters#
| Parameter | Notes |
|---|---|
status |
Filter by lifecycle status (queued, delivered, bounced, etc.) |
to_email |
Exact match on a recipient address |
from_email |
Exact match on the sender address |
subject |
Substring search on subject |
batch_id |
Messages belonging to a batch |
template_id |
Messages sent using a template |
category |
Messages tagged with this category |
start_date / end_date |
ISO-8601 or YYYY-MM-DD |
page |
1-indexed page number (default 1) |
page_size |
1–100 (default 25) |
Response#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
The list view is a summary. To see the body, headers, and event timeline, get the individual message.
Getting a single message#
1 2 | |
Response:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | |
The events[] array is the full message timeline in chronological order. Every event ScaiSend has recorded for this message lands here — it's the same events fanned out to your webhooks, just pull-based.
Event fields#
Events on the timeline include common fields plus type-specific extras:
| Field | Always present? | Notes |
|---|---|---|
event_type |
Yes | processed, deferred, delivered, bounce, blocked, dropped, open, click, spam_report, unsubscribe, group_unsubscribe, group_resubscribe |
recipient_email |
Yes | The recipient this event pertains to |
timestamp |
Yes | ISO-8601 UTC |
url |
On click |
The original URL that was clicked |
user_agent |
On open, click |
The recipient's client identifier |
ip_address |
On open, click |
The recipient's IP at time of interaction |
bounce_type |
On bounce |
hard, soft, block |
bounce_reason |
On bounce |
Human-readable reason string |
smtp_response |
On delivered, bounce, deferred, blocked |
Raw SMTP response line |
metadata |
Sometimes | Arbitrary extra fields |
See Events and Webhooks for the full metadata reference per event type.
Message management#
Three actions on a single message: cancel, retry, and fail.
Cancel#
Stops a scheduled or in-progress message from being sent. Works on QUEUED and PROCESSING. Fails (by design) if the message is already in SENDING or later.
1 2 | |
1 | |
Requires mail.cancel scope.
Retry#
Force-requeues a message that's stuck or in a terminal failure state. Works on PROCESSING, SENDING, FAILED, and BOUNCED.
1 2 | |
1 | |
Use cases:
- A soft bounce that you think is resolvable. The recipient's mailbox was full at the time; you've waited; retry.
- A stuck message. Rare, but if a message sat in
PROCESSINGtoo long (worker crashed mid-render), retry kicks it back into the queue. - A post-mortem redelivery. Bounced because of a policy issue that's since been fixed (SPF was wrong; you fixed SPF; retry).
Retrying a hard bounce (bounce_type: hard) does not remove the address from the suppression list — you'll just get another bounce event, unless you also manually remove the suppression first.
Fail#
Mark a stuck message as FAILED manually. Used to clean up orphans:
1 2 3 4 | |
The reason is optional; it's recorded as the message's error_message. The message transitions to FAILED and no further delivery is attempted.
Prefer retry over fail when the state is ambiguous. Use fail only when you're certain the message should not be redelivered.
Performance notes#
GET /v3/messages/{id}is cheap (< 10 ms typically) — the message, events, and body come from a single query.GET /v3/messagesis indexed ontenant_id,created_at,status, and common filter fields. Filters on high-cardinality fields (to_email,subject) fall back to full table scans on very large datasets; if you need those often, add a downstream index or an Elasticsearch export.- Pagination uses offset/limit under the hood. Deep pagination (page > ~100) gets slow on large tenants; prefer filtering by date range to narrow.
What's next#
- Events and Webhooks — the push model for the same events.
- Messages Reference — exhaustive endpoint reference.
- Statistics — aggregate counts instead of per-message.