# Batch Send (/sdks/csharp/how-to/queues/batch-send)



## Overview [#overview]

**Batch send** groups several queue messages into one call instead of sending them one at a time. Reach for it when publishing many related items together — importing records, fanning out a set of jobs, replaying a backlog — since sending each message individually pays a full round trip per message, while batching amortizes that cost across the whole set.

It works by building an `IEnumerable<QueueMessage>`, then passing the collection to `client.SendQueueMessagesAsync(messages)` in a single gRPC call. The broker assigns each message its own `MessageId` and persists them independently, reporting any partial failure per message in the result list.

**Gotchas:** batching isn't atomic — the broker can accept some messages and reject others in the same call, so always check each message's result instead of trusting an overall success; a batch is still one bounded request, so it doesn't help continuous, open-ended publishing (use `SendQueueMessagesUpstreamAsync`, which streams instead of batching, for that); and very large batches raise the size and latency of that single call, so there's a practical ceiling before splitting into multiple batches pays off.

## 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: Batch Send and Receive
//
// This example demonstrates sending and receiving batches of queue messages.
// Batch operations reduce network round trips for high-throughput scenarios.
// 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-batch-send-client",
});
await client.ConnectAsync();

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

// Send a batch of messages
var messages = Enumerable.Range(1, 10).Select(i => new QueueMessage
{
    Channel = "csharp-queues.batch-send",
    Body = Encoding.UTF8.GetBytes($"Batch item #{i}")
}).ToList();

var batchResult = await client.SendQueueMessagesAsync(messages);
Console.WriteLine($"Sent batch of {messages.Count} 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.batch-send",
    MaxMessages = 10,
    WaitTimeoutSeconds = 10,
    AutoAck = false,
});

Console.WriteLine($"Received {batch.Messages.Count} messages:");

foreach (var msg in batch.Messages)
{
    Console.WriteLine($"  {Encoding.UTF8.GetString(msg.Body.Span)}");
    await msg.AckAsync();
}

Console.WriteLine("Done.");

```

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

* `SendQueueMessagesAsync(IEnumerable<QueueMessage>)` sends all messages in a single gRPC call, reducing round-trip overhead compared to calling `SendQueueMessageAsync` in a loop.
* Each message in the batch is independent — the broker assigns a unique `MessageId` to each and persists them individually. A partial failure is reported per-message in the result list.
* The downstream receiver is created once and reused for the poll; `AutoAck = false` with per-message `AckAsync()` provides at-least-once delivery semantics.
* For very high throughput, consider `SendQueueMessagesUpstreamAsync` (stream-based) which avoids the batch size limits of the single-call API.

## Related [#related]

* [Pattern overview](/learn/queues/getting-started)
* [C# SDK Reference](/sdks/csharp/reference)
* [Send & Receive](/sdks/csharp/tutorials/send-receive)
* [Ack All](/sdks/csharp/how-to/queues/ack-all)
