Skip to content
Docs

Queues concepts

Vercel Queues is a durable event streaming system for asynchronous workloads. You publish messages to topics, and consumer groups process those messages independently with at-least-once delivery, retries, and visibility timeouts.

Queues is useful when you need to decouple request handling from background processing, absorb traffic spikes, and keep work reliable across function failures or deployment changes.

Vercel Queues is Vercel's managed queueing system for durable, asynchronous message processing. It acts as a reliability layer between producers and consumers:

  • Producers publish messages to a topic.
  • Consumer groups read and process those messages independently.
  • Messages persist until they are acknowledged or expire.
  • Failed processing attempts are retried automatically.
  • Delivery is at-least-once, so consumers should be idempotent.

Vercel Queues and Vercel Workflows solve related but different problems:

Vercel QueuesVercel Workflows
Primary abstractionTopics, messages, and consumer groupsDurable steps and workflow runs
Best forEvent delivery, fan-out consumers, and routing controlStateful, multi-step application logic
Execution modelMessage-driven consumers (push or poll)Step-driven orchestration with built-in state between steps
Failure handlingRetries, visibility timeout, and idempotent consumersRetries at step boundaries with workflow-aware recovery
Use this whenYou need low-level control over publish/consume behaviorYou need high-level orchestration and durable business flows

Use Queues when you want direct control over event ingestion and delivery. Use Workflow when you want a higher-level programming model for long-running business processes.

At a high level, Vercel Queues follows a publish, lease, acknowledge, and retry flow:

  1. A producer publishes a message to a topic.
  2. The topic makes that message available to each consumer group.
  3. A consumer in a group receives the message under a visibility timeout lease.
  4. If processing succeeds, the consumer acknowledges the message and removes it from that group.
  5. If processing fails or times out, the lease expires and the message is retried.

Messages remain durable throughout this lifecycle and are removed when acknowledged or when their retention period expires.

A topic is a durable, append-only log of messages. Producers publish messages to a topic, and the topic fans them out to all subscribed consumer groups. Messages are retained until they expire, regardless of whether any consumer has processed them. Retention is configurable per-message from 60 seconds to 7 days, defaulting to 24 hours.

A consumer group is an independent subscriber to a topic. Each group tracks its own position in the log and processes messages at its own pace. Because groups are fully isolated, a slow or failing consumer in one group has no effect on any other.

Topic(append-only log) Group A, position: 950 Group B, position: 720 Group C, position: 950

In poll mode, you can add a new consumer group at any time. New groups start reading from the beginning of the topic, giving you access to all non-expired messages. This makes it straightforward to backfill data or add new processing pipelines without republishing. Push mode consumers are configured at deploy time and cannot be added dynamically. JavaScript and TypeScript projects define triggers in vercel.json. Python projects declare subscriber entrypoints in pyproject.toml.

Vercel manages partitioning and scaling for you. You don't need to pre-configure partition counts, manage rebalancing, or provision throughput capacity. Queues scale with your traffic automatically.

When Vercel Queues accepts a message, it guarantees the message can be consumed. Every message is synchronously written to three separate availability zones before the publish call returns. This means your message is fully replicated before your producer receives confirmation. Even if an entire availability zone goes down, the message is safe.

Publish Replicate to 3 AZs Acknowledged Deliver Producer Queues Consumer

After replication, the publish acknowledgment and consumer notification happen simultaneously. This means a consumer may receive and begin processing a message before the producer's publish call returns, depending on network latency.

Once Vercel accepts a message, it retries delivery until the message is successfully processed or expires. Vercel only removes a message from the topic after your consumer acknowledges it. If your consumer crashes, a deployment rolls out, or the function times out, Vercel redelivers the message automatically.

Vercel Queues provides at-least-once delivery semantics. Every accepted message is delivered to each consumer group at least one time, as long as it becomes visible before its retention period (TTL) expires. In most cases, a message is delivered exactly once, but there are edge cases where a message may be delivered more than once:

  • Consumer timeouts: If your function processes a message but doesn't acknowledge it before the visibility timeout expires, Vercel assumes the delivery failed and redelivers the message.
  • Infrastructure events: During rare events like availability zone failovers, a message that was already delivered may be redelivered.

Design your consumers to be idempotent, meaning they produce the same result whether they process a message once or multiple times. Common strategies include using a unique message ID to deduplicate, or making operations naturally idempotent (like setting a value rather than incrementing it).

When a message is delivered to a consumer, it becomes temporarily invisible to other consumers in the same group. This is the visibility timeout. During this window, the message is "in flight" and won't be delivered again.

If your consumer processes the message and acknowledges it before the timeout expires, the message is removed. If the consumer crashes, times out, or fails to acknowledge, the visibility timeout expires and the message becomes available for redelivery.

Deliver Acknowledge Deliver Timeout expires Redeliver Queues Consumer Message removed Queues Consumer crashes Message visible again Consumer (deliveryCount + 1)

