# Expiration Policy (/sdks/csharp/how-to/queues/expiration-policy)



## Overview [#overview]

An **expiration policy** puts a hard time limit on how long a queue message may sit unconsumed. It solves a different problem than a dead-letter policy — this isn't about messages that fail processing, it's about messages that go *stale*: a price quote, a one-time code, a cache-invalidation signal, where late delivery is actively wrong, not just delayed. Instead of every consumer re-checking timestamps itself, the deadline lives on the message and the broker enforces it.

At the API level, `ExpirationSeconds = 30` attaches a per-message TTL when you build the `QueueMessage`, and the clock starts the moment the broker accepts it via `SendQueueMessageAsync`, not when a consumer picks it up. Let the TTL elapse unconsumed and the broker silently removes it — a later poll just comes back empty, no error, no trace.

**Gotchas:** expiration is silent — no DLQ routing, no event, just a message that vanishes — so pair it with monitoring if you need visibility into how much work is being dropped. The timer starts at send time, not when a consumer picks up the work, so a message can expire mid-backlog even while a consumer is actively polling. And setting the TTL too short for your real consumer lag just turns ordinary slowness into silent data loss.

## 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 — QueuesStream: Message Expiration Policy
//
// This example demonstrates sending a queue message with an expiration policy
// and receiving it via the downstream receiver API. Messages that are not consumed
// within the expiration window are automatically discarded.
//
// 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-expiration-policy-client",
});
await client.ConnectAsync();

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

var msg = new QueueMessage
{
    Channel = "csharp-queues.expiration-policy",
    Body = Encoding.UTF8.GetBytes("Expires in 30s"),
    ExpirationSeconds = 30,
};

var sendResult = await client.SendQueueMessageAsync(msg);
Console.WriteLine($"Sent: {sendResult.MessageId}, IsError={sendResult.IsError}");

await using var receiver = await client.CreateQueueDownstreamReceiverAsync();

// Receive via downstream receiver before expiration
var batch = await receiver.PollAsync(new QueuePollRequest
{
    Channel = "csharp-queues.expiration-policy",
    MaxMessages = 1,
    WaitTimeoutSeconds = 5,
    AutoAck = false,
});

if (batch.HasMessages)
{
    Console.WriteLine($"Received before expiration: {Encoding.UTF8.GetString(batch.Messages[0].Body.Span)}");
    await batch.AckAllAsync();
}
else
{
    Console.WriteLine("No messages received (may have expired).");
}

Console.WriteLine("Done.");

```

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

* `ExpirationSeconds = 30` sets a TTL on the message. If no consumer polls and acknowledges the message within 30 seconds of it being enqueued, the broker discards it silently.
* The downstream receiver polls before the TTL window expires, so the message is found and acknowledged.
* `sendResult.IsError` indicates whether the broker accepted the message. A false value confirms it was successfully stored.
* Use expiration for time-sensitive work items (e.g., notifications, rate-limited jobs) where stale messages are worse than dropped messages.

## 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)
