Skip to content

Delivery semantics

Last updated 

Everything on this page is observable with an API key and curl.

At least once

Spooler delivers each message at least once, and a delivery may repeat. Write handlers so a repeat run is harmless: make the effect idempotent, or track handled message ids.

A repeat delivery is not a bug. It happens when:

  • a lease times out while the handler is still working (renew prevents this),
  • the consumer crashed before acking,
  • an ack was sent but the connection dropped before the response,
  • the spool's primary changed — in-flight leases are dropped and their messages become visible again immediately.

Leases

A receive does not remove the message — it leases it. While the lease is valid, no other receiver gets that message; a lease stops being valid when it expires, is settled, or predates a primary change (stale_lease on the next call). Settle it one of three ways: ack removes the message durably, nack returns it for redelivery, renew resets the expiry to now plus the queue's current lease timeout. An unsettled lease times out and the message is redelivered.

Nacks and lease timeouts increment the retry count (Spooler-Retry-Count on the next delivery) — an expired lease is a nack the server performs on the consumer's behalf (the reference calls this the auto-nack). After the queue's maxRetries the message lands in the failure queue. A redelivery caused by a primary change repeats the delivery without incrementing the count: what a consumer does — or fails to do in time — moves a message toward the failure queue; infrastructure events never do.

The lease token is opaque. Never inspect, compare, or construct one; the only valid use is presenting it back.

Ordering

Spooler is not a FIFO queue. Visible messages are offered oldest first — approximately send order — but delivery order is not a contract:

  • a delayed message becomes visible after messages sent later,
  • a redelivery returns after messages sent later were already consumed,
  • concurrent consumers settle in whatever order they finish.

Work that must happen in order belongs in one message, or behind sequencing you own. In exchange, nothing blocks the queue head: a failing message retries and fails alone while everything behind it flows.

Durability

A 201 from send means the message is durably stored — written and synced to disk before the response, surviving a crash or restart. On a spool with replicas it also survives the loss of a node: replicas hold the same messages, and one becomes the new primary. With a single replica there is one copy: losing that node's storage loses the spool's data. A 204 from ack is durable the same way: the message will not come back.

What a primary change does not preserve is in-flight leases — their messages are redelivered (above). A 500 with kind operation_unconfirmed means the write was accepted but its durability is unknown; retry a send only on a queue with a dedup window, carrying the same key — see deduplication.

Retention

A message is kept at most the queue's retentionSeconds after it becomes visible (a send delay never eats into retention). Past the window it is discarded; expiredTotal in the queue stats counts these. A growing rate means messages age out before consumers reach them.