Start with the event contract: the short agreement that says what a message means and how it can be delivered. A message arriving tells you that the receiving service got it. It does not prove the business action happened only once, arrived in order or reflects the latest state.
1. Delivery guarantees are not domain guarantees
Many event systems provide at-least-once delivery: a message can be delivered again when acknowledgement is delayed or lost. Some systems also make no general ordering guarantee across all messages. AWS documents both properties for standard Amazon SQS queues, for example. Other services have their own ordering scopes and delivery policies, which must be read separately.
Imagine a booking service emitting BookingCreated, then BookingCancelled. A downstream email consumer may receive the cancellation first, then a delayed creation event. If it blindly sets its view to whichever payload arrives last, it can show a cancelled booking as active.
2. Give each event a stable identity
Give each message a unique ID and keep a record of IDs already handled. When possible, save that record and update your database together. If the same ID arrives again, recognise the repeat and avoid sending the same email, fulfilment request or other side effect twice.
Deduplication windows need an explicit retention policy. Keep identifiers at least as long as the producer can retry and the business process can be replayed. For high-value actions, preserve an audit trail or a durable unique constraint instead of relying on a short cache.
3. Keep business sequence separate from arrival time
If changes must happen in order, have the sending system attach an increasing version number to each booking, payment or other record. The receiving service can compare it with the version it has saved, ignore an older change and flag a missing step for checking.
Timestamps alone are weaker: clocks drift, precision varies, and two events may share a timestamp. Prefer a version number assigned to that record, a sequence number for its changes, or a position marker supplied by the provider. If the upstream service offers ordering only within a partition or entity key, use that same scope and do not promise global order.
4. Reconcile gaps instead of guessing
A gap can mean a delayed message, a lost event, or a legitimate jump in the source’s version scheme. Define how the consumer recovers: request the current resource, replay from a durable cursor, or place the event in a retry queue. A dead-letter queue is a holding area for messages that could not be delivered after retries. It helps someone find the problem, but it does not fix the affected record by itself.
Inspect an incoming event
Choose the condition to see a suitable consumer response. This is a static learning example; no events are sent.
Monitor age of the oldest queued message, retry volume, duplicate rate, sequence gaps and reconciliation outcomes. Alert on business impact rather than treating every redelivery as an incident.
5. A practical consumer checklist
- Document event identity, entity key, version, schema and delivery guarantee.
- Persist deduplication state and effect atomically where possible.
- Make external side effects idempotent or use an outbox pattern.
- Reject stale entity revisions without overwriting newer state.
- Define gap handling, replay bounds and dead-letter ownership.
- Measure lag and reconcile against authoritative state.
The interface should describe the record it has actually read. “Update received” is not necessarily “account current”; show when the consumer last reconciled if freshness matters to the user.
Sources and review date
- Amazon SQS: standard queues — at-least-once delivery and possible out-of-order delivery for standard queues.
- Amazon EventBridge: retry policy — service-specific retry behavior and event delivery attempts.
- Amazon EventBridge: ordering events — service-specific considerations; not a universal queue rule.
Reviewed 26 October 2026. The exact guarantees differ by service, queue type and configuration. Confirm current provider documentation before relying on them.
