KubeMQ
Client SDKsC#How-to guidesQueues

Ack & Reject

Selectively acknowledge or reject individual KubeMQ queue messages using the C# SDK.

Overview

Ack and reject give you per-message control over queue delivery instead of an all-or-nothing batch outcome. When PollAsync fetches a batch with AutoAck = false, each message stays locked on the broker — invisible to other consumers — until the consumer explicitly settles it. That's what you need when one bad record in a batch shouldn't take the rest down with it.

Settlement happens through calls on the received message: AckAsync(), which permanently removes it from the queue, and NackAsync(), which returns it to the queue immediately for another consumer. A third option, ReQueueAsync(), also returns the message but increments its receive count — pair that with MaxReceiveCount and MaxReceiveQueue for dead-letter routing after repeated failures.

Gotchas: an unsettled message isn't gone — it snaps back to the queue once the visibility timeout expires, so a slow consumer looks identical to a rejecting one; settle every message before that deadline, and never assume a batch is fully processed until you've called AckAsync(), NackAsync(), or ReQueueAsync() on each one individually.

Prerequisites

  • KubeMQ server running on localhost:50000
  • C# SDK installed (dotnet add package KubeMQ.SDK.CSharp)

Code

Program.cs
// KubeMQ .NET SDK — Queues: Acknowledge, Nack, and Requeue
//
// This example demonstrates different message settlement options:
// - AckAsync(): Successfully processed, remove from queue
// - NackAsync(): Processing failed, reject message
// - ReQueueAsync(): Return message to queue for retry
//
// Uses QueueDownstreamReceiver.PollAsync for transactional message settlement.
//
// Prerequisites:
//   - KubeMQ server running on localhost:50000
//   - dotnet run

using KubeMQ.Sdk.Client;
using KubeMQ.Sdk.Queues;
using System.Text;

await using var client = new KubeMQClient(new KubeMQClientOptions
{
    ClientId = "csharp-queues-ack-reject-client",
});
await client.ConnectAsync();

Console.WriteLine("Connected to KubeMQ server");

// Send test messages
for (var i = 1; i <= 3; i++)
{
    await client.SendQueueMessageAsync(new QueueMessage
    {
        Channel = "csharp-queues.ack-reject",
        Body = Encoding.UTF8.GetBytes($"Message #{i}")
    });
}

Console.WriteLine("Sent 3 messages");

// Receive via downstream receiver (supports manual settlement)
await using var receiver = await client.CreateQueueDownstreamReceiverAsync();

var batch = await receiver.PollAsync(new QueuePollRequest
{
    Channel = "csharp-queues.ack-reject",
    MaxMessages = 3,
    WaitTimeoutSeconds = 10,
    AutoAck = false,
});

foreach (var msg in batch.Messages)
{
    var body = Encoding.UTF8.GetString(msg.Body.Span);
    Console.WriteLine($"Processing: {body}");

    if (body.Contains("#1"))
    {
        await msg.AckAsync();
        Console.WriteLine("  -> Acknowledged (success)");
    }
    else if (body.Contains("#2"))
    {
        await msg.NackAsync();
        Console.WriteLine("  -> Nacked (rejected)");
    }
    else
    {
        await msg.ReQueueAsync();
        Console.WriteLine("  -> Requeued (will retry)");
    }
}

Console.WriteLine("Done.");

How It Works

  • PollAsync with AutoAck = false locks the batch for this receiver. Each message must be explicitly settled or it returns to the queue after the visibility timeout.
  • AckAsync() removes the message permanently — use after successful processing.
  • NackAsync() rejects the message and returns it to the queue immediately for another consumer or retry attempt.
  • ReQueueAsync() also returns the message to the queue, which increments its receive count. Pair this with MaxReceiveCount and MaxReceiveQueue to implement dead-letter routing after a configurable number of failures.

Was this page helpful?

On this page