Queue usage
After the quickstart works, most Queue code falls into four patterns: define a typed job, enqueue a payload, choose whether dispatch is awaited or deferred, and let the provider deliver the handler later.
Define payload types
Export payload types from the queue definition when producer code needs them.
import { defineQueue } from '@vitehub/queue'
export type WelcomeEmailPayload = {
email: string
template: 'default' | 'vip'
}
export default defineQueue<WelcomeEmailPayload>(async (job) => {
console.log(job.id, job.attempts, job.payload.email)
})
The handler receives a QueueJob<TPayload> with id, payload, attempts, and optional provider metadata.
Keep producers provider-neutral
The route should not know whether Cloudflare or Vercel is delivering the job.
const result = await runQueue('welcome-email', payload)
Provider details belong in config:
queue: {
provider: 'cloudflare',
}
queue: {
provider: 'vercel',
region: 'fra1',
}
Send a bare payload
Pass the payload directly when you do not need delivery options:
await runQueue('welcome-email', {
email: 'ava@example.com',
template: 'vip',
})
runQueue() resolves after the provider accepts the send:
{
"status": "queued",
"messageId": "queue_7f1b6f8e-7b5c-4c5e-b3a1-8d6a4b3d4c2a"
}
Send an enqueue envelope
Pass an object with payload when you need delivery options:
await runQueue('welcome-email', {
id: 'welcome-email-1',
payload: {
email: 'ava@example.com',
template: 'vip',
},
delaySeconds: 30,
idempotencyKey: 'welcome-email-1',
})
The shared envelope supports these fields:
| Field | Provider support | Description |
|---|---|---|
payload | Cloudflare, Vercel | The job payload delivered to the queue handler. |
id | Cloudflare, Vercel | Message id used in the ViteHub enqueue result. Vercel also uses it as the default idempotency key. |
delaySeconds | Cloudflare, Vercel | Delay delivery when the provider supports it. |
contentType | Cloudflare | Payload encoding passed to Cloudflare Queues. |
idempotencyKey | Vercel | Deduplicate sends through Vercel Queue. |
region | Vercel | Override the Vercel region for this send. |
retentionSeconds | Vercel | Keep a queued message longer when supported by Vercel Queue. |
QueueError. Cloudflare rejects idempotencyKey and retentionSeconds; Vercel rejects contentType.Defer dispatch until after the response
Use deferQueue() when the route should return immediately and enqueue dispatch can run through the current runtime context:
import { deferQueue } from '@vitehub/queue'
export default defineEventHandler(() => {
deferQueue('welcome-email', {
email: 'ava@example.com',
template: 'default',
})
return { ok: true }
})
deferQueue() uses waitUntil() when the runtime provides it. If enqueue fails, Queue logs the failure and calls onDispatchError from the queue definition when configured.
export default defineQueue<WelcomeEmailPayload>(async (job) => {
console.log(job.payload.email)
}, {
onDispatchError(error, context) {
console.error(`Deferred dispatch failed for ${context.name}`, error)
},
})
Resolve the active provider client
Use getQueue() when you need the concrete provider client instead of a plain send call:
import { getQueue } from '@vitehub/queue'
const queue = await getQueue('welcome-email')
if (queue.provider === 'cloudflare') {
console.log(queue.binding)
}
Provider-specific client methods:
| Provider | Extra methods |
|---|---|
| Cloudflare | sendBatch(), createBatchHandler() |
| Vercel | callback() |
Queue naming rules
Queue names always come from discovered files:
server/queues/welcome-email.tsbecomeswelcome-emailserver/queues/email/welcome.tsbecomesemail/welcome
Next steps
- Use Enqueue a job for full producer examples.
- Use Handle provider delivery for Cloudflare and Vercel consumer behavior.
- Use Runtime API for exact signatures and exported types.

