Message Delivery Guarantees Explained: At-Most-Once, At-Least-Once, and Exactly-Once
message deliveryreliabilityidempotencyretriesdead-letter queuesasync processingdistributed systems

Message Delivery Guarantees Explained: At-Most-Once, At-Least-Once, and Exactly-Once

MMessages Solutions Editorial Team
2026-11-01
7 min read

A practical guide to at-most-once, at-least-once, and exactly-once delivery, with retries, idempotency, deduplication, and dead-letter workflows.

Message delivery guarantees determine what happens when a broker, network, consumer, or application fails. This guide explains at-most-once, at-least-once, and exactly-once delivery, then gives you a practical workflow for choosing acknowledgments, retries, idempotency, deduplication, and dead-letter handling for payments, notifications, data pipelines, and background jobs.

Overview

A delivery guarantee is a design decision about failure. It describes how a messaging system behaves when a message cannot be confirmed, a consumer crashes, or a network connection disappears at an inconvenient moment. The three commonly discussed models are at-most-once, at-least-once, and exactly-once.

  • At-most-once: a message is delivered zero or one time. The system avoids redelivery, but a failure can cause the message to be lost.
  • At-least-once: a message is delivered one or more times. The system retries when confirmation is missing, so consumers must tolerate duplicates.
  • Exactly-once: the intended effect occurs once within a clearly defined scope. This usually requires coordinated transactions, deduplication, or both; it is not a blanket guarantee across every external system.

These terms are easy to oversimplify. A broker may persist a message reliably while an application processes it twice. Conversely, a consumer may complete its database update but crash before acknowledging the message, causing a retry. For that reason, distinguish message delivery from application processing. Reliable async processing usually depends on the complete path: producer, broker, consumer, side effects, acknowledgment, and recovery procedures.

Start with the consequence of loss and duplication. Losing a typing indicator is different from losing a payment command. A duplicate email may be inconvenient, while a duplicate inventory reservation can create a business error. The right guarantee is the one that makes failure manageable without adding unnecessary operational complexity.

Step-by-step workflow

1. Define the message and its side effects

Write down what the message means and what the consumer changes. Include database records, payments, emails, webhooks, inventory, cache entries, and downstream events. Avoid vague labels such as “important event.” A useful definition might be “request to charge order 4821” or “notification that a document is ready.”

Then identify whether the operation is safe to repeat. A read-only cache refresh may be naturally repeatable. A charge, shipment, or account credit usually requires an explicit idempotency strategy.

2. Choose the failure preference

Use at-most-once only when loss is acceptable and avoiding duplicates matters more than completeness. Ephemeral presence signals and some telemetry may fit this model, provided the system does not treat missing events as authoritative.

Use at-least-once when losing a message would be worse than processing it more than once. This is a common starting point for background jobs, integration events, and business workflows. It shifts responsibility to the consumer, which must acknowledge only after the required work is safely complete.

Use an exactly-once approach cautiously. Define the boundary first: exactly once in a database transaction, exactly once in a stream-processing state update, or exactly once as observed by a particular consumer. If the workflow calls an external payment provider or sends an email, the message platform alone cannot automatically make that external side effect exactly once.

3. Design acknowledgment timing

An acknowledgment should represent a meaningful point of completion, not merely receipt. A consumer that acknowledges before committing its database change can lose work if it crashes immediately afterward. A consumer that acknowledges after the change may see a duplicate if it crashes before the acknowledgment reaches the broker. That second outcome is often safer when the operation is idempotent.

Document the acknowledgment rule for each consumer: what must be committed, which failures trigger a retry, and which errors are permanent. Keep processing time, visibility timeouts, and lease settings aligned so that a slow but healthy job is not redelivered while it is still running.

4. Add idempotency and deduplication

Give each business operation a stable idempotency key. A message identifier can work when it remains stable across retries, but a business key is often more useful: an order ID plus operation type, for example. Store the key with the result or in a deduplication table, and enforce uniqueness at the database layer when possible.

Make the check and the side effect safe against races. In a relational system, this may mean using a unique constraint and a transaction. For an external API, pass the same idempotency key when the provider supports it, and record the request state so recovery can distinguish “not sent,” “sent but unknown,” and “completed.”

