Dead Letter Queue
Route failed KubeMQ queue messages to a dead-letter queue using the Go SDK to isolate poison messages.
Which to use
This is the task-oriented guide for routing failed messages to a dead-letter queue via the single-send API. For the stream-based SetMaxReceiveCount/SetMaxReceiveQueue policy option and its edge cases, see Dead Letter Policy.
Overview
A dead-letter queue (DLQ) gives a poison message somewhere to go instead of looping through consumers forever. When a message keeps failing — a malformed payload, a downstream outage, a handler bug — retrying it forever wastes consumer cycles and blocks everything behind it. A DLQ takes that decision out of your hands: past a set number of failed attempts, the broker diverts the message to a separate channel instead of retrying it again.
Routing runs on two settings attached to the message: SetMaxReceiveCount() and SetMaxReceiveQueue(). Every failed delivery — a nack, a reject, or an expired visibility window — increments the receive count; past the threshold, the broker reroutes the message to the DLQ instead of redelivering it. The DLQ itself is an ordinary queue, consumed like any other channel.
Gotchas: the DLQ doesn't drain itself — a dedicated consumer must watch it. The count increments on any failed delivery, not just deliberate rejections — a slow consumer that lets the visibility window lapse counts the same as an explicit nack. A typo in the DLQ channel name quietly creates an unrelated channel instead of failing loudly.
Prerequisites
- KubeMQ server running on
localhost:50000 - Go SDK installed (
go get github.com/kubemq-io/kubemq-go/v2)
Code
// Example: queues/dead-letter-queue
//
// Demonstrates configuring a dead-letter queue (DLQ) for messages that
// exceed the maximum receive count. After the max attempts, messages
// are moved to the specified DLQ channel.
//
// Channel: go-queues.dead-letter-queue
// Client ID: go-queues-dead-letter-queue-client
//
// Run with a KubeMQ server on localhost:50000
// (see https://docs.kubemq.io/deploy).
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/kubemq-io/kubemq-go/v2"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
client, err := kubemq.NewClient(ctx,
kubemq.WithAddress("localhost", 50000),
kubemq.WithClientId("go-queues-dead-letter-queue-client"),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
channel := "go-queues.dead-letter-queue"
dlqChannel := channel + ".dlq"
// Send a message with dead-letter queue configuration.
// After 3 failed receive attempts, the message is moved to the DLQ.
dlqMsg := kubemq.NewQueueMessage().
SetChannel(channel).
SetBody([]byte("message with DLQ")).
SetMaxReceiveCount(3).
SetMaxReceiveQueue(dlqChannel)
result, err := client.SendQueueMessage(ctx, dlqMsg)
if err != nil {
log.Fatal(err)
}
if result.IsError {
log.Fatalf("Send failed: %s", result.Error)
}
fmt.Printf("DLQ message sent: id=%s (max receives=3, dlq=%s)\n",
result.MessageID, dlqChannel)
}
How It Works
kubemq.NewQueueMessage().SetMaxReceiveCount(3).SetMaxReceiveQueue(dlqChannel)configures a dead-letter policy inline on the message at send time; no separate channel-level configuration is required.- When a consumer polls and nacks the message (or lets the transaction expire) more than 3 times, the broker automatically moves it to
dlqChannelinstead of redelivering it. - The DLQ channel name is
go-queues.dead-letter-queue.dlq— it is a regular queue that can be polled for investigation or reprocessing. result.IsErroron the send response confirms the message was accepted with its DLQ policy; the policy is stored with the message in the broker.
Related
Was this page helpful?