Receipts and delivery verdicts
Delivered, read and not delivered — the stream's receipt and delivery_failed frames, the webhooks that carry the same facts, and the question you can ask about one message now.
A command frame tells you how a command ended. For message.send,
succeeded is the transport's settlement of the send — the platform handed the
message on and the transport took it. It is not a delivery receipt, and nothing
about whether the message reached the recipient's device arrives on that frame.
That arrives later, if the transport reports it, as one of two facts:
- Delivered or read — a recipient's device reported it. On the stream this
is a
receiptframe; on a webhook,message.deliveredormessage.read. - Not delivered — the provider accepted the message and later reported it
undelivered, Apple's "Not Delivered" on iMessage. On the stream this is a
delivery_failedframe; on a webhook,message.delivery_failed.
Both frames are opt-in, and so is each webhook: a client that never asked for them meets nothing new.
Asking for them
Name the frames you want in events when you open the stream. The values
combine:
curl -N "https://api.mapier.ai/v1/response_stream?events=receipts,delivery_failures" \
-H "Authorization: Bearer $MAPIER_API_KEY" \
-H "x-mapier-version: 2026-10-15" \
-H "Accept: text/event-stream"receipts adds the receipt frame and delivery_failures adds the
delivery_failed frame. A value the stream does not frame is a 422 naming it,
never a stream that silently lacks what you asked for. Both frames carry an
id: like every other, so a reconnect with Last-Event-ID replays the ones you
missed — see Resuming.
receipt — delivered or read
id: 0192a7c4-7d55-7a90-b3e1-4c6a8f2d9e04
event: receipt
data: {"commandId":"3f1a0c2e-8b47-4d19-9a5e-2c6f0b8d4e71","messageId":"0192a7c4-5a31-7bd2-8e6f-1d0c9b8a7f65","status":"delivered","occurredAt":"2026-08-24T18:03:14.000Z","simulated":false}Correlate by commandId, the handle the send's 202 gave you. status is
delivered or read, and a receipt never walks a message backwards: once a
message is read, a late delivered for it moves nothing and frames nothing.
occurredAt is when the device reported it. The full field list is on the
reference.
What each transport can report differs, and the frame says only what was reported:
- In test, the sandbox produces receipts according to each contact's
delivery mode, and every one carries
simulated: true. - A live iMessage line frames them from Apple's status of the send, which is first asked about 75 seconds after the send and last about 10 minutes after it. A delivery or a read later than that is never framed, and a read arrives only from a recipient who shares read receipts.
So the absence of a receipt is not evidence of anything. A message with no
delivered frame may have been delivered after the window closed.
delivery_failed — not delivered
id: 0192a7c4-8e20-7f43-a1b6-9d3c5e7f0a28
event: delivery_failed
data: {"commandId":"3f1a0c2e-8b47-4d19-9a5e-2c6f0b8d4e71","messageId":"0192a7c4-5a31-7bd2-8e6f-1d0c9b8a7f65","occurredAt":"2026-08-24T18:05:02.000Z","simulated":false}The provider accepted the message and later reported it not delivered. Three things about it are easy to get wrong:
- It is not a failed send. The message keeps its status, and the frame
carries no
status,errorCodeorfailureClass— the send did not fail, its delivery did. Do not route it through the code that handles afailedcommandframe. - It can arrive before the send's own
commandframe. Live, a verdict can land while the transport's settlement of the send is still on its way, and thecommandframe follows it. Correlate bycommandId, and do not assume you recorded the send before the verdict. - Nothing retries it. The platform does not resend, and it does not mark
the message failed. What to do next is yours; a resend is a new message
under a new
Idempotency-Key.
In test, a contact whose delivery mode is Sends, then not delivered rehearses
it: the send settles succeeded, and once the world's delivery delay has
passed the verdict arrives with simulated: true. There the verdict always
comes after the command frame; live it need not, which is the difference to
design for.
The webhooks that carry the same facts
Subscribe an endpoint on the project's Webhooks page to the types you want. Each carries the same fact as its frame, with the time under a name of its own:
| Stream frame | Webhook type | data |
|---|---|---|
receipt | message.delivered | messageId, conversationId, commandId, deliveredAt, simulated |
receipt | message.read | messageId, conversationId, commandId, readAt, simulated |
delivery_failed | message.delivery_failed | messageId, conversationId, commandId, failedAt, simulated |
Every data also carries projectId and environment. A webhook needs no
opt-in beyond the subscription, and it is retried when your endpoint does not
answer — see Webhooks.
Asking about one message now
GET /v1/messages/{messageId}/receipts asks the transport
what it can prove about one message at the moment you call it. It stores
nothing and moves nothing, and its delivered and read are three-valued:
null means the transport does not know, which is not the same as false.
Use it when a person is looking at one message and wants the current answer;
use the frames or the webhooks to keep your own records.
Related
GET /v1/response_stream
The receipt and delivery_failed frames, field by field.
GET /v1/messages/{messageId}/receipts
The live question, unknowns included.
Webhooks
message.delivered, message.read and message.delivery_failed, signed and retried.
Test mode
Rehearse a delivery, a read and a verdict with the sandbox's delivery modes.