# Delivery Guarantees (/learn/concepts/delivery-guarantees)



Imagine mailing a contract. You could drop it in a mailbox and hope it arrives — fast, but if it gets lost you never know. You could send it certified mail, where the courier keeps trying until someone signs for it — nothing is lost, but a frazzled courier might leave two copies. Or you could number every envelope and have the recipient ignore duplicates — slower and more bookkeeping, but each contract lands exactly once.

Those three choices are the three **delivery guarantees** every messaging system makes you pick between. The guarantee decides what happens when something fails — a subscriber is offline, a network blips, a consumer crashes mid-work. There is no "always perfect" option for free: stronger guarantees cost latency, storage, and complexity. This page explains the three guarantees, the acknowledgement mechanics that make the stronger ones possible, and the safety nets (idempotency and dead-letter queues) that keep them honest.

## Delivery guarantees — the idea [#delivery-guarantees--the-idea]

A delivery guarantee is a promise about how many times a message is processed by a consumer in the face of failure. There are three:

* **at-most-once** — deliver the message, never retry. A message is processed zero or one times. Fastest and cheapest; messages can be silently lost.
* **at-least-once** — keep delivering until the consumer confirms success. A message is processed one or more times. Nothing is lost, but duplicates are possible.
* **exactly-once** — the message is processed once and only once, even across failures. No loss, no duplicates. The strongest promise and the most expensive to provide.

The pivot between them is the **acknowledgement** — a small signal the consumer sends back after handling a message. Whether the system waits for an ack, and what it does when an ack never arrives, is what separates the three guarantees.

### at-most-once: fire and forget [#at-most-once-fire-and-forget]

The broker hands the message to whoever is listening right now and immediately forgets it. There is no ack, no retry, no stored copy. If a subscriber is offline or the message is dropped in transit, it is gone.

<Mermaid
  chart="sequenceDiagram
  participant P as Publisher
  participant B as Broker
  participant S as Subscriber

  P->>B: publish &#x22;order.created&#x22;
  B-->>P: accepted
  Note over B,S: Subscriber is offline — no stored copy, no retry
  B--XS: deliver (lost)
  Note over P,S: Message processed 0 times"
/>

*at-most-once: the broker delivers to active subscribers only; an offline subscriber misses the message and it is never retried.*

### at-least-once: acknowledge or redeliver [#at-least-once-acknowledge-or-redeliver]

The broker keeps the message until the consumer **acks** it. If the consumer fails to ack — it crashes, times out, or sends a **nack** (negative acknowledgement) — the broker redelivers. Nothing is lost, but a consumer that did the work and then crashed *before* acking will see the same message again.

<Mermaid
  chart="sequenceDiagram
  participant B as Broker
  participant C as Consumer

  B->>C: deliver &#x22;order.created&#x22; (attempt 1)
  Note over C: Consumer crashes before ack
  C--XB: no ack
  Note over B,C: Ack window expires — message redelivered
  B->>C: deliver &#x22;order.created&#x22; (attempt 2)
  C->>C: process order
  C-->>B: ack
  Note over B,C: Message processed 1+ times (a duplicate is possible)"
/>

*at-least-once: with no ack inside the window, the broker redelivers; the message is never lost but may arrive more than once.*

### exactly-once: acknowledge, then deduplicate [#exactly-once-acknowledge-then-deduplicate]

You reach effectively-once by combining at-least-once delivery with **deduplication** on the consumer side. The consumer records which message IDs it has already handled; a redelivered duplicate is recognized and skipped before it has any effect. The work happens once even though the message may be delivered twice.

<Mermaid
  chart="sequenceDiagram
  participant B as Broker
  participant C as Consumer
  participant D as Dedup store

  B->>C: deliver &#x22;order.created&#x22; (id: m-42)
  C->>D: seen m-42 before?
  D-->>C: no — record m-42
  C->>C: process order
  C-->>B: ack
  Note over B,C: Ack lost in transit — broker redelivers
  B->>C: deliver &#x22;order.created&#x22; (id: m-42)
  C->>D: seen m-42 before?
  D-->>C: yes — skip
  C-->>B: ack
  Note over B,D: Effect applied exactly 1 time"
/>

*exactly-once (effectively-once): at-least-once delivery plus a dedup check on the message ID means the redelivered duplicate has no effect.*

## Precise definition [#precise-definition]

A **delivery guarantee** is the contract a messaging system upholds for how many times a given message is successfully processed when failures occur. It is realized through three mechanics:

* **Acknowledgement (ack)** — a signal from the consumer that a message was processed successfully and can be discarded by the broker. A &#x2A;*negative acknowledgement (nack)** signals failure and asks for redelivery.
* **Redelivery window** — the time the broker waits for an ack before assuming failure and redelivering. (When a consumer holds a message during processing, this is the **visibility timeout**.)
* **Idempotency** — a processing operation is *idempotent* if applying it twice has the same effect as applying it once. Idempotent consumers turn at-least-once delivery into effectively-once results, because duplicates do no extra harm.

