Dispatch & Sync
How records travel from Apple Health to your servers, what the states mean and what Courier promises (and honestly does not promise) about timing.
Triggers
- Background delivery — Apple Health receives new sleep/workout data and iOS wakes Courier briefly; it reads the delta and dispatches.
- Opening the app — foreground sync on launch and on return.
- Manual — the dispatch actions in the app, or the Dispatch Now Shortcut.
- Batch dispatch — Desk → Batch Dispatch: pick a date range and backfill history to any subscriber.
All four feed the same pipeline.
Item states
Every outgoing record moves through an explicit state machine, persisted so that a force-quit or reboot can't lose track:
| State | Meaning |
|---|---|
queued | Waiting its turn |
awaitingRoute | Outdoor workout whose GPS track hasn't arrived from the Watch yet — Courier waits rather than send an incomplete record |
sending | Request in flight |
sent | Delivered (2xx). Recorded in the ledger |
failed | Failed and acknowledged — will never be retried automatically; use manual resend |
needsAttention | Failed/incomplete and not yet acknowledged: retries exhausted, route never arrived, or the source data disappeared. Red dot + notification |
Retries
- Foreground dispatches retry network errors with exponential backoff (2 s → 4 s → 8 s → 16 s).
- Background dispatches get a single attempt with a 15-second timeout — iOS background time is scarce and Courier doesn't gamble with it. Failures simply queue for attention.
- Nothing is ever auto-retried once it reaches
failed— you stay in control of what gets re-sent.
Idempotency
Every request carries an Idempotency-Key header:
- Normal sends and manual retries use the record's stable canonical
id— an endpoint that de-duplicates by key will never store the same workout twice, no matter how many times delivery is retried. - Deliberate duplicates ("send another copy") rotate the key to
id#2,id#3, … so idempotent endpoints can still accept intentional copies.
If your server honours Idempotency-Key (or upserts by data.id), the whole pipeline becomes safe to retry at any point.
The ledger
Desk → Dispatch History shows every dispatch with timestamp, subscriber, HTTP status and response snippet. The ledger keeps 6 months of history; older entries are cleaned up automatically — the delivered content itself, of course, lives on your endpoints.
Timing, honestly
Background delivery is throttled by iOS. Expect minutes to about an hour of delay, and in the worst case the dispatch happens the next time you open the app. Courier surfaces this in its UI copy rather than pretending to be instant. If you need a guaranteed send right now: open the app, or use the Dispatch Now Shortcut.
Notifications
Two modes (Desk → Notifications):
- Always — a summary notification after each automatic dispatch round, success or failure. Default.
- Failures only — silence unless something needs you.
Notifications require the system permission; if it was denied, Courier shows the real status and a shortcut to iOS Settings instead of pretending to work.