# Delayed Messages (/sdks/ruby/how-to/queues/delayed-messages)



<Callout type="info" title="Which to use">
  This is the task-oriented guide for sending delayed messages — send one with a delivery delay, confirm it's hidden, then receive it once the delay expires. For the `QueueMessagePolicy` / `delay_seconds` object reference, see [Delay Policy](./delay-policy).
</Callout>

## Overview [#overview]

A **delivery delay** holds a queue message out of consumers' reach for a fixed window after it's sent — the message is accepted and persisted immediately, but invisible to pollers until the delay expires. It's the building block for scheduled work — a reminder to fire in an hour, a retry with back-off, a task queued for off-peak processing — without standing up a separate scheduler or cron service.

Set it with `delay_seconds` on `QueueMessagePolicy` before sending; the broker does the waiting. A poll via the downstream receiver against the channel before the delay elapses simply returns zero messages — it isn't hidden in a separate place, it's the same queue, just not yet eligible for delivery. Once the delay window passes, the next poll retrieves it normally.

**Gotchas:** the delay is set once at send time, per message, and can't be extended or shortened afterward — if you need a different wait, send a new message. A long delay still counts as an in-flight, persisted message, so it survives a broker restart, but it also occupies queue storage for the whole waiting period. Don't confuse this with a *visibility timeout* after delivery — that's a separate mechanism for redelivery on failed acknowledgment, not initial availability.

## 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.delayed-messages'

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

  policy = KubeMQ::Queues::QueueMessagePolicy.new(delay_seconds: 3)
  msg = KubeMQ::Queues::QueueMessage.new(
    channel: channel,
    metadata: 'delayed-3s',
    body: 'process-later',
    policy: policy
  )
  result = client.send_queue_message(msg)
  puts "Sent message with delay=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 (should be 0)"

  puts 'Waiting for delay to expire...'
  sleep 4

  response2 = receiver.poll(request)
  puts "After delay: #{response2.messages.size} message(s)"
  response2.messages.each do |m|
    puts "  #{m.metadata}: #{m.body}"
    m.ack
  end
rescue KubeMQ::Error => e
  puts "KubeMQ error: #{e.message}"
ensure
  receiver&.close
  client&.close
  puts 'Done'
end
```

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

* `delay_seconds` in `QueueMessagePolicy` makes the message invisible until the delay expires.
* Polling before the delay returns no messages.
* 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)
* [Expiration Policy](/sdks/ruby/how-to/queues/expiration-policy)