A &#x2A;*dead letter queue (DLQ)** is the final safety net: after a message fails and is redelivered up to a configured maximum number of times, the broker stops retrying and moves it to a separate channel for inspection. This prevents a single "poison" message from being redelivered forever and blocking the queue behind it.

> True end-to-end **exactly-once** across independent systems is impossible in the general case (a consumer cannot atomically both ack the broker and commit a side effect). In practice, "exactly-once" means &#x2A;*at-least-once delivery + idempotent processing (or dedup)** — often called **effectively-once**.

## Trade-offs [#trade-offs]

| Guarantee         | Loss?    | Duplicates?    | Cost                                 | Use when                                                                                        |
| ----------------- | -------- | -------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------- |
| **at-most-once**  | Possible | Never          | Lowest latency, no storage           | Telemetry, live dashboards, cache invalidation — a missed message is harmless                   |
| **at-least-once** | Never    | Possible       | Storage + ack round-trip             | Orders, payments, jobs — losing a message is unacceptable; you can tolerate (or dedup) a repeat |
| **exactly-once**  | Never    | Never (effect) | All of the above + dedup/idempotency | Financial postings, inventory decrements — a duplicate would corrupt state                      |

<Callout type="warn">
  **Pitfall:** "at-least-once" guarantees delivery, not single processing. If your consumer is **not idempotent** — for example it blindly increments a balance — a redelivered duplicate will double-charge. Make the handler idempotent (key side effects by message ID, or upsert instead of insert) before relying on at-least-once for anything stateful.
</Callout>

## In KubeMQ [#in-kubemq]

<Callout type="info">
  **In KubeMQ:** the guarantee is a property of the **pattern you choose**, not a per-message flag. Events are fire-and-forget (at-most-once). Events Store persists every message so durable subscribers never lose one (at-least-once on the delivery path). Queues give each consumer an explicit **ack / nack** decision plus a **dead-letter queue** after `maxReceiveCount` retries — the basis for exactly-once-processing when your handler is idempotent. RPC is request/reply: the response *is* the acknowledgement.
</Callout>

Each KubeMQ pattern occupies a different point on the guarantee spectrum:

| Delivery guarantee          | KubeMQ pattern                      | How it is provided                                                                                                                    |
| --------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| **at-most-once**            | [Events](/learn/events)             | Fire-and-forget pub/sub — delivered to active subscribers only, no persistence, no retry                                              |
| **at-least-once**           | [Events Store](/learn/events-store) | Every message is persisted; durable subscriptions track their position and replay anything missed                                     |
| **exactly-once-processing** | [Queues](/learn/queues)             | Explicit ack/nack settlement + redelivery on nack/timeout + a dead-letter queue; pair with an idempotent handler for effectively-once |
| **request/reply**           | [RPC](/learn/rpc)                   | The reply confirms processing synchronously; a Command returns an ack, a Query returns data — no separate ack step                    |

The clearest place to see ack/nack in code is the Queues consumer. After receiving a message, you decide its fate: **ack** removes it, **nack** returns it for redelivery. If it is nacked past `maxReceiveCount`, KubeMQ routes it to the configured dead-letter queue.

