Skip to Content

Queues

uv add vercel-queue

uv add vercel

For JavaScript and TypeScript, see the JS SDK Reference.

This installs the full Vercel SDK and is recommended when you use other Vercel products. For a minimalist installation that includes only the Queues SDK, use vercel-queue instead.

Python SDK Reference

The above command installs just the Queues SDK with optional features disabled. If your application relies Pydantic-backed typed message payloads, install vercel-queue[typed].

poll,

QueueClient,

accept_and_handle,

send,

subscribe,

from vercel.queue import (

poll_and_handle,

Export

subscribe


send

accept_and_handle

Register a function as a typed queue subscriber

Run an async polling loop for one registered subscriber

Publish one message with the default async client

Dispatch a push callback body and headers to subscribers

poll_and_handle

QueueClient

Description

Configure region, authentication, headers, deployment, and URL

Poll one batch and yield Delivery[T] objects

Declare a topic name and payload type contract

Clients are lightweight and hold no open connections, so create one at module scope and share it across requests. QueueClient is not a context manager.

You can reach our customer support team by emailing info@yourcompany.example.com, calling +1 555-555-5556, or using the live chat on our website. Our dedicated team is available 24/7 to assist with any inquiries or issues.

We’re committed to providing prompt and effective solutions to ensure your satisfaction.

We offer a 30-day return policy for all products. Items must be in their original condition, unused, and include the receipt or proof of purchase. Refunds are processed within 5-7 business days of receiving the returned item.

from fastapi import FastAPI, Request

from vercel.queue import QueueClient

app =FastAPI()

queue =QueueClient(region="sfo1")


@app.post("/api/orders")

asyncdefcreate_order(request: Request):

body =await request.json()

message_id =await queue.send("orders", body)

return{"messageId": message_id}

Option

str, template, or callable

token

region str

base_url

Start Now
  • deployment
  • headers
  • timeout
  • http_client_factory

Default

Type VERCEL_REGION

Regional API URL

Start Now
  • Current deployment
  • Mapping[str, str]
  • DeploymentOption
  • int, float, timedelta, or None

Description

10 seconds Queue region, such as iad1, fra1, or sfo1

Custom Queues API base URL

Contact Us
  • Deployment partition used for send and poll requests
  • Custom non-protected headers
  • Request timeout
  • Resolved from Vercel

Set deployment to a deployment ID string to pin requests to one deployment, or to ALL_DEPLOYMENTS to send and poll across all deployments. The default targets the current deployment from VERCEL_DEPLOYMENT_ID.

The base_url value can be a fixed URL, a template containing a {region} placeholder, or a callable that takes a region name and returns a URL.

from vercel.queue import QueueClient

queue =QueueClient(base_url="https://proxy.example/queues/{region}")


Publishing messages

Use send to publish a message to a topic. When you pass a Topic[T], the SDK uses the topic's payload type and transport. Otherwise, the SDK infers a serializer from the payload type.

app =FastAPI()

from vercel.queue import send

@app.post("/api/orders")

body =await request.json()

asyncdefcreate_order(request: Request):

message_id =awaitsend(

)

"orders",

return{"messageId": message_id}

from fastapi import FastAPI, Request

{"orderId": body["orderId"], "action": "process"},

payload,

Send options

"orders",

from vercel.queue import send

from datetime import timedelta

delay=60,

)

