# Reliability (/connectors/amqp/how-to/reliability)



AMQP 1.0 reliability is **settlement modes + delivery state**, not publisher confirms and not
numeric reason codes. This guide is the practical playbook for getting the delivery guarantee you
want from the KubeMQ AMQP 1.0 connector: which settlement modes exist (and which is rejected), how
each delivery-state outcome maps to a KubeMQ queue action, how durable subscriptions resume,
what happens on disconnect (nothing is lost), and why there is no connector dead-letter exchange.

For the credit machinery that governs *when* deliveries arrive — and the pre-settled-pattern
data-loss footguns — see [Flow control](/connectors/amqp/how-to/flow-control).

## Settlement modes — pick your guarantee at ATTACH [#settlement-modes--pick-your-guarantee-at-attach]

Settlement is negotiated when the link attaches.

### `snd-settle-mode` (the produce / out path) [#snd-settle-mode-the-produce--out-path]

| Requested                      | Server behavior                                                                                          | Guarantee                      |
| ------------------------------ | -------------------------------------------------------------------------------------------------------- | ------------------------------ |
| **`settled`**                  | honored — each outbound `TRANSFER` carries `settled=true` and the server acks it immediately at send     | **at-most-once** (pre-settled) |
| `unsettled` / `mixed` / absent | server uses &#x2A;*`unsettled`** — sends deliveries unsettled, tracks them, waits for your `DISPOSITION` | **at-least-once** (default)    |

* **At-least-once (default):** leave `snd-settle-mode` unset (or `unsettled`). The server keeps
  each delivery tracked until you settle it; if you never do (you disconnect), it is requeued.
* **At-most-once:** request `snd-settle-mode=settled`. Use this for high-throughput
  fire-and-forget where occasional loss is acceptable. (Events are *always* pre-settled
  regardless — see [Events](/connectors/amqp/concepts/events).)

### `rcv-settle-mode` (the consume / in path) [#rcv-settle-mode-the-consume--in-path]

* The server **always** replies `rcv-settle-mode=first`.
* **`rcv-settle-mode=second` is NOT implemented.** Requesting it closes the link with
  `DETACH(amqp:not-implemented)` **before the link is built**. Pin `first`.

<Callout type="info">
  There is no two-stage (`second`) receiver settlement. Your consumer sends a `DISPOSITION` with a
  terminal delivery state and that settles the delivery in one step.
</Callout>

## Delivery-state outcomes → KubeMQ actions [#delivery-state-outcomes--kubemq-actions]

When your client is the **receiver** (consuming a queue), it settles each delivery by sending a
`DISPOSITION` carrying a terminal **delivery state**. The connector maps that state to a KubeMQ
queue `AckRange` (settle/remove) or `NAckRange` (requeue):

| Client delivery state                                            | KubeMQ action          | Queue request | Effect on the message                                                                                                    |
| ---------------------------------------------------------------- | ---------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------ |
| **`accepted`**                                                   | settle / consume       | **AckRange**  | removed from the queue                                                                                                   |
| **`rejected`**                                                   | discard                | **AckRange**  | discarded; **poison handled by the broker `MaxReceiveQueue` policy — there is NO connector DLX**                         |
| **`released`**                                                   | requeue to tail        | **NAckRange** | redelivered with a grown `delivery-count`, `first-acquirer=false`; **increments receive-count** toward `MaxReceiveCount` |
| **`modified{delivery-failed}` / `modified{undeliverable-here}`** | requeue to tail        | **NAckRange** | requeued (no per-consumer exclusion)                                                                                     |
| **nil state** (settled, no outcome)                              | treat as success       | **AckRange**  | removed                                                                                                                  |
| **unknown terminal state**                                       | conservatively requeue | **NAckRange** | requeued — **never silently dropped**                                                                                    |

* A `DISPOSITION` may cover a `first..last` delivery-id range; the connector resolves it against
  the per-link unsettled map, groups by `RefTransactionId`, and emits one `AckRange`/`NAckRange`
  per group. Re-settling an already-settled id is **idempotent**.
* **`rejected` does NOT dead-letter through the connector.** It discards (AckRanges) the message;
  whether a repeatedly-failing message is moved aside is a **broker-side `MaxReceiveQueue` poison
  policy**, not an AMQP-controllable per-link feature.

<Callout type="warn">
  **`released` / `modified` increment the receive-count.** Every NAck-for-redelivery bumps
  `ReceiveCount`, so a message you keep releasing will eventually hit the broker's `MaxReceiveCount`
  cap and be removed even though you never `rejected` it. There is **no requeue-without-increment**.
</Callout>

## Body sections [#body-sections]

A message body must be one of two AMQP sections:

* **`Data`** (binary) — the default; multiple `Data` sections concatenate.
* **`AmqpValue`** (a typed value) — use for typed bodies.

An **empty body is valid** (it becomes an empty `Data` body downstream).

<Callout type="warn">
  **`AmqpSequence` bodies are rejected.** Only `Data` and `AmqpValue` are supported. A message
  carrying an `AmqpSequence` body section gets a `rejected` `DISPOSITION` then a `DETACH` with
  `amqp:not-implemented`. Emit `Data` by default and `AmqpValue` for typed bodies; never
  `AmqpSequence`.
</Callout>

## Confirming a produce [#confirming-a-produce]

When your client is the **sender**, the *server* is the receiver and settles your delivery for
you:

