# Ack modes and receipts (/connectors/stomp/how-to/ack-modes-and-receipts)



STOMP has **no QoS levels*&#x2A;. Delivery reliability is controlled instead by the SUBSCRIBE
**`ack` mode*&#x2A;, and frame acceptance is confirmed by the optional &#x2A;*`receipt`** header. This guide
covers both — the three ack modes and which KubeMQ pattern each applies to, how the connector
correlates your ACK across STOMP 1.0/1.1/1.2, the ack-timeout requeue, and what a `RECEIPT` does
(and does not) mean.

<Callout type="info">
  Ack modes apply to **Queues** consumption. Events and Events-Store deliver as **auto** (no client
  ack) and are **at-most-once*&#x2A;. For Queues, **`client-individual` is the recommended reliable
  default** — it gives at-least-once with the simplest mental model.
</Callout>

## The three ack modes [#the-three-ack-modes]

A SUBSCRIBE carries an `ack` header; if it is absent the mode defaults to `auto`. The connector
accepts exactly three values — anything else returns `ERROR "unknown ack mode"` and closes the
connection.

| `ack` mode                   | Pending tracked | Client action                                                 | KubeMQ downstream                                                                                                |
| ---------------------------- | --------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `auto&#x60; &#x2A;(default)* | No              | none                                                          | reserve → enqueue → immediately ack the delivery; requeue (NAck) if the output buffer is full; **never dropped** |
| `client-individual`          | Yes             | ACK/NACK **one** message                                      | resolve that one delivery → one ack/NAck for its transaction                                                     |
| `client` (cumulative)        | Yes             | ACK/NACK that message **and all earlier** on the subscription | collect pending with `orderIdx ≤ acked`, **group by transaction**, one ack/NAck **per transaction**              |

### `auto` — fire-and-forget [#auto--fire-and-forget]

The connector reserves the inflight slot, enqueues the MESSAGE, and **immediately** acks the
delivery downstream. If the per-subscriber output buffer is full it &#x2A;*requeues (NAcks)** rather
than dropping — Queues are at-least-once. No `ack` token is emitted and no pending state is
tracked.

### `client-individual` — per-message ACK [#client-individual--per-message-ack]

Each MESSAGE must be acknowledged by **its own** `ack` token. An ACK resolves exactly that one
delivery; a NACK requeues it. This is the recommended reliable default for Queues.

```text
SEND       /queue/jobs/email   (×3)
SUBSCRIBE  /queue/jobs/email   ack:client-individual
  MESSAGE  message-id=m1  ack=tok1  →  ACK id:tok1   (1.2)
  MESSAGE  message-id=m2  ack=tok2  →  ACK id:tok2
  MESSAGE  message-id=m3  ack=tok3  →  ACK id:tok3
queue drains; nothing redelivered
```

### `client` — cumulative ACK [#client--cumulative-ack]

A cumulative ACK acknowledges the named message **and all earlier deliveries on the same
subscription**. The connector collects every pending entry with `orderIdx ≤` the acked delivery,
groups them by downstream transaction id, and emits **one** ack/NAck **per transaction**.

```text
SEND       /queue/work   (×10)
SUBSCRIBE  /queue/work   ack:client
  ... receive m1..m5 ...
  ACK id:<tok of m5>    → cumulatively acks m1..m5 (grouped by transaction)
  ... receive m6..m10 ...
  ACK id:<tok of m10>   → cumulatively acks m6..m10
queue drains; nothing redelivered after drain
```

<Callout type="info">
  **Why one range per transaction matters.** A single subscription's pending set routinely spans
  multiple downstream transactions. The connector emits one ack request per transaction id so that no
  foreign-transaction sequence is silently dropped — this is correctness, not an optimization. It is
  transparent to your client code.
</Callout>

## Per-version ACK/NACK correlation [#per-version-acknack-correlation]

How the connector matches your ACK or NACK frame to a tracked delivery depends on the **negotiated
version**:

| Version | ACK/NACK carries              | Resolution                                                    |
| ------- | ----------------------------- | ------------------------------------------------------------- |
| **1.2** | `id:<ack-token>`              | direct token lookup                                           |
| **1.1** | `message-id` + `subscription` | lookup by `(message-id, subscription)`                        |
| **1.0** | `message-id` only             | resolves to the **oldest** pending delivery on the connection |

The `ack` token is a **1.2-only** opaque UUID, emitted on the MESSAGE frame and &#x2A;*distinct from
`message-id`**. On 1.1 you ACK by `message-id` + `subscription`; on 1.0 by `message-id` alone, which
the connector maps to the oldest pending delivery.

ACK resolution is **connection-scoped** — a token that belongs to another connection (even a
same-login one) is silently ignored, as is a **late** ACK after the ack-timeout expired, and an ACK
on an **events** subscription (accepted as a no-op; the RECEIPT, if requested, still fires).

<Callout type="info">
  **Lenient 1.0.** The connector accepts `client-individual` and NACK on 1.0 too (ActiveMQ-style
  leniency), even though those are not in the 1.0 spec. Don't rely on this for portability — prefer
  `accept-version:1.2`. See [Protocol versions](/connectors/stomp/how-to/protocol-versions).
</Callout>

## Ack timeout, requeue, and no client-side DLQ [#ack-timeout-requeue-and-no-client-side-dlq]

If a `client-individual` or `client` delivery is **not** ACKed within the ack-timeout (default
**30 s**), a 1-second sweeper &#x2A;*requeues (NAcks)** the delivery — it does **not** disconnect the
client. A late ACK after expiry is silently ignored. The same requeue happens when the client
**disconnects** mid-stream with un-ACKed deliveries.

<Callout type="warn">
  **Ack-timeout = 30 s → requeue (not disconnect); there is no client-side DLQ.** The connector
  surfaces redelivery **only** via the `redelivered:true` MESSAGE header. There is **no STOMP-level
  dead-letter queue or redelivery-limit knob** — a `maxReceiveCount` / DLQ is **broker queue-channel**
  configuration, not a STOMP feature. A second consumer that receives a redelivered message sees
  `redelivered:true`; it never arrives on a client-side DLQ.
</Callout>

```text
SUBSCRIBE  /queue/rq   ack:client-individual
  MESSAGE  job-1   (NOT ACKed)
  ... 30s elapse → sweeper requeues ...
