Skip to main content
A queue moves work out of the request. A producer sends messages, and the queue handler of your App receives them in batches, with retries and an optional dead-letter queue. Use a queue for email, webhooks, upload processing, and fan-out. celld, the Judt runtime, implements the Cloudflare Queues API, with one consumer for each queue in your App. For the features that celld doesn’t support, see Differences from Cloudflare.

Configure

Add a queues object to the wrangler.json file with a producer and a consumer:
wrangler.json
A producer entry accepts the following keys:
  • binding: the name of the binding in env.
  • queue: the queue name.
  • delivery_delay: a delay, in seconds, for each message that the binding sends. The maximum is 86,400.
The following table lists the keys of a consumer entry: Deploy refuses other keys. A consumer needs a main entry module that exports a queue handler. Each queue has one consumer. A dead-letter queue is an ordinary queue. You can add a consumer for it, or keep its messages for inspection.

Send messages

To send a message, call the send method on the producer binding. To send many messages in one call, use the sendBatch method:
Keep messages small. Send IDs, and keep large data in D1 or R2. If a list has more than 100 items, divide it into several sendBatch calls. A queue accepts a maximum of 256 concurrent producer calls. After that, the call throws. Catch the error and try again after a short delay.

Consume messages

The queue handler receives a batch. We recommend that you settle each message: call ack() when the work succeeds, and retry() when it fails.
index.js
Each message has the following members:
  • id: the message ID. It doesn’t change between deliveries.
  • timestamp: the time the message was sent.
  • body: the message content.
  • attempts: the delivery count, starting at 1.
  • ack(): marks the message as done.
  • retry({ delaySeconds }): returns the message to the queue.
The batch has batch.queue, the queue name. The batch.ackAll() and batch.retryAll() methods apply to all messages that aren’t settled. The handler’s result decides what happens to messages that you don’t settle:
  • If the handler returns without any settle calls, then celld acknowledges all messages.
  • If the handler throws, then all messages that aren’t acknowledged go back to the queue.
celld adds no backoff of its own. A retry waits for retry_delay, or for the delaySeconds value that you pass to retry(). The preceding example computes an exponential backoff from msg.attempts. After the number of retries in max_retries, the message goes to the dead_letter_queue. If the consumer has no dead-letter queue, then celld deletes the message.

Make handlers safe to repeat

Delivery is at least once. A message can run two times, for example after a crash before ack(). Make each handler idempotent. Use msg.id as the idempotency key, for example in a UNIQUE column in D1, or in the Idempotency-Key header of an external API. Delivery has no order. Retries, delays, and concurrent batches change the order of messages. If order matters, put a sequence number in each message, or send the work through a Durable Object.

Fan out from a Cron Trigger

A cron handler that processes many records can run past its time. Instead, let the cron handler select IDs and send them to a queue. The consumer does the slow work:
index.js
The slot field makes each job idempotent for each run, because controller.scheduledTime is the same on a retry. For more information, see Cron Triggers.

Limits

The following table lists the limits of a queue: Each queue has one writer. For high volume, use more queues.

Differences from Cloudflare

The following Cloudflare Queues features aren’t available on celld:
  • Pull consumers
  • The Queues HTTP API
  • Consumers in other scripts
  • R2 event notifications
Message retention is fixed at four days.