# Expiration Policy (/sdks/elixir/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, `KubeMQ.QueuePolicy.new(expiration_seconds: 5)` attaches a per-message TTL when you build the `QueueMessage`, and the clock starts the moment the broker accepts it via `send_queue_message`, 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`
* Elixir SDK installed (`{:kubemq, "~> 1.0"}` in mix.exs)

## Code [#code]

```elixir title="main.exs"
channel = "elixir-queues.expiration-policy"
{:ok, client} = KubeMQ.Client.start_link(address: "localhost:50000", client_id: "elixir-qs-expire")

msg = KubeMQ.QueueMessage.new(
  channel: channel,
  body: "Expires in 5 seconds",
  policy: KubeMQ.QueuePolicy.new(expiration_seconds: 5)
)

{:ok, result} = KubeMQ.Client.send_queue_message(client, msg)
IO.puts("Message sent with 5s expiration. Expires at: #{result.expiration_at}")

case KubeMQ.Client.receive_queue_messages(client, channel,
       max_messages: 1,
       wait_timeout: 2_000,
       is_peek: true
     ) do
  {:ok, result} ->
    IO.puts("Before expiry: #{result.messages_received} message(s) available")

  {:error, _} ->
    IO.puts("No messages")
end

IO.puts("Waiting 6 seconds for expiration...")
Process.sleep(6_000)

case KubeMQ.Client.receive_queue_messages(client, channel,
       max_messages: 1,
       wait_timeout: 2_000
     ) do
  {:ok, result} ->
    IO.puts("After expiry: #{result.messages_received} messages (expected 0)")
    IO.puts("Messages expired: #{result.messages_expired}")

  {:error, _} ->
    IO.puts("No messages (expired as expected)")
end

KubeMQ.Client.close(client)
```

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

* `QueuePolicy.new(expiration_seconds: 5)` sets a 5-second TTL on the message
* After the TTL expires, the message is removed from the queue automatically
* `result.messages_expired` shows how many messages expired during the receive window

## Related [#related]

* [Delay Policy](/sdks/elixir/how-to/queues/delay-policy)
* [Dead Letter Queue](/sdks/elixir/how-to/queues/dead-letter-queue)