This is how Vercel Queues handles failures without manual intervention. You don't need to build retry infrastructure or monitor for stuck messages. If a Vercel Function crashes mid-processing or hits its execution time limit, the message automatically returns to the queue and gets delivered to the next available consumer.

The default visibility timeout is 60 seconds. You can configure it per receive request from 0 to 3,600 seconds (60 minutes). Setting it to 0 peeks at the message without leasing it. If your consumer needs more time mid-processing, you can extend the lease using the ExtendLease API.

A message moves through several states from the time it's published to when it's processed or expires:

delay expires ReceiveMessages Acknowledge ExtendLease Lease expires TTL expires SendMessage Pending Visible In-Flight (leased) Processed (removed) Expired (deleted)
  1. SendMessage writes the message. If a delay is configured, the message enters a pending state.
  2. Once the delay expires (or immediately if no delay), the message becomes visible to consumers.
  3. ReceiveMessages claims the message and starts a lease (visibility timeout). The message is now in-flight.
  4. AcknowledgeMessage permanently removes the message from the consumer group.
  5. ExtendLease resets the visibility timeout if your consumer needs more processing time.
  6. If the lease expires without acknowledgment, the message becomes visible again with an incremented delivery count.
  7. Messages are permanently deleted when their retention period (TTL) expires, regardless of processing state. Once a message expires, it cannot be consumed.

See the API reference for details on each operation.

Vercel Queues is available in all 19 Vercel regions. When you create a queue, you select a region, and your message data is persisted in that region. The three-zone replication described in Durability happens across availability zones within your selected region.

Choose a region close to your producers and consumers to minimize latency. If you need queues in multiple regions, create separate queues in each region. See the regions documentation for the full list of available regions.

Vercel writes queue data to the region you select. During a regional outage, Vercel may temporarily store messages in a neighboring region and relocate them once the target region recovers. This ensures ingestion continues even when a region is unavailable.

Strict data residency (guaranteeing data never leaves the selected region, even temporarily during failover) is not supported yet.

On Vercel, topics are partitioned by deployment ID by default. In push mode, Vercel delivers messages back to the same deployment that published them.

Deployment A (current) Deployment B (previous) Producer orders topic Consumer Producer orders topic Consumer

This design means you don't have to worry about message compatibility across deployments. When you change a message schema or update your consumer logic, the new deployment produces and consumes its own messages. There's no risk of a new deployment consuming messages published by an older version with a different format.

This is useful during rollouts: both the current and previous deployments can have active queues processing their own messages independently until the old deployment drains.

In poll mode, you can reference the deployment ID as an opaque version identifier to partition your consumers manually, or omit it entirely and handle versioning at the application level.

Because messages stay pinned to the deployment that published them, promoting or rolling back to a different deployment does not stop deliveries to the old one. Its consumer functions keep being invoked, and keep being retried, until every message it published is acknowledged or expires. A deployment that was in production for a few minutes can therefore stay busy for up to the message retention period (24 hours by default) if its messages keep failing.

To stop a deployment's consumers from running, delete the deployment. Vercel stops invoking its functions immediately. Pending messages stay in the queue until they expire, but no compute is used for them.

Two settings bound how much compute resources a failing deployment can consume:

  • Cap retries. Set maxDeliveries on the trigger so a message that keeps failing is dropped after a bounded number of deliveries.
  • Acknowledge bad messages. As an alternative to the above, you can return { acknowledge: true } from the SDK's retry callback based on any checks you want to perform, which will stop the message from being redelivered.

To see which deployments are consuming compute, open the Observability Query tab with function duration grouped by deployment. A deployment that is no longer current but still accounts for a large share of GB-hours is a deployment with failing consumers.

By default, Vercel delivers messages to your Vercel Functions using push mode. When a message is published to a topic, Vercel invokes your consumer function automatically with fluid compute. You define a consumer function, and Vercel calls it for each message (or batch of messages) as they become available.

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

For consumers running outside of Vercel, or advanced on-Vercel setups that need more control, Vercel Queues also supports poll mode. Both modes provide the same delivery guarantees and can be used together within the same queue.

Queue consumer functions on Vercel are not accessible from the outside world. JavaScript and TypeScript projects configure a consumer by adding a queue/v2beta trigger to vercel.json:

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

Python projects declare decorated subscriber entrypoints under [[tool.vercel.subscribers]] in pyproject.toml. Vercel reads the @subscribe configuration and generates the private queue-triggered function at build time. See the Python SDK reference for the configuration format.

The vercel.json queue trigger supports these options for JavaScript and TypeScript projects:

OptionTypeDefaultDescription
typestring-Trigger type. Use queue/v2beta
topicstring-Topic name to consume. Supports wildcards (e.g., order-*)
retryAfterSecondsnumber60 secondsTime before a failed message is retried
initialDelaySecondsnumberZero secondsDelay before the consumer starts processing after deploy
maxDeliveriesnumberUnlimitedDeliveries after which a message is no longer retried. See Retries

