Queue
The queue delivers a payload to a worker. It is at-least-once and fire-and-forget: there is no result storage and no per-message status. A message is enqueued, then a push-worker's HTTP handler is invoked to process it.
Enqueue
import { createSdk } from "@bext-stack/framework";
const sdk = createSdk("<app-id>");
const { id } = await sdk.queue.push("emails", { to: "a@b.com" }, /* delaySecs */ 30);
Or over HTTP:
curl -s http://127.0.0.1/__bext/sdk/queue/push \
-H "X-Bext-App-Id: <app-id>" \
-H "Content-Type: application/json" \
-d '{"queue":"emails","payload":{"to":"a@b.com"},"delay_seconds":30}'
Endpoints
Scoped by X-Bext-App-Id. POST unless marked GET:
| Endpoint | Body / notes | Returns |
|---|---|---|
/queue/push |
{ queue, payload, delay_seconds? } |
{ id } |
/queue/pull |
pull messages | — |
/queue/ack |
acknowledge a message | — |
/queue/stats |
GET | queue stats |
/queue/list |
GET | queues |
/queue/dead/list |
list dead-letter messages | — |
/queue/dead/retry |
retry dead-letter messages | — |
/queue/dead/purge |
purge dead-letter messages | — |
/queue/worker/register |
{ queue, handler_url, concurrency?, visibility_timeout_secs?, max_attempts? } |
— |
/queue/worker/unregister |
remove a worker | — |
/queue/worker/list |
list registered workers | — |
Push-worker handler contract
Register a push-worker with a handler_url. Register it as the app's public vhost
URL — for example https://<app>.inklura.fr/api/queue/<name> — not a loopback address:
curl -s http://127.0.0.1/__bext/sdk/queue/worker/register \
-H "X-Bext-App-Id: <app-id>" \
-H "Content-Type: application/json" \
-d '{
"queue": "emails",
"handler_url": "https://<app>.inklura.fr/api/queue/emails",
"concurrency": 4,
"visibility_timeout_secs": 60,
"max_attempts": 5
}'
The dispatcher POSTs this body to your handler_url:
{ "id": "<message-id>", "queue": "emails", "payload": { }, "attempts": 1 }
Your handler's response status decides the outcome:
| Response | Meaning |
|---|---|
2xx |
ack — message done |
408, 429, 5xx |
retry — redelivered later |
any other 4xx |
dead-letter — moved to the dead queue |
Implement the handler as a route.ts:
export async function POST(request: Request) {
const { id, payload, attempts } = await request.json();
try {
await sendEmail(payload);
return new Response(null, { status: 200 }); // ack
} catch (e) {
return new Response(String(e), { status: 500 }); // retry
}
}
Semantics to design for
- At-least-once — a handler may receive the same message more than once. Make handlers
idempotent (e.g. dedupe on
id). - Fire-and-forget — the enqueuer gets an
idbut nothing else; there is no built-in way to ask "did message X succeed?". - Dead-letter — messages that exhaust retries (or return a non-retry
4xx) land in the dead queue; inspect / retry / purge with/queue/dead/*.
Layer a job record when you need status
Because there is no result store, apps that need outcomes keep a durable job record in
KV — keys like job:<id> updated by the
handler (queued → running → complete / fail).
Queue vs Tasks
If you need long-running work with built-in status and progress, use the warm task-executor instead — see Tasks & Scheduler. The queue is best for high-volume, fire-and-forget delivery to a public handler.
Where to next
| Page | What it covers |
|---|---|
| Tasks & Scheduler | Long jobs with status/progress; cron |
| KV Store | The durable job-record pattern |
| Queue Workers | Operating and observing workers |