KubeMQ
Client SDKsRubyHow-to guidesQueues

Dead Letter Queue

Route failed KubeMQ queue messages to a dead-letter queue in Ruby, diverting poison messages after retries are exhausted.

Which to use

This page is the task-oriented walkthrough: send a message with DLQ routing configured, let it exhaust retries, and consume the diverted message. For the QueueMessagePolicy field reference — max_receive_count/max_receive_queue defaults and 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 inside QueueMessagePolicy: max_receive_count and max_receive_queue. Every failed delivery — a nack_all, 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
  • Ruby SDK installed (gem install kubemq)

Code

main.rb
require 'kubemq'

address = ENV.fetch('KUBEMQ_ADDRESS', 'localhost:50000')
channel = 'ruby-queues.dead-letter-queue'
dlq_channel = 'ruby-queues.dead-letter-queue-dlq'

begin
  client = KubeMQ::QueuesClient.new(address: address, client_id: 'qstream-dlq-example')
  puts "Connected to #{address}"

  policy = KubeMQ::Queues::QueueMessagePolicy.new(max_receive_count: 2, max_receive_queue: dlq_channel)
  msg = KubeMQ::Queues::QueueMessage.new(
    channel: channel,
    metadata: 'fragile-message',
    body: 'may-fail',
    policy: policy
  )
  client.send_queue_message(msg)
  puts "Sent message with max_receive_count=2, DLQ=#{dlq_channel}"

  receiver = client.create_downstream_receiver

  2.times do |attempt|
    request = KubeMQ::Queues::QueuePollRequest.new(channel: channel, max_items: 1, wait_timeout: 3)
    response = receiver.poll(request)
    puts "Attempt #{attempt + 1}: #{response.messages.size} messages"
    response.nack_all if response.messages.any?
  end

  sleep 1
  dlq_request = KubeMQ::Queues::QueuePollRequest.new(channel: dlq_channel, max_items: 1, wait_timeout: 3)
  dlq_response = receiver.poll(dlq_request)
  puts "DLQ messages: #{dlq_response.messages.size}"
  dlq_response.messages.each do |m|
    puts "  DLQ: #{m.metadata}"
    m.ack
  end
rescue KubeMQ::Error => e
  puts "KubeMQ error: #{e.message}"
ensure
  receiver&.close
  client&.close
  puts 'Done'
end

How It Works

  • QueueMessagePolicy with max_receive_count and max_receive_queue configures automatic dead-letter routing.
  • After exceeding the max receive count, the broker moves the message to the dead-letter queue.
  • Review timeouts, channel names, and client IDs before running against shared environments.

Was this page helpful?

On this page