VercelVercel
Menu

SDK Reference

Last updated February 28, 2026

The @vercel/queue Node.js SDK lets you publish and consume messages in push-based workflows on Vercel. For poll-based consumption, see PollingQueueClient.

Terminal
pnpm i @vercel/queue

Import send and handleCallback directly from @vercel/queue. A lazily-created default client auto-detects the region from VERCEL_REGION, falling back to iad1.

import { send, handleCallback } from '@vercel/queue';

If you need to target a specific region, use a non-default transport, or manage multiple clients, create a QueueClient explicitly:

lib/queue.ts
import { QueueClient } from '@vercel/queue';
 
const queue = new QueueClient({ region: 'sfo1' });
 
export const { send, handleCallback } = queue;

Then import from your module instead of @vercel/queue:

app/api/orders/route.ts
import { send } from '@/lib/queue';
 
export async function POST(request: Request) {
  const body = await request.json();
  const { messageId } = await send('orders', body);
  return Response.json({ messageId });
}
app/api/queues/process-order/route.ts
import { handleCallback } from '@/lib/queue';
 
export const POST = handleCallback(async (message, metadata) => {
  await processOrder(message);
});

Use send to publish a message to a topic. The message can be any JSON-serializable value.

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 });
}
await send('orders', payload, {
  region: 'sfo1',
  retentionSeconds: 3600,
  delaySeconds: 60,
  idempotencyKey: 'order-123',
  headers: { 'x-trace-id': 'abc-123' },
});
OptionTypeDefaultDescription
regionstringAuto-detectedTarget a specific region for this message
retentionSecondsnumber24 hoursMessage TTL. Minimum 60 seconds, maximum 24 hours (86,400 seconds)
delaySecondsnumberZero secondsDelay before message becomes visible
idempotencyKeystring-Deduplication key for the message
headersRecord<string, string>-Custom headers to include with this message

Use handleCallback to create a push mode consumer. Messages are automatically acknowledged when your handler completes, and retried if it throws.

For Express, Connect, or Next.js Pages Router apps, use handleNodeCallback instead, which accepts (req, res) arguments. Unlike the top-level exports, handleNodeCallback is only available on a QueueClient instance:

pages/api/queues/process-order.ts
import { QueueClient } from '@vercel/queue';
 
const queue = new QueueClient();
 
export default queue.handleNodeCallback(async (message, metadata) => {
  await processOrder(message);
});

First, configure the consumer in vercel.json:

vercel.json
{
  "functions": {
    "app/api/queues/process-order/route.ts": {
      "experimentalTriggers": [
        { "type": "queue/v2beta", "topic": "orders" }
      ]
    }
  }
}

Then create the handler:

app/api/queues/process-order/route.ts
import { handleCallback } from '@vercel/queue';
 
export const POST = handleCallback(async (message, metadata) => {
  await processOrder(message);
});

The metadata object includes:

FieldTypeDescription
messageIdstringUnique message identifier
deliveryCountnumberNumber of times this message has been delivered
createdAtDateWhen the message was published
expiresAtDateWhen the message expires
topicNamestringTopic the message was published to
consumerGroupstringConsumer group receiving the message
regionstringRegion where the message is stored

Pass an options object as the second argument to handleCallback to configure visibility timeout and retry behavior:

OptionTypeDefaultDescription
visibilityTimeoutSecondsnumber5 minutesHow long the message stays in-flight before redelivery
retryfunction-Custom retry logic. See custom retry behavior

The SDK automatically re-extends the visibility timeout while your handler is running, so you don't need to configure it for most workloads. If you need to override it for advanced use cases, pass visibilityTimeoutSeconds:

app/api/queues/process-order/route.ts
import { handleCallback } from '@vercel/queue';
 
export const POST = handleCallback(
  async (message, metadata) => {
    await processOrder(message);
  },
  {
    visibilityTimeoutSeconds: 600,
  },
);

The SDK defaults visibilityTimeoutSeconds to 300 seconds (5 minutes) and automatically re-extends the lease while your handler is still running. The underlying Queues API defaults to 60 seconds and does not auto-extend.

Control retry timing and handle poison messages with the retry option:

app/api/queues/process-order/route.ts
import { handleCallback } from '@vercel/queue';
 
export const POST = handleCallback(
  async (message, metadata) => {
    await processOrder(message);
  },
  {
    retry: (error, metadata) => {
      if (metadata.deliveryCount > 5) {
        return { acknowledge: true };
      }
      const delay = Math.min(300, 2 ** metadata.deliveryCount * 5);
      return { afterSeconds: delay };
    },
  },
);

The retry callback can return:

Return valueBehavior
{ afterSeconds: number }Retry after the specified delay
{ acknowledge: true }Acknowledge the message (stop retrying)
undefinedUse default retry behavior

The SDK provides typed error classes for each failure mode:

import {
  UnauthorizedError,
  BadRequestError,
  DuplicateMessageError,
  MessageNotFoundError,
  QueueEmptyError,
} from '@vercel/queue';
 
try {
  await send('orders', payload);
} catch (error) {
  if (error instanceof UnauthorizedError) {
    // Invalid or expired token
  } else if (error instanceof DuplicateMessageError) {
    // Idempotency key collision
  }
}

QueueClient supports multiple serialization formats through transports:

import { QueueClient, BufferTransport, StreamTransport } from '@vercel/queue';
 
const binaryQueue = new QueueClient({
  transport: new BufferTransport(),
});
 
const streamQueue = new QueueClient({
  transport: new StreamTransport(),
});
TransportDescription
JsonTransportDefault. Serializes messages as JSON
BufferTransportSends and receives raw binary data
StreamTransportSends and receives ReadableStream for large payloads

Was this helpful?

supported.