<Tabs groupId="language" items="['Go','Python','Node.js','Java','C#','Kotlin','C++','Rust','Ruby','Elixir']">
  <Tab value="Go">
    ```go title="settle.go"
    resp, err := client.PollQueue(ctx, &kubemq.PollRequest{
        Channel:            "orders",
        MaxItems:           1,
        WaitTimeoutSeconds: 5,
    })
    if err != nil {
        log.Fatal(err)
    }
    for _, m := range resp.Messages {
        if err := process(m.Message.Body); err != nil {
            m.NAck() // failed — return to queue for redelivery
            continue
        }
        m.Ack() // success — remove from queue
    }
    ```
  </Tab>

  <Tab value="Python">
    ```python title="settle.py"
    response = client.receive_queue_messages(
        channel="orders",
        max_messages=1,
        wait_timeout_in_seconds=5,
    )
    for msg in response.messages:
        try:
            process(msg.body)
            msg.ack()   # success — remove from queue
        except Exception:
            msg.nack()  # failed — return to queue for redelivery
    ```
  </Tab>

  <Tab value="Node.js">
    ```typescript title="settle.ts"
    const messages = await client.receiveQueueMessages({
      channel: 'orders',
      maxMessages: 1,
      waitTimeoutSeconds: 5,
    });
    for (const msg of messages) {
      try {
        await process(msg.body);
        await msg.ack();   // success — remove from queue
      } catch {
        await msg.nack();  // failed — return to queue for redelivery
      }
    }
    ```
  </Tab>

  <Tab value="Java">
    ```java title="Settle.java"
    ReceiveQueueMessagesResponse response = client.receiveQueueMessages(
        ReceiveQueueMessagesRequest.builder()
            .channel("orders")
            .maxMessages(1)
            .waitTimeoutSeconds(5)
            .build());

    for (QueueMessageReceived msg : response.getMessages()) {
        try {
            process(msg.getBody());
            msg.ack();   // success — remove from queue
        } catch (Exception e) {
            msg.nack();  // failed — return to queue for redelivery
        }
    }
    ```
  </Tab>

  <Tab value="C#">
    ```csharp title="Settle.cs"
    var response = await client.ReceiveQueueMessagesAsync(new ReceiveQueueMessagesRequest
    {
        Channel = "orders",
        MaxMessages = 1,
        WaitTimeoutSeconds = 5,
    });
    foreach (var msg in response.Messages)
    {
        try
        {
            Process(msg.Body);
            await msg.AckAsync();   // success — remove from queue
        }
        catch
        {
            await msg.NAckAsync();  // failed — return to queue for redelivery
        }
    }
    ```
  </Tab>

  <Tab value="Kotlin">
    ```kotlin title="Settle.kt"
    val response = client.receiveQueueMessages(
        channel = "orders",
        maxMessages = 1,
        waitTimeoutSeconds = 5
    )
    for (msg in response.messages) {
        try {
            process(msg.body)
            msg.ack()   // success — remove from queue
        } catch (e: Exception) {
            msg.nack()  // failed — return to queue for redelivery
        }
    }
    ```
  </Tab>

  <Tab value="C++">
    ```cpp title="settle.cpp"
    auto response = client.receiveQueueMessages("orders", 1, 5);
    for (const auto& msg : response.messages) {
        try {
            process(msg.body);
            msg.ack();   // success — remove from queue
        } catch (const std::exception&) {
            msg.nack();  // failed — return to queue for redelivery
        }
    }
    ```
  </Tab>

  <Tab value="Rust">
    ```rust title="settle.rs"
    let mut receiver = client.new_queue_downstream_receiver().await?;
    let response = receiver
        .poll(PollRequest {
            channel: "orders".to_string(),
            max_items: 1,
            wait_timeout_seconds: 5,
            auto_ack: false,
        })
        .await?;

    for msg in &response.messages {
        match process(&msg.message.body) {
            Ok(_) => msg.ack().await?,   // success — remove from queue
            Err(_) => msg.nack().await?, // failed — return to queue for redelivery
        }
    }
    ```
  </Tab>

  <Tab value="Ruby">
    ```ruby title="settle.rb"
    receiver = client.create_downstream_receiver
    request = KubeMQ::Queues::QueuePollRequest.new(
      channel: 'orders', max_items: 1, wait_timeout: 5
    )
    response = receiver.poll(request)

    response.messages.each do |m|
      begin
        process(m.body)
        m.ack   # success — remove from queue
      rescue StandardError
        m.nack  # failed — return to queue for redelivery
      end
    end
    ```
  </Tab>

  <Tab value="Elixir">
    ```elixir title="settle.exs"
    {:ok, poll} =
      KubeMQ.Client.poll_queue(client,
        channel: "orders",
        max_items: 1,
        wait_timeout: 5_000
      )

    # Settle the whole transaction: ack on success, nack to redeliver
    case process(poll.messages) do
      :ok -> KubeMQ.PollResponse.ack_all(poll)
      _ -> KubeMQ.PollResponse.nack_all(poll)
    end
    ```
  </Tab>
</Tabs>

*The same receive-then-settle loop in every SDK: ack a processed message, nack a failed one. After `maxReceiveCount` nacks, KubeMQ moves the message to the dead-letter queue.*

### How KubeMQ does this → [#how-kubemq-does-this-]

<Cards>
  <Card title="Events — at-most-once" href="/learn/events" description="Fire-and-forget pub/sub: lowest latency, delivered to active subscribers only." />

  <Card title="Events Store — at-least-once" href="/learn/events-store" description="Durable pub/sub: every message persisted, durable subscriptions replay anything missed." />

  <Card title="Queues — ack / nack / DLQ" href="/learn/queues" description="Explicit settlement, redelivery, and dead-letter queues for exactly-once-processing." />

  <Card title="Ack, Nack & Requeue" href="/learn/queues/tutorials/ack-nack-requeue" description="The three settlement options in code, with redelivery semantics." />

  <Card title="Dead Letter Queue" href="/learn/queues/tutorials/dead-letter-queue" description="Route messages to a DLQ after they exceed the max retry count." />

  <Card title="RPC — request/reply" href="/learn/rpc" description="The reply is the acknowledgement: Commands return an ack, Queries return data." />
</Cards>