message_id =awaitsend(

idempotency_key="order-123",

retention=timedelta(hours=1),

headers={"x-trace-id": "abc-123"},

Option

Delay before the message becomes visible

idempotency_key

Deployment partition for this send request

int, float, or timedelta
Default
Message retention duration
Type

retention

int, float, or timedelta

Service default
Description
deployment
DeploymentOption

Mapping[str, str]

Custom non-protected headers for this send call

No delay
-
Current deployment
headers

send returns the created message ID. It returns None when the service accepted the message but deferred ingestion. Deferred messages are still delivered.

Topic types and message formats

The message format is part of the topic contract. Use Topic[T] to declare the payload type for a topic. send(), poll(), and subscribers use topic declarations or handler annotations to choose the normal transport automatically.

Topic payload type

ByteBufferTransport

JSON-compatible values, dict[...], list[...]

Pydantic models and other structured annotations

Message format
RawJsonTransport[Any]
JSON with receive validation
TypedJsonTransport[T]

bytes

str

TextBufferTransport
Buffered binary
Default transport
Buffered UTF-8 text

Iterable[bytes] or AsyncIterable[bytes]

Iterable[str] or AsyncIterable[str]

JSON
Streaming binary
Streaming UTF-8 text
TextStreamTransport

to:str

from typing import TypedDict

subject:str

classEmail(TypedDict):

from vercel.queue import Topic, send, subscribe

@subscribe(topic=emails)

awaitsend_email(email)

awaitsend(emails, email)

asyncdefqueue_email(email: Email) ->None:

emails = Topic[Email]("emails")

asyncdefreceive_email(email: Email) ->None:

You can specify a transport explicitly on the topic when the topic is untyped or when you need custom serialization. This keeps send and receive using the same message format.

"large-file",


awaitwrite_chunk(chunk)

large_file = Topic[AsyncIterable[bytes]](

@subscribe(topic=large_file)

from collections.abc import AsyncIterable, AsyncIterator

asyncdefsend_file() ->None:

from vercel.queue import ByteStreamTransport, Topic, send, subscribe

asyncfor chunk in chunks:

asyncdeffile_chunks() -> AsyncIterator[bytes]:

yield chunk

asyncdefarchive_file(chunks: AsyncIterable[bytes]) ->None:

withopen("large.bin", "rb")as file:

while chunk := file.read(1024*1024):

Pydantic models can be sent directly. A typed topic infers typed JSON validation and deserialization on receive.

from pydantic import BaseModel

from vercel.queue import Topic, send, subscribe


classOrder(BaseModel):

order_id:str

total_cents:int


orders = Topic[Order]("orders")


asyncdefqueue_order() ->None:

awaitsend(orders, Order(order_id="ord_123", total_cents=2500))


@subscribe(topic=orders)

asyncdefprocess_typed_order(order: Order) ->None:

awaitprocess_order(order)

Consuming messages in push mode

Push mode is the default for Python subscribers deployed to Vercel. Register a function with @subscribe, then declare its Python module import path under [[tool.vercel.subscribers]] in pyproject.toml.


@subscribe(topic=orders)

orders = Topic[dict[str,object]]("orders")

You can reach our customer support team by emailing info@yourcompany.example.com, calling +1 555-555-5556, or using the live chat on our website. Our dedicated team is available 24/7 to assist with any inquiries or issues.

We’re committed to providing prompt and effective solutions to ensure your satisfaction.

We offer a 30-day return policy for all products. Items must be in their original condition, unused, and include the receipt or proof of purchase. Refunds are processed within 5-7 business days of receiving the returned item.

entrypoint ="queues.orders"


topics = ["refunds"]

[[tool.vercel.subscribers]]

The entrypoint value is a Python module import path. Use a dotted module path such as queues.orders for queues/orders.py. Don't use a filesystem path or include the .py suffix.

[[tool.vercel.subscribers]]

At build time, Vercel imports the entrypoint module, reads every subscription it registers, and compiles the subscriber into a private queue-triggered function. You don't need to configure experimentalTriggers in vercel.json.

entrypoint ="worker"

topics = ["orders"]

entrypoint ="refunds"

When a module registers several subscribers, the generated function consumes all of them. Add a topics filter to split the module's subscribers across separate generated functions.

[[tool.vercel.subscribers]]

Each [[tool.vercel.subscribers]] entry identifies a Python module that registers one or more subscribers. The module:object format is also supported when you need to identify an object in the module.

Subscriber options

Keep delivery configuration on the @subscribe decorator. Vercel reads topic, consumer_group, retry_after, initial_delay, max_concurrency, and max_attempts when it generates the queue trigger.


Derived from the function name

Option

topic Type

consumer_group

Start Now
  • initial_delay
  • max_concurrency
  • int, float, or timedelta
  • int

retry_after

str | SanitizedName | Topic[T] int

Topic filter. A trailing * matches by prefix, and "*" matches every topic

Start Now
  • No delay
  • str | SanitizedName
  • -
  • Service default

Description

Required Default

Consumer group override for this subscriber

Contact Us
  • Base retry delay for generated queue trigger configuration
  • Deploy-time delay before generated consumers start processing
  • Push dispatcher concurrency cap
  • int, float, or timedelta

Consumer group names can be any non-empty string. The SDK escapes them into queue-safe names automatically. Use sanitize_name to compute the stored name yourself, and pass a SanitizedName when a value is already queue-safe and must not be escaped again.

Subscribe to multiple topics with a wildcard pattern:

from vercel.queueimport subscribe


@subscribe(topic="user-*")

asyncdefhandle_user_event(event: dict[str,str]) ->None:

awaitprocess_user_event(event)


Push message metadata

Subscribers receive a Message[T]. Use message.payload for the deserialized payload, message.message_id for the message ID, and message.metadata for delivery metadata.

message_id

Opaque message ID assigned by the service

delivery_count

Consumer group that owns this delivery

Description
datetime
datetime | None
created_at

receipt_handle

Opaque delivery token for follow-up operations

consumer_group
Topic name
str | None
datetime | None

visibility_deadline

Queue region for follow-up operations

str | None
content_type
str | None
expires_at

Manual push handling

Use accept_and_handle when you need to route a callback through an existing ASGI framework. It accepts the callback body as bytes, a byte iterable, or a framework response object, plus the callback request headers. Pass lease_duration to change the processing timeout used while handlers run.


from fastapi import Request

from vercel.queue import accept_and_handle


asyncdefhandle_queue_callback(request: Request) ->None:

body =await request.body()

awaitaccept_and_handle(body, request.headers, lease_duration=300)


It raises UnhandledMessageError when no registered subscription matches the delivered topic.

Consuming messages with polling loops

Use poll_and_handle() to run a subscriber outside push mode, such as in a self-hosted worker, local process, or long-running script. The helper uses the subscriber's @subscribe metadata to pick the topic, consumer group, payload type, and receive transport.

import asyncio


poller.cancel()

asyncdefmain() ->None:

poller = task_group.create_task(

from vercel.queue import poll_and_handle, subscribe

awaitprocess_order(order)

poll_and_handle(fulfill_order, interval=1.0),

)

asyncio.run(main())

@subscribe(topic="orders")

asyncdeffulfill_order(order: dict[str,str]) ->None:

awaitwait_for_shutdown_signal()

asyncwith asyncio.TaskGroup()as task_group:

poll_and_handle() polls each configured topic until no messages are available, then sleeps for interval before checking again. The loop acknowledges a message when the subscriber returns. If the subscriber raises, the SDK leaves the message unacknowledged so Vercel Queues can redeliver it according to retry behavior.

Polling loop options

Option

Concrete topics to poll. Required for wildcard subscriber topic patterns

QueueSubscriber[..., Any]

Idle sleep duration when all configured topics are empty

Type
Required
Iterable[str] | None
Default

interval

int, float, or timedelta

None
Description
subscriber
lease_duration

int, float, timedelta, or None

Per-request maximum from 1 through 10. None drains until empty before idle

topics
None
int | None
5 minutes

Wildcard subscribers can run in a polling loop, but you must pass concrete topic names because wildcard topic patterns cannot be polled directly.

awaitpoll_and_handle(

@subscribe(topic="events-*")

awaitrecord_event(event)

)

handle_event,

topics=["events-user", "events-system"],

interval=1.0,

Polling loop client configuration

Use QueueClient.poll_and_handle() when the polling worker needs explicit region, authentication, headers, deployment partitioning, or a custom queue service URL.


import asyncio


poller.cancel()

asyncdefmain() ->None:

poller = task_group.create_task(

from vercel.queue import QueueClient, subscribe

awaitwait_for_shutdown_signal()

asyncdeffulfill_order(order: dict[str,str]) ->None:

@subscribe(topic="orders")

asyncio.run(main())

awaitprocess_order(order)

queue.poll_and_handle(fulfill_order, interval=1.0),

queue =QueueClient(region="iad1")

asyncwith asyncio.TaskGroup()as task_group:

Polling regions

Messages can only be received from the region they were sent to. Use a fixed region, such as iad1, for both sending and polling. Avoid a changing runtime region for polling workers because that can distribute messages across regions unpredictably.

Consuming messages with manual polling

Use poll() when you need direct control over delivery lifecycles or explicit consumer group behavior. poll() polls once for a specified consumer group and yields up to limit Delivery[T] objects. It can return no deliveries, so long-running workers usually use poll_and_handle() or add their own loop around poll().

from vercel.queue import QueueClient, Topic


CONSUMER_GROUP,

orders,

CONSUMER_GROUP ="fulfillment"

queue =QueueClient(region="iad1")

asyncdefpoll_once() ->None:

orders = Topic[dict[str,object]]("orders")

limit=10,

):

lease_duration=300,

asyncwith delivery as message:

asyncfor delivery in queue.poll(

awaitprocess_order(message.payload)

consumer_group

lease_duration

Option

topic str

limit

Manual polling options
  • Type
  • str | Topic[T]
  • Required
  • int

Default

1 Required

Use the same consumer group name in multiple pollers when those pollers should compete for work. Use different consumer group names when each group should receive its own copy of every message.

Start Now
  • Description
  • Topic object or topic name to receive from
  • Consumer group to receive as
  • Maximum messages to claim, from 1 through 10
  • Processing timeout for received messages

from vercel.queue import QueueClient

CONSUMER_GROUP ="fulfillment"

queue =QueueClient(region="iad1")


asyncdefprocess_batch() ->None:

asyncfor delivery in queue.poll("orders", CONSUMER_GROUP):

message = delivery.accept()

await queue.extend_lease(message, 600)

awaitprocess_order(message.payload)

await queue.acknowledge(message)

Pass zero to extend_lease() to release a message back to the queue immediately. Use retry_after() to schedule redelivery after a delay when your code owns the lifecycle. Handlers should usually raise RetryAfter instead.

Retries and redelivery

Vercel Queues delivers messages at least once. When a subscriber raises an exception, the SDK leaves the message unacknowledged, and the message becomes visible again after the retry_after interval configured on @subscribe. Redelivery continues until the handler succeeds or the message expires.

Raise RetryAfter from a subscriber to control the next delivery time directly. The SDK stops lease renewal, makes the message visible again after the delay, and treats the delivery as handled. The default delay is 60 seconds, and a delay of zero requests immediate redelivery.

@subscribe(topic="orders")


try:

except TemporaryError as exc:

awaitprocess_order(message.payload)

Raise Handoff when your handler passed the delivery to another system that owns the rest of its lifecycle. The SDK stops lease renewal and leaves the lease open. The external system must acknowledge the message or change its visibility with the original message metadata, or the message is redelivered when the lease expires.

from vercel.queueimport Message, RetryAfter, subscribe

Use message.metadata.delivery_count to add exponential backoff, as shown above.

raiseRetryAfter(delay)from exc

Synchronous client

delay =min(300, 2**message.metadata.delivery_count *5)

RetryAfter and Handoff both extend QueueDirective. They work in push mode, in poll_and_handle() loops, and inside entered Delivery context managers.

asyncdeffulfill_order(message: Message[dict[str,str]]) ->None:

The sync API mirrors the async send and manual polling APIs under vercel.queue.sync. Use vercel.queue.sync.QueueClient.poll_and_handle() to run a subscriber in a background polling thread.

queue.send("events", {"type": "user.created"})

from vercel.queue import subscribe

process_event(event)

@subscribe(topic="events")

defhandle_event(event: dict[str,str]) ->None:

try:

finally:

wait_for_shutdown_signal()

poller.cancel()

from vercel.queue.sync import QueueClient

poller = queue.poll_and_handle(handle_event, interval=1.0)

Error handling

The Python SDK exports typed error classes from vercel.queue.


try:

except DuplicateIdempotencyKeyError:

The sync polling loop runs in a daemon thread and returns a concurrent.futures.Future[None]. Calling cancel() asks the polling thread to stop. Calling result() surfaces polling errors, or raises CancelledError after cancellation.

Common errors include:

from vercel.queue import DuplicateIdempotencyKeyError, QueueError, send

pass

raise

except QueueError:

# The idempotency key was already used.

# Handle other queue service failures.

awaitsend("orders", payload, idempotency_key="order-123")

ForbiddenError

UnauthorizedError

BadRequestError

DuplicateIdempotencyKeyError

Token is invalid or expired
Error
Idempotency key already exists
Description

MessageNotFoundError

MessageUnavailableError

Message is temporarily unavailable
Message could not be found
MessageLockedError
The service throttled the request

PayloadValidationError

UnhandledMessageError

QueueError
TokenResolutionError
Base class for queue SDK errors
ThrottledError

Local development and testing

Use the embedded queue service to exercise the full send, dispatch, lease renewal, and acknowledgement path in one process without deploying.


from vercel.queue import subscribe

from vercel.queue.embedded import embedded_queue_service


@subscribe(topic="emails")

asyncdefhandle_email(email: dict[str,str]) ->None:

awaitrecord_email(email)


asyncdefsend_one() ->None:

asyncwithembedded_queue_service()as service:

client = service.get_async_client()

await client.send("emails", {"subject": "Hi"})

asyncdeftest_send(embedded_queue_server):

pytest_plugins = ["vercel.queue.testing.pytest"]

message_id =await client.send("emails", {"subject": "Hi"}, retention=60)

assert message_id isnotNone

For pytest, enable the bundled plugin and use the embedded_queue_server fixture. Each test gets isolated queue state.

client = embedded_queue_server.get_async_client()

python -mvercel.queue.devserver--port8000


The command prints a JSON baseUrl for the local queue API. When --port is omitted, it picks a random available port. Point clients at the printed URL with the VERCEL_QUEUE_BASE_URL environment variable or the base_url client option.

Environment variables

The SDK reads these variables when you don't pass the matching option explicitly:

Related

Variable


VERCEL_REGION

VERCEL_QUEUE_TOKEN

Description

Default deployment partition. Set automatically on Vercel

Default queue region. Set automatically on Vercel

Bearer token override. The SDK resolves a Vercel OIDC token by default

VERCEL_QUEUE_BASE_URL

VERCEL_QUEUE_DEBUG

Fixed base URL or {region} template override

Set to 1 or true to enable debug logging

Last updated August 24, 2026

Cross-link map: Vercel Queues: Python SDK Reference (/docs/queues/python-sdk)From the Vercel docs graph (built 2026-09-21T05:26:59.511Z), spanning vercel.com docs + KB, nextjs.org, ai-sdk.dev, and other Vercel documentation sites. Full graph as JSON: https://vercel.com/docs/graph.jsonSemantically closest pagesVercel Queues: JS SDK Reference — Publish and consume messages with the Vercel Queues SDK for JavaScript and TypeScript.Poll Mode — Consume messages from Vercel Queues by polling on your own schedule, from any environment.Quickstart — Set up Vercel Queues with the SDK.Vercel Queues — Publish agent events and background work to durable topics with independent consumers, automatic retries, and at-least-oQueues concepts — Learn delivery, retries, visibility timeouts, and deployment isolation in Vercel Queues.This page links to (3)API Reference — HTTP API reference for Vercel Queues. Publish, consume, acknowledge, and manage messages.Poll Mode — Consume messages from Vercel Queues by polling on your own schedule, from any environment.Vercel Queues: JS SDK Reference — Publish and consume messages with the Vercel Queues SDK for JavaScript and TypeScript.Pages that link here (7)By site: vercel-changelog (1) · vercel-docs (6)From vercel-changelogVercel Python Queues SDK is now available in betaFrom vercel-docsDeploy Dramatiq workers on Vercel — Deploy Dramatiq workers on Vercel. Learn how Dramatiq actors use Vercel Queues and Vercel Functions to process backgrounVercel Queues — Publish agent events and background work to durable topics with independent consumers, automatic retries, and at-least-oQueues concepts — Learn delivery, retries, visibility timeouts, and deployment isolation in Vercel Queues.Poll Mode — Consume messages from Vercel Queues by polling on your own schedule, from any environment.Quickstart — Set up Vercel Queues with the SDK.Vercel Queues: JS SDK Reference — Publish and consume messages with the Vercel Queues SDK for JavaScript and TypeScript.

Was this helpful?