Multiple route files with the same topic create separate consumer groups, each receiving a copy of every message.

With this configuration, the function is completely air-gapped from the internet. It has no public URL and can only be invoked by Vercel's internal queue infrastructure.

This means you don't need to add authentication or authorization logic to your consumer functions. Unauthorized requests can never reach them. Only messages delivered through Vercel Queues can trigger execution.

In poll mode, security works differently because your application initiates the connection. Your code authenticates with the Vercel API using standard OIDC tokens when polling for messages, the same way you authenticate with any other Vercel API.

You can delay a message's visibility by setting a delay when publishing. The message is accepted and stored immediately, but consumers won't see it until the delay expires. Delays can be set from 0 seconds up to 7 days, but cannot exceed the message's TTL.

This is useful for scheduling future work, such as sending a reminder email in 30 minutes or retrying an external API call after a cooldown period.

Messages cannot be consumed after their TTL expires. If a message's delay would extend beyond its TTL, the message will expire before it becomes visible and will never be delivered.

If you need to schedule work further into the future than the 7-day maximum delay, consider these approaches:

  • Daisy-chain messages: Publish a message with the maximum delay, then have your consumer republish another delayed message until you reach the target time.
  • Use Vercel Workflows: Vercel Workflows supports sleep() for arbitrarily long durations with built-in durability. This is often simpler than managing chained messages yourself.

You can include an idempotency key when publishing a message to have Vercel deduplicate it for you. Vercel handles deduplication out-of-band after the publish call returns, so the SendMessage response itself always succeeds. The duplicate is silently dropped, and the original message continues to be delivered with at-least-once semantics.

In practice, this means:

  • Push and poll mode consumers never receive the duplicate message. It's filtered out before delivery. The original message is unaffected.
  • ReceiveMessageById returns a 409 response with the originalMessageId if you try to receive a duplicate by its ID. This lets you follow the redirect to the original message.

The deduplication window is the message's retention period, capped at 24 hours: min(retention, 24 hours). Within that window, any republish with the same idempotency key is deduplicated. A message with a retention period shorter than 24 hours is deduplicated for its full retention period. A message with a longer retention period (up to 7 days) is only deduplicated for the first 24 hours.

This is useful when your producer might retry a publish (for example, after a network timeout) and you need to guarantee exactly-once publishing.

In push mode, you can set a maximum concurrency per consumer group. This limits how many messages can be in-flight simultaneously for that group. If the limit is reached, Vercel holds back delivery until an in-flight message is acknowledged or its lease expires.

This is useful when your consumer calls a rate-limited downstream service, or when you need to control resource usage.

Vercel Queues retries failed messages automatically until they expire or reach the trigger's maxDeliveries. A delivery counts as failed when the consumer does not acknowledge the message before its lease runs out, whichever way that happens: the handler threw, the function crashed or timed out, or the function could not be reached.

Every delivery to a push consumer is a function invocation. If a delivery fails by timing out, it is billed for the function's full maxDuration at its configured memory. To prevent runaway costs for misconfigured deployments, set up a retry depth alert, and stop it as described in Stopping deliveries to a deployment.

Vercel Queues doesn't have a built-in dead-letter queue. Instead, you handle poisoned messages at the application level using the SDK's retry handler.

Because messages with no delivery attempts are always prioritized over retried messages, a poisoned message naturally falls to lower priority. Your consumer keeps progressing through new messages while the failing message retries in the background. Even with max concurrency set to 1, a poisoned message can't block your consumer.

Use the retry callback to control backoff timing and acknowledge poisoned messages to stop retrying them:

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

See the JS SDK reference for the full retry API.

Vercel Queues delivers messages in approximate write order. Messages are generally delivered in the order they were published, but strict ordering is not guaranteed:

  • Retried messages have lower priority than new messages. If a message fails and is retried, new messages published in the meantime may be delivered first.
  • No FIFO guarantee. Even with a single consumer and max concurrency set to 1, message order is not strictly first-in-first-out.

If your workload requires strict ordering, design your consumers to handle messages in any order, or include sequence numbers in your message payloads to reorder on the consumer side.

Queue consumers work the same inside a service. You add a queue/v2beta trigger to a function to make it a consumer, and several services can consume the same topic at once:

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

Topics belong to the project and deployment, not to an individual service. Every service in the deployment publishes to and consumes from the same set of topics.

A queue consumer is never reachable from the internet, the same as in a single-application project. Adding a top-level rewrite to expose a service does not expose its consumers, because the rewrite only routes public traffic while consumers are invoked by Vercel's internal queue infrastructure. A consumer is also unreachable over a service binding: bindings let other services call the service's exposed routes, but a queue consumer can only be triggered by Vercel Queues.

In push mode, triggers in different services have independent consumer groups, so each consumer in a service receives its own copy of every message. In poll mode, you need to manage consumer groups manually to avoid collisions across services.

Last updated September 10, 2026

Was this helpful?

supported.