# a second consumer:
SUBSCRIBE  /queue/rq   ack:client-individual
  MESSAGE  job-1   redelivered:true
```

## Reliability semantics [#reliability-semantics]

<Callout type="warn">
  **Queues are at-least-once; Events and Events-Store are at-most-once. Never exactly-once.**

  * **Queues = at-least-once.** Duplicates are tolerated, never lost: requeue on a full output
    buffer, sweeper requeue on ack-timeout, and a disconnect NAcks pending deliveries. Design Queue
    consumers to be **idempotent**.
  * **Events / Events-Store = at-most-once.** A full per-subscriber output buffer **drops that one
    delivery for that one subscriber** — the connection stays alive and no requeue happens (events
    have no ack channel).
</Callout>

## Receipts [#receipts]

The `receipt` header is honored on **every processed client frame** (SEND, SUBSCRIBE, UNSUBSCRIBE,
ACK, NACK, DISCONNECT). The connector enqueues a `RECEIPT receipt-id=<id>` frame **after** the frame
is processed.

<Callout type="warn">
  **A RECEIPT means "KubeMQ accepted the frame", not "a consumer received the message".** For a SEND,
  the RECEIPT fires after the publish returns successfully — it confirms the message was accepted by
  KubeMQ, **not** that any subscriber has consumed it.
</Callout>

```text
SEND  /topic/demo  receipt:r-1
  RECEIPT  receipt-id=r-1     # KubeMQ accepted the SEND (NOT consumer delivery)
```

### ERROR-path frames never send a RECEIPT [#error-path-frames-never-send-a-receipt]

A handler that closes the connection on an **ERROR path never sends a RECEIPT** — the STOMP spec
permits an `ERROR` in lieu of a RECEIPT. So a SEND to a **bad destination** carrying a `receipt:`
header returns an `ERROR` frame and **no** RECEIPT. Client code that waits for a RECEIPT must also
handle the ERROR-then-close case.

```text
SEND  /badprefix//  receipt:r-2
  ERROR  message:invalid destination     # no RECEIPT; connection closes
```

### DISCONNECT receipt — the clean-shutdown confirmation [#disconnect-receipt--the-clean-shutdown-confirmation]

A `DISCONNECT` carrying `receipt:<id>` flushes its `RECEIPT` to the wire **before** the socket
closes, deterministically. This is the clean way to confirm a graceful shutdown — the examples in
this connector use it.

```text
DISCONNECT  receipt:bye
  RECEIPT  receipt-id=bye     # flushed before close
```

## Broker-not-ready: SEND closes, SUBSCRIBE gates [#broker-not-ready-send-closes-subscribe-gates]

When the message broker is **not ready**, the connector treats SEND and SUBSCRIBE asymmetrically —
worth knowing because it surprises STOMP migrants who expect buffering:

| Frame       | Broker not ready                                                                                     | Receipt                              |
| ----------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------ |
| `SEND`      | `ERROR "broker not ready"` + **close** — SENDs do **not** buffer                                     | no RECEIPT (ERROR path)              |
| `SUBSCRIBE` | **gated** — accepted with an empty subscription id, then **activated** when the broker becomes ready | RECEIPT **still sent** on acceptance |

So a publisher hitting a not-ready broker is disconnected (retry the connection), while a
subscriber's SUBSCRIBE is held and activated transparently on recovery.

## Related [#related]

<Cards>
  <Card title="Protocol versions" href="/connectors/stomp/how-to/protocol-versions" description="The per-version MESSAGE field rules and ACK token source — 1.2 id token, 1.1 message-id + subscription, 1.0 oldest pending." />

  <Card title="Destination mapping" href="/connectors/stomp/how-to/destination-mapping" description="How /queue/... resolves to the KubeMQ channel that an ACK or NACK acknowledges." />

  <Card title="Queues" href="/connectors/stomp/how-to/queues" description="Competing consumers, redelivery, and the at-least-once guarantee in practice." />
</Cards>
