Skip to content
sproutboatDocs
Esc
↑↓navigate↵open⌘Jpreview
On this page

Queues

Send messages with env.NAME.send, consume them with a queue handler.

Configuration

"queues": ["JOBS"]

Bare names are auto-provisioned on deploy; see Storage resources.

Sending and consuming

A producer calls send() from any handler. A consumer is the queue(batch, env, ctx) method on the same default export as fetch:

interface Job {
  type: "email";
  to: string;
}

export default {
  async fetch(request, env, ctx) {
    const job = (await request.json()) as Job;
    await env.JOBS.send(job, { delaySeconds: 30 });
    await env.JOBS.sendBatch([{ body: job }, { body: job, delaySeconds: 5 }]);
    return new Response("queued\n", { status: 202 });
  },

  async queue(batch, env, ctx) {
    for (const message of batch.messages) {
      const response = await fetch("https://mail.example.com/send", {
        method: "POST",
        body: JSON.stringify({ to: message.body.to, attempt: message.attempts }),
      });
      if (response.ok) message.ack();
      else message.retry({ delaySeconds: 30 });
    }
  },
} satisfies ExportedHandler<Env, Job>;

send returns once the message is stored; delivery happens separately. The body is sent as JSON by default and arrives already parsed. contentType can also be "text", "bytes" (arrives as an ArrayBuffer) or "v8" (structured clone).

Delivery

From compatibility date 2026-10-31, queues behave like Cloudflare Queues:

  • send() and sendBatch() return a Promise of { metadata: { metrics } }, the queue’s backlog after the send. env.JOBS.metrics() reads it.
  • A message the handler neither acks nor retries counts as acked. If queue() throws, the messages it did not ack are delivered again.
  • A retried message is delivered again at once, up to 3 times (4 attempts in all), then dropped, or sent to the queue’s dead letter queue when it has one. message.retry({ delaySeconds }) waits first.
  • Messages arrive in batches of up to max_batch_size: 10 by default, at most 100.

Before 2026-10-31, send() answers at once with nothing, a message the handler throws on counts as acked, a retried message is delivered again after about 5 seconds, and after 5 attempts it is dropped. await works at both, so the code above is correct at every date.

To name the queue, set a batch size, a dead letter queue or the retry settings, give the entry in queues as an object. The settings are named as in Wrangler’s queues.consumers:

"queues": [
  { "binding": "JOBS", "queue": "jobs", "max_batch_size": 10, "max_retries": 3, "dead_letter_queue": "jobs-dlq" },
  { "binding": "JOBS_DLQ", "queue": "jobs-dlq" }
]

Methods

Method Signature Notes
send (body, { delaySeconds?, contentType? }) One message.
sendBatch (messages[], { delaySeconds? }), each { body, delaySeconds?, contentType? } Several messages.
metrics () { backlogCount, backlogBytes, oldestMessageTimestamp? }.
message.ack() Mark a message handled.
message.retry() ({ delaySeconds? }) Deliver it again later. The first of ack() and retry() wins.
batch.ackAll(), batch.retryAll() The same for every message in the batch.

Each message also has id, timestamp (a Date from 2026-10-31, epoch milliseconds before) and attempts; a batch has queue and metadata.

  • The handler: the full fetch, scheduled and queue export

Was this page helpful?