5. Define retry and dead-letter behavior

Separate transient failures from permanent failures. A temporary database connection problem may deserve a retry. Invalid input, an unsupported schema, or a missing required entity may need correction rather than repeated delivery.

Use bounded retries with increasing delays, and add jitter where many consumers might retry simultaneously. Set a maximum attempt count or time window. After that limit, route the message to a dead-letter queue or another quarantine mechanism with the original payload, error details, attempt count, timestamps, and relevant identifiers. A dead-letter queue is not a final destination; it is an operational work queue that needs ownership, access controls, and a replay procedure.

Tools and handoffs

The broker is only one part of the design. A queue, event streaming platform, or managed pub/sub service may provide different acknowledgment, retention, ordering, visibility, and replay controls. Compare those behaviors against the workflow rather than choosing a product solely by its delivery label. The message broker decision matrix can help structure that comparison, while the Kafka, RabbitMQ, and NATS comparison provides additional architecture context.

Define clear handoffs between components:

  • Producer to broker: decide whether the producer may retry publishing and how it prevents duplicate messages.
  • Broker to consumer: configure acknowledgment, visibility, retention, ordering, and redelivery behavior.
  • Consumer to database: commit business state and idempotency records in a consistent way.
  • Consumer to external API: use a stable request key and preserve enough state to recover from an uncertain response.
  • Failure path to operations: expose retry counts, dead-letter volume, age, and replay ownership.

For event streams, retention and replay can change the recovery model: a consumer may rebuild state from earlier events rather than depend on a single retry queue. For background jobs, a queue with visibility control and a dead-letter path may be more direct. If a system is moving from polling to events, document which old polling behavior provided implicit retries or reconciliation; the polling-to-event-driven migration guide covers that transition.

Quality checks

Test delivery semantics with failures, not only successful messages. Stop a consumer after the side effect but before acknowledgment. Delay an acknowledgment. Return a timeout after an external request may have completed. Publish the same event twice. Send malformed input. Confirm that each case produces the intended result.

Review these checks before production:

  • Every message has a traceable identifier and, where needed, a business idempotency key.
  • Acknowledgment occurs after the documented completion point.
  • Retries are bounded and distinguish transient from permanent errors.
  • Duplicate processing produces no harmful duplicate side effect.
  • Dead-letter messages retain enough context for diagnosis and safe replay.
  • Replay is protected against re-triggering irreversible actions without review.
  • Dashboards and alerts show processing latency, retry rate, oldest unprocessed message, and dead-letter growth.
  • Access to payloads, replay tools, and credentials follows the sensitivity of the data.

For duplicate-specific implementation patterns, see how to prevent duplicate messages in event-driven systems. Observability should connect a producer event, broker delivery, consumer attempt, database transaction, and external request where possible. Without that chain, teams may see “successful processing” while missing a duplicate or an unacknowledged failure.

When to revisit

Revisit delivery guarantees whenever the business consequence of failure changes, a new consumer is added, or a message begins triggering an external side effect. Also review the design when changing brokers, retention settings, acknowledgment APIs, schema versions, retry policies, or stream-processing tools. Platform feature changes can alter available transactions, replay controls, timeout behavior, or dead-letter integration.

Schedule a practical review after a production incident and after major workflow changes such as introducing payments, fulfillment, customer notifications, or webhook delivery. Keep a short decision record for each message type: guarantee, acknowledgment point, idempotency key, retry limit, dead-letter owner, and replay method. On the next review, run one controlled failure test and confirm that the documented behavior still matches the deployed configuration.

If you are choosing a queue for jobs, compare the operational requirements in the background-job queue guide. If the workflow is a realtime application, delivery guarantees should be considered alongside connection recovery and synchronization; the WebSocket scalability guide provides that broader reliability context.

Next step: choose one important message flow, write down its failure preference, add a stable idempotency key, and perform a crash-before-acknowledgment test. That small exercise usually reveals whether the system’s stated guarantee matches what users and operators actually experience.

Related Topics

#message delivery#reliability#idempotency#retries#dead-letter queues#async processing#distributed systems
M

Messages Solutions Editorial Team

Senior Technology Editor

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.