Skip to content

Publisher Confirms

A plain publish is fire-and-forget — literally

Section titled “A plain publish is fire-and-forget — literally”

Here is an uncomfortable fact about a basic publish: by default it returns nothing. Your client writes the message to the socket and moves on. If the broker was overloaded, the connection dropped at that instant, or the message was rejected, you never find out. The message is simply gone, and your code thinks it succeeded.

For a background analytics event, maybe that’s fine. For “the customer paid us”, it absolutely is not. Publisher confirms close this gap.

Publisher confirms: the broker acks your publish

Section titled “Publisher confirms: the broker acks your publish”

When you put a channel into confirm mode, the broker sends back an acknowledgement for every message once it has taken responsibility for it (routed it to all matching durable queues, and — for persistent messages — written it to disk). You get an ack (the broker has it) or, rarely, a nack (the broker could not take it).

sequenceDiagram
  participant P as Publisher
  participant B as Broker
  P->>B: publish (confirm mode)
  B->>B: route + persist
  B-->>P: ack (I have it)
  Note over P: safe to consider it sent
  P->>B: publish (bad case)
  B-->>P: nack (could not accept)
  Note over P: retry or alert
A publish with confirms enabled

Confirms are asynchronous: the broker streams acks back as it processes messages, so you don’t have to block after each publish. High-throughput publishers keep a window of unconfirmed messages in flight and only treat a message as durable once its ack arrives.

// amqplib: a confirm channel gives per-message callbacks
const channel = await conn.createConfirmChannel();
channel.publish('orders', 'order.created', Buffer.from(body), { persistent: true },
(err) => {
if (err) console.error('NACK — message not confirmed, retry:', err);
else console.log('ACK — broker has the message');
});

The mandatory flag: catch unroutable messages

Section titled “The mandatory flag: catch unroutable messages”

Confirms tell you the broker got the message — but a message routed to zero queues is still, from the broker’s view, handled successfully. It just vanishes. If you published order.created but no queue was bound for it (a typo, a missing binding), the message is dropped and you’d never know.

The mandatory flag fixes this: if a mandatory message can’t be routed to any queue, the broker returns it to the publisher instead of dropping it. Listen for returned messages and you’ll catch misrouting immediately.

Together, confirms + mandatory answer both questions: did the broker accept it? and did it actually reach a queue?

Confirms aren’t free. Waiting for an ack after every single publish serializes your publisher and tanks throughput. The healthy middle ground:

  • Async confirms with a window — keep N messages in flight, handle acks as they stream back. Near-full throughput, full safety.
  • Batch confirms — publish a batch, wait for the batch to confirm. Simpler, slightly less throughput.
  • Publish-and-wait per message — safest to reason about, slowest. Only for low-volume, critical publishes.
What happens to a plain publish (no confirms) if the broker fails to accept it?
What does a publisher confirm tell you?
What does the mandatory flag add on top of confirms?
Why not just wait for a confirm after every single publish?