# Expiration Policy (/sdks/ruby/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, `expiration_seconds` on `QueueMessagePolicy` 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`
* Ruby SDK installed (`gem install kubemq`)

## Code [#code]

```ruby title="main.rb"
require 'kubemq'

address = ENV.fetch('KUBEMQ_ADDRESS', 'localhost:50000')
channel = 'ruby-queues.expiration-policy'

begin
  client = KubeMQ::QueuesClient.new(address: address, client_id: 'qstream-expire-example')
  puts "Connected to #{address}"

  policy = KubeMQ::Queues::QueueMessagePolicy.new(expiration_seconds: 3)
  msg = KubeMQ::Queues::QueueMessage.new(
    channel: channel,
    metadata: 'expires-in-3s',
    body: 'ephemeral-data',
    policy: policy
  )
  result = client.send_queue_message(msg)
  puts "Sent message with expiration=3s: id=#{result.id}"

  receiver = client.create_downstream_receiver
  request = KubeMQ::Queues::QueuePollRequest.new(channel: channel, max_items: 1, wait_timeout: 1)
  response = receiver.poll(request)
  puts "Immediate poll: #{response.messages.size} messages"
  response.messages.each(&:ack)

  puts 'Waiting for expiration...'
  sleep 4

  response2 = receiver.poll(request)
  puts "After expiration: #{response2.messages.size} messages (should be 0)"
rescue KubeMQ::Error => e
  puts "KubeMQ error: #{e.message}"
ensure
  receiver&.close
  client&.close
  puts 'Done'
end
```

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

* `expiration_seconds` in `QueueMessagePolicy` sets the message TTL — the message is automatically removed after expiry.
* If not consumed before the TTL, the message is discarded by the broker.
* Review timeouts, channel names, and client IDs before running against shared environments.

## Related [#related]

* [Pattern overview](/learn/queues/getting-started)
* [Ruby SDK Reference](/sdks/ruby/reference)
* [Delay Policy](/sdks/ruby/how-to/queues/delay-policy)
* [Dead Letter Policy](/sdks/ruby/how-to/queues/dead-letter-policy)
