Queues
Use queues to process work asynchronously, such as sending emails or handling uploads. Define a consumer in queues/, then send it typed messages from your app.
Defining queues
Create a consumer in queues/. TypeScript and JavaScript files are supported (.ts, .mts, .js, .mjs). Its path determines the queue name: queues/emails.ts creates emails, and queues/order/notifications.ts creates order-notifications. Each queue must have a unique name.
The resulting name must follow Cloudflare's queue naming rules: 1–63 characters, only letters, digits, and -, beginning and ending with a letter or digit.
Export a default handler wrapped with 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
Import the typed queues proxy from void/queues to send messages. Each queue is available as a property matching its name, with full type safety from the consumer's defineQueue<T> generic.
// 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 in the batch has ack() and retry() methods matching 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 with no explicit decision are acknowledged if the handler succeeds, or retried if it throws.
The timestamp field on each message is a Date object.
Consumer options
Export named constants to configure how Cloudflare delivers batches to your consumer:
// 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)
Void-managed queues do not configure a dead-letter queue. Cloudflare permanently deletes messages that exhaust their retries without one; see Cloudflare's dead-letter queue guide.
Void provisions queues and connects their producers and consumers when you deploy.
Local development
In native Void apps, sending a message through a queue binding automatically invokes the consumer in Miniflare. Local delivery uses the same handler as production.
You can also manually dispatch a batch by POSTing to the dev endpoint Void exposes:
POST /__void/queue
Content-Type: application/json
{ "queue": "<queue-name>", "messages": [{ "id": "1", "timestamp": <unix_ms>, "body": <any>, "attempts": 1 }] }The endpoint requires a local dev trigger token. Void prints a paste-ready curl command with the current token when the dev server starts.
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}]}'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. Sending to a queue binding doesn't automatically invoke the consumer in this mode.