
When the Figment receipt endpoint returns unknown, the bridge accepted your send request and queued it, but it has not received a confirmed outcome from the Mac-side connector. This is an indeterminate state, not a failure. The correct response is to poll the same receipt using the same Idempotency-Key you used to send. Issuing a new key does not check on the original message — it starts a second, independent send that the bridge cannot collapse with the first.
After every POST .../messages/send, call GET .../messages/receipt with the same Idempotency-Key header and your messages:send access token. The receipt endpoint requires messages:send — messages:read alone is not sufficient. The bridge resolves the key to its recorded action and returns the most recent status it has observed from the native Mac layer.
GET https://figment.so/api/apple-connect/v1/connections/con_abc123/messages/receipt
Authorization: Bearer ACCESS_TOKEN
Idempotency-Key: your-stable-key-here
Poll this endpoint repeatedly until the receipt reaches a terminal state. unknown and submitted both mean the outcome is not yet settled; all other states are terminal.
The table below covers every status value the receipt endpoint can return. The two pending or indeterminate states require polling; the remaining four are terminal.
| Status | What it means | Your action |
|---|---|---|
submitted | Bridge accepted the send; queued for the Mac worker | Poll again; outcome not yet available |
delivered | Mac confirmed the message reached the recipient's device | No action required |
read | Recipient opened the conversation; they have read receipts enabled (one-to-one conversations only) | No action required |
failed | Send attempt conclusively failed — connector error, bad chatGuid, or service offline | Log the error and surface it to the user; polling the same key again returns the same failure record |
unknown | Outcome is indeterminate; bridge has not confirmed delivery or failure | Poll again with the same key; never send again under a new key |
simulated | Synthetic result returned in sandbox mode | Test your polling logic; no real message was sent |
A note on the native layer. On Apple's side, iMessage shows "Delivered" once the recipient's device acknowledges the message and "Read" only if the recipient has read receipts turned on — and only in one-to-one conversations; group message threads do not surface read receipts regardless of settings. SMS and MMS conversations show no delivery status indicator; RCS conversations surface Delivered when the recipient's device supports it. An unknown receipt at the Figment layer means the bridge has not yet surfaced any of those native signals — not that the recipient's device rejected the message.
A failed receipt means the bridge recorded a conclusive error at the point of delivery. For example: the connector was offline when the Mac worker tried to hand the message off, or the chatGuid referenced a conversation not included in the messages:send grant. When you call the receipt endpoint on a failed action, the bridge returns that same failure record. It does not retry the send.
An unknown receipt means the bridge has no conclusive record either way. The message may still be in transit, the Mac worker may not have reported back yet, or the recipient's device may have been temporarily unreachable. That ambiguity is exactly why a new key is dangerous: if you send again under a different Idempotency-Key and the original message delivers a moment later, you now have two independent send actions recorded in the bridge. The bridge cannot deduplicate them because they carry different keys and therefore different idempotency records.
Reuse the original Idempotency-Key on each poll until the receipt reaches a terminal state or your application-level timeout expires.
loop:
GET .../messages/receipt [Idempotency-Key: <original key>]
if status is delivered, read, failed, or simulated → break
if status is submitted or unknown → wait with backoff, then repeat
if elapsed > application_timeout → break with UNRESOLVED
Use exponential backoff between polls. Short intervals waste quota; unnecessarily long ones delay detection of fast deliveries.
When your timeout expires with the receipt still at submitted or unknown, record the action as unresolved and surface it for manual review. Do not create a new send attempt for the same intended message. The original action remains in the bridge's idempotency store, and issuing a new key constitutes a separate intended action that could duplicate the original if it eventually delivers.
A terminal status does not change on subsequent reads. Polling a delivered receipt a second time still returns delivered; the receipt envelope includes a replayed field that is true on polls after the first response, so your code can distinguish a fresh outcome from a repeated one.
Safe polling depends on correct key management from the moment you decide to send.
Generate the key before the first request. Persist it immediately. If your process crashes between key generation and the HTTP call, you can safely re-issue the same key — the bridge treats it as a replay of the same action and returns the existing receipt.
Bind the key to one intended send, not to the message body. A body hash alone can merge two separately intended identical sends — for example, re-creating a greeting message you sent to the same contact last week. Use a UUID (or equivalent unique token) generated fresh per send attempt.
Never reuse a key with a different body. The bridge rejects changed bodies under an existing key to prevent silent data corruption. If you change the message text, generate a new key and treat it as a new send with its own receipt lifecycle.
Never issue a new key for an unresolved unknown. If the receipt is unknown, the original action is still active. A new key means a new action.
The Prevent Duplicate iMessages After a Timeout guide covers the full idempotency pattern including failure-state recovery and the timeout-expiry workflow.
The receipt endpoint only exists for sends that reached submitted. If your send is rejected before that point, check two things:
Connection status. A connection with any status other than ready — including needs_approval, syncing, offline, reconnect_required, or revoked — rejects operations immediately. You will get an error response from the send endpoint, not a receipt. See Grant the Mac Permissions Your Connector Needs if your sends are failing at submission.
Send grant scope. messages:read and messages:send are separate scopes. Reading or searching a conversation does not automatically authorise sends into it. The messages:send grant must explicitly include the conversation's chatGuid. A send attempt against a chatGuid outside the grant fails at the send endpoint, not at the receipt stage. The receipt endpoint itself also requires messages:send.
Sandbox connections are free and return simulated receipts, so you can build and exercise your polling logic — including timeout and unresolved-state handling — before connecting a live Apple Account.
Connect an Apple Account through the Figment developer dashboard. Live connections are $29 per month per active connection; sandbox is free and access starts after setup.
For the OAuth setup and scope request flow, see OAuth for iCloud: Connect Apple Data to Your App. If your live connection is ready but the Mac worker is not surfacing delivery outcomes, check whether iMessage history is fully synced first: Fix Missing iMessage History on a Connected Mac.