Delivery and store-and-forward

At-most-once, at-least-once, and exactly-once delivery

At-most-once, at-least-once and exactly-once are the three delivery guarantees a messaging system can offer. At-most-once means a message may be lost but is never delivered twice, at-least-once means it is not lost but may arrive more than once, and exactly-once means each message takes effect once. Over an unreliable network, exactly-once is built from at-least-once delivery plus a receiver that recognises and ignores repeats.

Learning objectives

After reading this article you will be able to:

  • Distinguish at-most-once, at-least-once and exactly-once by what each can lose or repeat
  • Describe how MQTT's three QoS levels build each delivery guarantee
  • Explain why exactly-once needs a receiver that recognises and ignores repeats

The three guarantees

Apache Kafka’s documentation gives the standard definitions:

  • At most once: messages may be lost but are never redelivered.
  • At least once: messages are never lost but may be redelivered.
  • Exactly once: each message is processed once and only once.

MQTT 5.0 names its three quality of service levels after the same ideas.

GuaranteeWhat can go wrongHow the sender behavesMQTT level
At most onceA message can be lostSends once, never retriesQoS 0
At least onceA message can arrive twiceResends until it gets an acknowledgmentQoS 1
Exactly onceNeither, if both sides keep stateResends, and the receiver remembers what it has seenQoS 2

Why the sender cannot simply know

The difficulty comes from one situation. A sender transmits a message and hears nothing back. Kafka’s documentation describes a producer that gets a network error and cannot tell whether the error happened before or after the message was committed. Either the message was lost, or it arrived and the acknowledgment was lost.

The sender has two choices. If it does nothing, a lost message stays lost: that is at-most-once. If it resends, a message that had in fact arrived now arrives twice: that is at-least-once. No choice made by the sender alone avoids both outcomes. Avoiding both needs the receiver’s help.

How protocols build each one

MQTT 5.0 shows all three side by side.

QoS 0, at most once. The sender sends the message once. The receiver sends no response and the sender performs no retry, so the message arrives once or not at all. It sends the fewest packets, and it suits data where losing an occasional message does not matter.

QoS 1, at least once. The sender stores the message, sends it with a packet identifier, and treats it as unacknowledged until a PUBACK with the same identifier comes back. When it reconnects to an existing session, it resends anything still unacknowledged. If the PUBACK was lost, the receiver gets a second copy, and the specification tells the receiver to treat that copy as a new message.

QoS 2, exactly once. Here the receiver keeps state. It replies to the PUBLISH with PUBREC and stores the packet identifier. Until the sender confirms with PUBREL, the receiver answers any repeat of that PUBLISH with another PUBREC and must not deliver a duplicate onward. A final PUBCOMP closes the exchange. MQTT notes the extra overhead that comes with QoS 2.

CoAP (RFC 7252) uses the same idea for its Confirmable messages: the receiver should acknowledge every duplicate but process the request only once.

Exactly-once is an end-to-end property

Kafka’s documentation warns that many systems claim exactly-once delivery and that it is important to read the fine print, because such claims may not hold when producers or consumers fail, when there are multiple consumers, or when data written to disk is lost.

Two points follow. First, a guarantee on one link does not extend to the whole path. MQTT says its delivery protocol is concerned only with delivery from a single sender to a single receiver, and a broker may use a different QoS level when it passes a message on.

Second, “processed once” includes what the receiver does after the message arrives. Kafka describes a consumer that reads messages, processes them, then saves its position: if it crashes between the last two steps, the next consumer reprocesses some messages, which is at-least-once. Saving the position before processing risks skipping messages instead, which is at-most-once. When reading from one Kafka topic and writing to another, Kafka reaches exactly-once by writing the consumer’s position and its output in the same transaction, and its optional idempotent producer lets the broker discard resent duplicates, using a producer ID and a sequence number sent with every message.

In practice, exactly-once is at-least-once delivery combined with a receiver that makes repeats harmless, either through message deduplication by ID or by making the operation idempotent, so that applying it twice has the same effect as applying it once.

What this means for offline delivery

Offline systems lean heavily on at-least-once delivery. Messages wait in an outbox, are retried when connectivity returns, and may be carried along more than one path. Every one of those steps can produce a duplicate. The transactional outbox pattern says so directly: the relay that sends outbox entries might publish a message more than once, so consumers must be idempotent.

That makes the receiver’s design the deciding factor. Give each business operation an ID that stays the same across retries and restarts. On arrival, look the ID up, apply the operation and record the ID in one local transaction, and if the ID has been seen before, return the stored result instead of applying it again. Offline Protocol’s local handoff guide uses this design, and it keeps delivery to a device, acceptance by the receiving application, and commit to a backend as three separate outcomes, each with its own evidence.

Note that at-least-once is only “never lost” while the sender keeps trying. Any real outbox has a size limit and an expiry, so a message that waits too long is reported as failed rather than delivered. A complete design says what happens then, too.

Frequently asked questions

Which guarantee should an offline app use?

For anything a person would miss if it disappeared, use at-least-once delivery with a stable ID on every message and a receiver that ignores repeats. At-most-once suits data where a newer value soon replaces a lost one.

Does MQTT QoS 2 give exactly-once from publisher to subscriber?

Not by itself. The MQTT delivery protocol covers one sender and one receiver, so a message from a publisher to a subscriber goes through two separate exchanges, and the QoS used on the second can differ from the first.

Sources

Build it with Offline Protocol

The Offline Protocol backend delivery guide sets out the evidence needed at each boundary, from a stable event ID on the device to the backend's durable acceptance, and how to test repeated delivery.

Read the backend delivery guide