# Handle an Unknown iMessage Delivery Result

Canonical: https://figment.so/blog/imessage-delivery-unknown

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.

## What the receipt endpoint returns

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.

```http
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.

## Receipt state decision table

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](https://support.apple.com/guide/messages/send-read-receipts-ichtee6857f6/mac) (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](https://support.apple.com/guide/messages/send-read-receipts-ichtee6857f6/mac) — 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.

## Unknown is not the same as failed

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.

## How to poll without creating duplicates

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.

## Idempotency key rules

Safe polling depends on correct key management from the moment you decide to send.

1. **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.

2. **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.

3. **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.

4. **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](/blog/prevent-duplicate-imessages) guide covers the full idempotency pattern including failure-state recovery and the timeout-expiry workflow.

## Before your first send

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](/blog/mac-connector-permissions) 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`.

## Connect and start testing

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](/apple-connect/developers) 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](/oauth-for-icloud). 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](/blog/imessage-history-not-syncing).
