Use this guide to build retry-safe integrations.

Message lifecycle

Typical state flow:
queued -> sent -> delivered -> read
   \-> failed
  • queued: accepted and waiting for dispatch.
  • sent: handed off to provider.
  • delivered: delivered to recipient device/provider state.
  • read: read receipt observed.
  • failed: dispatch failed or provider rejected.

Retry boundaries

  1. Retry on 429 and 5xx only.
  2. Do not retry validation failures (400, 422) before fixing payload.
  3. Use bounded exponential backoff with jitter.

Client idempotency pattern

  • Generate an operation key in your app per send intent.
  • Save operation_key -> message_id mapping.
  • If the same operation is retried, return existing message_id from your store.

Webhook deduplication pattern

Use event IDs to avoid duplicate processing:
if event_id already processed:
  return 200
store event_id
enqueue processing
return 200

Reconciliation flow

1

Send message request

Call POST /v1/messages and persist request metadata.
2

Track status

Query GET /v1/messages/{messageID} or consume webhook events.
3

Resolve final state

Treat read and failed as terminal states for most workflows.
Server-side webhook processing is designed for idempotent ingestion. Your consumer should still deduplicate events in your own storage.