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
Default
Regional API URL
Start Now- Current deployment
- Mapping[str, str]
- DeploymentOption
- int, float, timedelta, or None
Description
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
consumer_group
Start Now- initial_delay
- max_concurrency
- int, float, or timedelta
- int
retry_after
Topic filter. A trailing * matches by prefix, and "*" matches every topic
Start Now- No delay
- str | SanitizedName
- -
- Service default
Description
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)
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.
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:
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
- Type
- str | Topic[T]
- Required
- int
Default
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
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
VERCEL_DEPLOYMENT_ID
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?