Queues
Use queues for work that can happen later, such as sending emails or processing uploads. Define a consumer in queues/, then send it typed messages from your app.
On this pageDefine a queue consumer · Send typed messages · Acknowledge or retry messages
Defining queues
This step creates a consumer in queues/. .ts, .mts, .js and .mjs files work. The path sets the queue name: queues/emails.ts creates emails, and queues/order/notifications.ts creates order-notifications. Each queue must have a unique name.
The name must follow Cloudflare's rules: 1–63 characters of letters, digits and -, starting and ending with a letter or digit.
Export a default handler wrapped in defineQueue<T>. T defines the message body for both the consumer and calls to send():
// queues/emails.ts
import { defineQueue } from 'void';
interface Message {
to: string;
subject: string;
}
export default defineQueue<Message>(async (batch, env) => {
for (const msg of batch.messages) {
console.log(`Send to ${msg.body.to}: ${msg.body.subject}`);
msg.ack(); // Acknowledge successful processing
}
});Files or directories starting with _ are ignored.
Sending messages
This step sends messages with the typed queues proxy from void/queues. Each queue is a property named after it, typed by the consumer's defineQueue<T>.
// routes/api/orders.ts
import { defineHandler } from 'void';
import { queues } from 'void/queues';
export const POST = defineHandler(async (c) => {
const order = await c.req.json();
// Fully typed: body must match the
// Message interface from queues/emails.ts
await queues.emails.send({
to: order.email,
subject: 'Order confirmed',
});
return c.json({ ok: true });
});Per-message acknowledgment
Each message has ack() and retry() methods, as in the Cloudflare Queues API:
export default defineQueue<Message>(async (batch, env) => {
for (const msg of batch.messages) {
try {
await processMessage(msg.body);
msg.ack(); // Remove from queue
} catch {
msg.retry({ delaySeconds: 30 }); // Re-deliver after 30s
}
}
});msg.ack(): acknowledge the message and remove it from the queuemsg.retry(options?): retry the message, optionally with a{ delaySeconds }delaybatch.ackAll(): acknowledge all messages in the batchbatch.retryAll(options?): retry all messages in the batch
The first call wins for each message. Calling ack() after retry(), or the reverse, does nothing. Messages without a decision are acked if the handler succeeds and retried if it throws.
The timestamp field on each message is a Date object.
Consumer options
Show consumer options
This step sets batch delivery options with named exports.
// queues/emails.ts
import { defineQueue } from 'void';
interface Message {
to: string;
subject: string;
}
export const maxBatchSize = 50;
export const maxBatchTimeout = 10; // seconds
export const maxRetries = 5;
export const retryDelay = 30; // seconds
export default defineQueue<Message>(async (batch, env) => {
for (const msg of batch.messages) {
console.log(`Send to ${msg.body.to}: ${msg.body.subject}`);
}
});maxBatchSize: maximum number of messages per batch (default10)maxBatchTimeout: maximum seconds to wait before delivering an incomplete batch (default5)maxRetries: maximum number of retries before delivery is exhausted (default3)retryDelay: seconds to wait between retries (default0)
SDK-managed queues have no dead-letter queue. Without one, Cloudflare deletes messages that run out of retries. See Cloudflare's dead-letter queue guide.
The SDK provisions queues and connects their producers and consumers when you deploy.
Local development
In native SDK apps, a sent message runs the consumer in Miniflare automatically. Local delivery uses the same handler as production.
This step sends a batch by hand to the SDK's dev endpoint.
POST /__void/queue
Content-Type: application/json
{ "queue": "<queue-name>", "messages": [{ "id": "1", "timestamp": <unix_ms>, "body": <any>, "attempts": 1 }] }The endpoint needs a local dev trigger token. At startup, the dev server prints a ready-to-paste curl command with it.
curl -X POST http://localhost:5173/__void/queue \
-H "Content-Type: application/json" \
-H "x-void-dev-trigger: <printed-token>" \
-d '{"queue":"my-queue","messages":[{"id":"1","timestamp":'"$(date +%s000)"',"body":{"hello":"world"},"attempts":1}]}'Example output: The local queue consumer runs, and its logs appear in the dev server.
In supported meta-frameworks, use /__void/queue to test a consumer manually. It runs inside the framework adapter's development runtime with that adapter's bindings. In this mode, sending to a queue does not run the consumer.