Skip to content

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

  1. Background delivery — Apple Health receives new sleep/workout data and iOS wakes Courier briefly; it reads the delta and dispatches.
  2. Opening the app — foreground sync on launch and on return.
  3. Manual — the dispatch actions in the app, or the Dispatch Now Shortcut.
  4. 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:

StateMeaning
queuedWaiting its turn
awaitingRouteOutdoor workout whose GPS track hasn't arrived from the Watch yet — Courier waits rather than send an incomplete record
sendingRequest in flight
sentDelivered (2xx). Recorded in the ledger
failedFailed and acknowledged — will never be retried automatically; use manual resend
needsAttentionFailed/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.

Printed at home · © 2026 Xheldon