Dead Letter Policy
Field-level reference for the maxReceiveCount and maxReceiveQueue policy fields that configure dead-letter routing in the Node.js SDK.
Which to use
This page is the field-level reference for the policy object's maxReceiveCount/maxReceiveQueue fields, shown here via the queue-stream API. For the end-to-end task — sending a message, exhausting retries, and consuming from the resulting DLQ — see Dead Letter Queue.
Overview
maxReceiveCount and maxReceiveQueue are fields on the policy object passed to createQueueMessage(). Together they define a message's dead-letter routing — set once at send time, they travel with the message; the producer, not the consumer, decides the retry ceiling. This page shows the fields via the queue-stream (streamQueueMessages) API; the fields themselves are identical to the standard queue API.
Gotchas: the receive count increments on every failed delivery — an explicit nackAll(), an expired transaction, or a visibility timeout — not just deliberate rejections, so set the ceiling above your normal retry budget. The dead-letter channel is an ordinary queue with no special behavior: nothing drains it for you, so monitor it and build a reprocessing path or failures pile up silently.
Prerequisites
- KubeMQ server running on
localhost:50000 - Node.js SDK installed (
npm install kubemq-js)
Code
/**
* Example: Queue Stream — Dead-Letter Queue Policy
*
* Demonstrates the dead-letter queue (DLQ) pattern via the stream API.
* A message is sent with maxReceiveCount=3 and maxReceiveQueue pointing
* to a DLQ channel. When the message is rejected (nacked) more than
* 3 times, it is automatically moved to the DLQ.
*
* Prerequisites:
* - KubeMQ server running on localhost:50000
*
* Run: npx tsx examples/queues-stream/dead-letter-policy.ts
*/
import { KubeMQClient, createQueueMessage } from 'kubemq-js';
async function main(): Promise<void> {
const client = await KubeMQClient.create({
address: 'localhost:50000',
clientId: 'js-queues-stream-dead-letter-policy-client',
});
try {
const upstream = client.createQueueUpstream();
await upstream.send([
createQueueMessage({
channel: 'js-queues-stream.dead-letter-policy',
body: 'Flaky job that will fail',
policy: {
maxReceiveCount: 3,
maxReceiveQueue: 'js-queues-stream.dead-letter-policy-dlq',
},
}),
]);
console.log('Sent message with DLQ policy (maxReceiveCount=3)');
for (let attempt = 1; attempt <= 4; attempt++) {
const handle = client.streamQueueMessages({
channel: 'js-queues-stream.dead-letter-policy',
maxMessages: 1,
waitTimeoutSeconds: 3,
});
await new Promise<void>((resolve) => {
handle.onMessages((msgs) => {
if (msgs.length === 0) {
console.log(`Attempt ${attempt}: no messages (moved to DLQ)`);
} else {
const first = msgs[0]!;
console.log(
`Attempt ${attempt}: received "${new TextDecoder().decode(first.body)}" (receiveCount=${first.receiveCount}) — rejecting`,
);
handle.nackAll();
}
setTimeout(() => {
handle.close();
resolve();
}, 500);
});
handle.onError(() => {
handle.close();
resolve();
});
});
}
console.log('\nChecking dead-letter queue...');
const dlqHandle = client.streamQueueMessages({
channel: 'js-queues-stream.dead-letter-policy-dlq',
maxMessages: 10,
waitTimeoutSeconds: 3,
autoAck: true,
});
dlqHandle.onMessages((msgs) => {
console.log(`DLQ contains ${msgs.length} message(s):`);
for (const msg of msgs) {
console.log(` "${new TextDecoder().decode(msg.body)}" (receiveCount=${msg.receiveCount})`);
}
});
dlqHandle.onError((err) => {
console.error('DLQ error:', err.message);
});
await new Promise((resolve) => setTimeout(resolve, 3000));
dlqHandle.close();
upstream.close();
} finally {
await client.close();
}
}
main().catch(console.error);
Field reference
maxReceiveCount(number) — the number of failed receives allowed before the broker reroutes the message.maxReceiveCount: 3means the message survives 3 rejections/nacks; the next failed receive triggers the move. Each attempt'sreceiveCounton the received message reflects the current count.maxReceiveQueue(string) — the destination channel name for diverted messages. Leaving it unset means messages that exceedmaxReceiveCountare discarded rather than rerouted.- Both fields are part of the
policyobject passed tocreateQueueMessage()at send time — there's no way to change the policy after the message is queued.
For the walkthrough of sending, exhausting retries, and consuming from the resulting DLQ, see Dead Letter Queue.
Related
- Dead Letter Queue — task-oriented walkthrough for DLQ routing
- Node.js SDK Reference
- Send & Receive
- Ack All
Was this page helpful?