Gerson

Gerson

Passionate developer specializing in web development, cloud architecture, and system design.

TypeScriptReactNext.jsPythonFastAPISQLNode.jsAWS

Durable Event Streaming with Vercel Queues

Vercel Queues gives you at-least-once delivery, dead-letter queues, and consumer functions built on Fluid Compute — without standing up SQS or Kafka. Here's how to use it for webhook processing, async email, and work offloading.

Gersonhttps://vercel.com/docs/queues
Data pipeline visualization with flowing packets

One of the quieter but more useful things Vercel shipped in 2025 is Vercel Queues — a durable event streaming system that is now in public beta. Before this, offloading work from a request (sending an email, calling a webhook, kicking off a long job) usually meant wiring up SQS, Upstash QStash, or a self-hosted Redis queue. Queues collapses that into a primitive that lives next to your functions.

This article walks through what Queues actually is, a working example of a webhook processor, and where it fits versus the alternatives.

What You Get

  • At-least-once delivery — your consumer is guaranteed to see each message, possibly more than once (so write idempotent handlers)
  • Consumer functions built on Fluid Compute, same runtime as your API routes
  • Automatic retries with exponential backoff
  • Dead-letter queues for messages that exceed the retry budget
  • Per-message delay for scheduled work (up to 15 minutes)
  • Per-queue concurrency limits to avoid hammering downstream APIs

Declaring a Queue

Queues are declared in vercel.ts (or vercel.json) and consumed by functions at a specific path. The contract is tiny: the producer enqueues a JSON payload, the consumer receives it.

vercel.ts

import type { VercelConfig } from '@vercel/config/v1';

export const config: VercelConfig = {
  queues: [
    {
      name: 'webhook-deliveries',
      consumer: '/api/queues/webhooks',
      maxRetries: 5,
      maxConcurrency: 10,
      deadLetter: 'webhook-deliveries-dlq',
    },
    { name: 'webhook-deliveries-dlq', consumer: '/api/queues/dlq' },
  ],
};

Producing Messages

app/api/events/route.ts

import { queues } from '@vercel/queues';

export async function POST(req: Request) {
  const event = await req.json();

  await queues.send('webhook-deliveries', {
    subscriberId: event.subscriberId,
    url: event.url,
    payload: event,
  });

  return Response.json({ queued: true }, { status: 202 });
}

Returning a 202 immediately means the request finishes in single-digit milliseconds even though the actual webhook delivery might take seconds and retry for minutes. The cost model is by the message, not by the request that enqueued it.

Consuming Messages

app/api/queues/webhooks/route.ts

import { queueHandler } from '@vercel/queues';

export const POST = queueHandler(async (message) => {
  const { url, payload } = message.body;

  const res = await fetch(url, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify(payload),
    signal: AbortSignal.timeout(10_000),
  });

  if (!res.ok) {
    throw new Error(`Subscriber ${url} returned ${res.status}`);
  }
});

The queueHandler wrapper verifies the request signature (so random internet traffic cannot invoke your consumer), parses the body, and converts thrown errors into retries. Throw to retry. Return to acknowledge.

Make it idempotent: At-least-once means your handler will eventually run twice for some messages. Use an idempotency key (message ID is a good choice) and a small store — a processed_message_id column, a Redis SET with a TTL — to short-circuit duplicates.

Scheduled Work

Queues support a per-message delay, which is the cleanest way to do things like "send a reminder email 2 hours after signup" without dragging in a separate job scheduler.

Scheduling a delayed message

await queues.send('reminders', { userId }, { delaySeconds: 7200 });

The 15-minute upper bound on delay is the current beta constraint. For longer waits, combine Queues with a cron that re-enqueues, or reach for Vercel Workflow's durable sleep.

When To Pick Queues vs Alternatives

  • Queues — short, fan-out work that needs at-least-once delivery and retries. Email sends, webhook fanout, async analytics, outbound integrations.
  • Vercel Cron Jobs — anything on a schedule, not triggered by an event.
  • Vercel Workflow — multi-step processes that need to pause for hours or days with durable state (order processing, approval flows). Workflow is the right tool when Queues would start to feel like a state machine built out of messages.
  • External queue (SQS, QStash) — when you have producers outside Vercel, or you need ordering guarantees and FIFO delivery.

Resources