Vercel Queues guidance — durable topics with at-least-once delivery, independent consumer groups, retries, delays, and idempotency keys via @vercel/queue (JS) or vercel-queue (Python). Use when deferring background work, buffering traffic, fanning out events, or choosing between Queues and Workflows.
86%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
You are an expert in Vercel Queues, the durable message topics that power background work and agent events on Vercel.
Vercel Queues (public beta) gives you durable, append-only topics. Producers publish JSON messages, and every subscribed consumer group receives every message with at-least-once delivery and automatic retries. New consumer groups can join later and replay non-expired history. Queues is the primitive under Vercel Workflows; use Queues directly when you need control over publishing, consumption, and routing.
| Need | Use | Why |
|---|---|---|
| Fire-and-forget background job, fan-out, buffering | Queues | Direct publish/consume, independent consumer groups |
| Multi-step logic with sleep, hooks, or human approval | Workflows (⤳ skill: workflow) | Durable steps and replay built on top of Queues |
| Scheduled invocation on a cron | Cron Jobs (⤳ skill: vercel-functions) | Time-based trigger, not message-based |
Install the SDK:
npm install @vercel/queuePublish from any route, Server Action, or function:
// app/api/orders/route.ts
import { send } from '@vercel/queue';
export async function POST(request: Request) {
const body = await request.json();
const { messageId } = await send('orders', { orderId: body.orderId, action: 'process' });
return Response.json({ messageId });
}Consume with a push-mode handler. Messages are acknowledged when the handler returns and retried when it throws:
// app/api/queues/process-order/route.ts
import { handleCallback } from '@vercel/queue';
export const POST = handleCallback(async (message, metadata) => {
await processOrder(message);
console.log('processed', metadata.messageId, 'delivery', metadata.deliveryCount);
});Register the consumer in vercel.json (or vercel.ts) so Vercel routes the topic to that function:
{
"functions": {
"app/api/queues/process-order/route.ts": {
"experimentalTriggers": [{ "type": "queue/v2beta", "topic": "orders" }]
}
}
}Run vercel link and vercel env pull before local development so the SDK can authenticate.
await send('orders', payload, {
region: 'sfo1', // target a specific region
retentionSeconds: 3600, // message TTL; min 60, max 604800 (7 days); default 24 hours
delaySeconds: 60, // delay first delivery; max 7 days, capped at the TTL
idempotencyKey: 'order-123', // duplicates within min(retention, 24 hours) are dropped
headers: { 'x-trace-id': 'abc-123' },
});Create a QueueClient when you need defaults, a fixed region, or multiple clients:
// lib/queue.ts
import { QueueClient } from '@vercel/queue';
const queue = new QueueClient({ region: 'sfo1' });
export const { send, handleCallback } = queue;handleCallback(handler, options) accepts:
| Option | Default | Notes |
|---|---|---|
visibilityTimeoutSeconds | 300 | How long a message stays in flight; the SDK re-extends the lease while the handler runs |
retry | trigger retryAfterSeconds (60s) | (error, metadata) => { afterSeconds } | { acknowledge: true } | undefined |
Handle poison messages by acknowledging after a delivery-count threshold:
export const POST = handleCallback(processOrder, {
retry: (error, metadata) => {
if (metadata.deliveryCount > 5) return { acknowledge: true }; // stop retrying
return { afterSeconds: Math.min(300, 2 ** metadata.deliveryCount * 5) };
},
});metadata includes messageId, deliveryCount, createdAt, expiresAt, topicName, consumerGroup, and region.
For Express, Connect, or Next.js Pages Router handlers use queue.handleNodeCallback(async (message, metadata) => ...) from a QueueClient instance, which takes (req, res).
vercel-queue publishes and consumes with the same topic model, and FastAPI, Flask, and Django apps can use it; Celery and Dramatiq integrations are documented under the Python backend frameworks.vercel.queues.triggers in nitro.config.ts and handle messages with the vercel:queue runtime hook; send from @vercel/queue works in any server route.⤳ skill: daemons).BufferTransport for binary or StreamTransport for large bodies when constructing a QueueClient.@vercel/queue exports typed errors: UnauthorizedError, BadRequestError, MessageNotFoundError, and QueueEmptyError. Duplicate idempotency keys do not throw; the duplicate is silently dropped.
handleCallback route with no experimentalTriggers entry never receives messages. Register every consumer in vercel.json/vercel.ts.metadata.messageId or your own idempotencyKey.retry policy that acknowledges poison messages or a trigger maxDeliveries cap, a permanently failing message is redelivered until it expires.vercel link and vercel env pull first; otherwise send() throws Failed to get OIDC token for local development.vercel/vercel-plugin
808a6c9
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.