# Send & Receive (/sdks/csharp/tutorials/send-receive)



## Overview [#overview]

Queue send/receive is the foundational operation for guaranteed-delivery, point-to-point messaging: you reach for it whenever work needs to survive past the moment it's created and be handled by exactly one consumer, not broadcast to every subscriber. Unlike pub/sub, a queued message sits durably on the broker until something pulls it, so the producer and consumer never need to be online at the same time — a slow or offline worker adds latency, it doesn't drop the message.

This tutorial builds the smallest possible version of that round trip: `SendQueueMessageAsync` enqueues a message on a channel, and a `QueueDownstreamReceiver`'s `PollAsync` pulls it back within a bounded `WaitTimeoutSeconds`. With `AutoAck = false`, settlement is manual — each message must be explicitly confirmed with `AckAsync()`, rejected with `NackAsync()`, or returned to the queue with `ReQueueAsync()` once your handler decides its outcome.

**Gotchas:** if your handler crashes before calling `AckAsync()`, the message stays locked to that receiver only until the broker's visibility timeout expires, after which it reappears for redelivery — write handlers that tolerate seeing the same message twice. Polling an empty queue isn't an error; `PollAsync` just blocks up to `WaitTimeoutSeconds` and returns a batch with no messages. And the downstream receiver is meant to be created once and reused across many `PollAsync` calls, not recreated per poll.

## Prerequisites [#prerequisites]

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

## Code [#code]

```csharp title="Program.cs"
// KubeMQ .NET SDK — Queues: Basic Send and Receive
//
// This example demonstrates sending a queue message and receiving it with acknowledgment.
// Queue messages are pull-based and processed by exactly one consumer.
// 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;

// TODO: Replace with your KubeMQ server address
await using var client = new KubeMQClient(new KubeMQClientOptions
{
    ClientId = "csharp-queues-send-receive-client",
});
await client.ConnectAsync();

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

// Send a queue message
var sendResult = await client.SendQueueMessageAsync(new QueueMessage
{
    Channel = "csharp-queues.send-receive",
    Body = Encoding.UTF8.GetBytes("Process order #1234"),
    Tags = new Dictionary<string, string> { ["priority"] = "high" }
});

Console.WriteLine($"Sent message: {sendResult.MessageId}");

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

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

if (batch.HasMessages)
{
    foreach (var msg in batch.Messages)
    {
        Console.WriteLine($"Received: {Encoding.UTF8.GetString(msg.Body.Span)}");
        await msg.AckAsync();
        Console.WriteLine("Message acknowledged");
    }
}
else
{
    Console.WriteLine("No messages received");
}

Console.WriteLine("Done.");

// Expected output:
// Connected to KubeMQ server
// Sent message: <message-id>
// Received: Process order #1234
// Message acknowledged
// Done.

```

## How It Works [#how-it-works]

* `SendQueueMessageAsync` delivers the message to the broker, which persists it and assigns a unique `MessageId`. The returned `sendResult` confirms delivery.
* `CreateQueueDownstreamReceiverAsync` opens a long-lived gRPC stream to the broker. One receiver instance can be reused across many `PollAsync` calls — create it once and dispose it with `await using`.
* `PollAsync` with `AutoAck = false` locks the batch for this receiver. Each message must be explicitly settled with `AckAsync()`, `NackAsync()`, or `ReQueueAsync()`.
* `msg.AckAsync()` removes the message from the queue permanently. Unanswered messages return to the queue after the broker's visibility timeout.

## Related [#related]

* [Pattern overview](/learn/queues/getting-started)
* [C# SDK Reference](/sdks/csharp/reference)
* [Ack All](/sdks/csharp/how-to/queues/ack-all)
* [Ack & Reject](/sdks/csharp/how-to/queues/ack-reject)