* On broker success it emits a settled `DISPOSITION(role=receiver, accepted)`.
* On failure it emits `rejected{condition}`: broker-not-ready → `amqp:not-allowed`; decode error
  → `amqp:decode-error`; translate error → `amqp:invalid-field`; array/broker error →
  `amqp:internal-error`.
* A **pre-settled** inbound delivery gets **no** disposition; a failure is dropped, logged, and
  counted in `kubemq_amqp10_transfers_in_dropped_total`. If you need to know a produce succeeded,
  do **not** pre-settle — send unsettled and read the server's `DISPOSITION`.

## Durable subscriptions [#durable-subscriptions]

The **events-store** pattern is a durable, replayable subscription. To consume durably, attach a
**receiver** from `events-store/<ch>&#x60; with terminus &#x2A;*`expiry-policy = never`**, a **stable
container-id**, and a &#x2A;*stable link `Name`**. On reconnect with the same identity, the
subscription resumes where it left off.

The durable identity is derived from your `container-id` and link name. The practical
consequence: &#x2A;*to resume, reconnect with the same container-id AND the same link name.** Change
either and you get a *different* durable subscription that starts fresh.

<Callout type="warn">
  Durable subscriptions (and dynamic reply nodes) are **node-local**: a durable subscription
  created on node A is not visible from node B. Cluster-wide *uniqueness* of the durable identity is
  still enforced, but a durable subscriber must reconnect to the **same node** to resume — use
  load-balancer session affinity or a sticky connection.
</Callout>

A durable receiver's start position is set with the link property &#x2A;*`x-opt-kubemq-start`** (it
applies only to `events-store`):

| `x-opt-kubemq-start` value     | Meaning                                                                 |
| ------------------------------ | ----------------------------------------------------------------------- |
| `""` or `new-only`             | only messages published **after** the subscription starts (the default) |
| `first`                        | replay from the **beginning** of stored history                         |
| `last`                         | start from the **most recent** stored message                           |
| `sequence:<n>`                 | start at sequence number `<n>` (1-based, non-negative)                  |
| `time:<RFC3339\|unix-seconds>` | start at a wall-clock time                                              |
| `time-delta:<seconds>`         | start `<seconds>` ago from now                                          |

There is &#x2A;*no "last N by count"** — use `sequence:`, `time:`, or `time-delta:` to bound a replay.
A malformed value (`sequence:abc`, `time:not-a-time`, or an unknown token) is rejected at attach
with `DETACH(amqp:invalid-field)` naming the offending token.

## Redelivery and teardown NAck-all [#redelivery-and-teardown-nack-all]

`released`, `modified`, and **disconnect-with-unsettled** all requeue the delivery to the tail. A
fresh consumer recovers it. A redelivered copy carries a grown `header.delivery-count` and
`first-acquirer=false`, so your consumer can detect a redelivery.

<Callout type="info">
  **On link detach, connection close, or graceful shutdown, every unsettled delivery is
  `NAckRange`'d exactly once** — returned to the queue tail (the client experiences it as
  `released`). A disconnecting at-least-once consumer **loses nothing**: whatever it had not yet
  `accepted` is simply redelivered to the next consumer. This guarantee applies to **queue** consume
  links — events and events-store are pre-settled and have their own footguns (see
  [Flow control](/connectors/amqp/how-to/flow-control)).
</Callout>

## No connector DLX, no visibility timeout [#no-connector-dlx-no-visibility-timeout]

The connector has **no dead-letter exchange and no visibility/redelivery timeout**:

* `rejected` discards via `AckRange`; it does **not** route to a dead-letter destination.
* There is no per-link "make this message invisible for N seconds" verb. Queue receive is
  **destructive, credit-based consume only** — no peek, no browse.
* **Poison handling is entirely broker-side**: the broker's `MaxReceiveQueue` / `MaxReceiveCount`
  policy removes a message redelivered too many times.

Design your consumer accordingly: `accept` on success, `reject` for a permanently-bad message
(discarded, with the broker policy as your only poison backstop), and `release` / `modify` for a
transient failure you want retried — knowing each retry bumps the receive-count toward the
broker's cap.

## Decision guide [#decision-guide]

| You want…                                     | Do this                                                                                                 |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| At-least-once consume (no loss on disconnect) | Consume **unsettled** (default `rcv-settle-mode=first`); `accept` on success; rely on teardown NAck-all |
| At-most-once produce (fire-and-forget)        | Request `snd-settle-mode=settled`                                                                       |
| Confirm a produce succeeded                   | Send **unsettled** and read the server's `DISPOSITION(accepted/rejected)`                               |
| Retry a transient failure                     | `release` (or `modify`) — but each retry **increments the receive-count**                               |
| Discard a permanently-bad message             | `reject` — discarded via AckRange; broker `MaxReceiveQueue` is the poison backstop                      |
| Resume after reconnect                        | events-store + stable `container-id` + stable link name + `expiry-policy=never`                         |

## Related [#related]

<Cards>
  <Card title="Flow control" href="/connectors/amqp/how-to/flow-control" description="Link credit, drain, prefetch, and the two silent data-loss footguns on the pre-settled patterns." />

  <Card title="Queues" href="/connectors/amqp/concepts/queues" description="Competing-consumer work queues — the at-least-once pattern these settlement rules govern." />

  <Card title="Error conditions" href="/connectors/amqp/reference/error-conditions" description="The amqp:* conditions a denied, malformed, or unsupported request closes a link with." />
</Cards>
