Queue Workers
The bext SDK queue is an at-least-once push queue. You enqueue payloads (see
SDK → Queue) and register a push-worker: an HTTP handler the dispatcher
POSTs each message to. It is reached over the loopback SDK at
http://127.0.0.1/__bext/sdk/queue/*, authenticated by the X-Bext-App-Id header (see
SDK Overview).
Registering a worker
POST http://127.0.0.1/__bext/sdk/queue/worker/register
X-Bext-App-Id: <app-id>
Content-Type: application/json
Body fields:
| Field | Required | Notes |
|---|---|---|
queue |
yes | Queue name to consume |
handler_url |
yes | Your app's public vhost URL for this queue |
concurrency |
no | Max in-flight deliveries |
visibility_timeout_secs |
no | How long a delivery is held before it's retried |
max_attempts |
no | Attempts before a message is dead-lettered |
Register handler_url as the app's public URL, not a loopback path — the dispatcher
calls it like any HTTP client:
await fetch("http://127.0.0.1/__bext/sdk/queue/worker/register", {
method: "POST",
headers: { "X-Bext-App-Id": "<app-id>", "content-type": "application/json" },
body: JSON.stringify({
queue: "emails",
handler_url: "https://<app>.inklura.fr/api/queue/emails",
concurrency: 4,
max_attempts: 5,
}),
});
The handler contract
On each delivery the dispatcher POSTs a JSON envelope to your handler_url:
{ "id": "<message-id>", "queue": "emails", "payload": { }, "attempts": 1 }
Your handler's HTTP status decides the outcome:
| Response status | Outcome |
|---|---|
2xx |
Ack — the message is done and removed |
408, 429, 5xx |
Retry — redelivered later (until max_attempts) |
any other 4xx |
Dead-letter — moved to the dead-letter queue, not retried |
// src/app/api/queue/emails/route.ts
export async function POST(req: Request) {
const { id, payload, attempts } = await req.json();
try {
await sendEmail(payload);
return new Response("ok"); // 2xx → ack
} catch (e) {
if (isTransient(e)) return new Response("retry", { status: 503 }); // 5xx → retry
return new Response("bad payload", { status: 400 }); // 4xx → dead-letter
}
}
Delivery is at-least-once. A message can arrive more than once (a 2xx that never reached
the dispatcher, a redelivery after a visibility timeout). Make handlers idempotent — key
side effects on the message id.
No result storage — layer a durable job record
The queue is fire-and-forget with no result storage. Once a message is acked it's gone;
there is no built-in place to read "did job X succeed, what did it return." If you need
status or a result, write your own durable record — the idiomatic place is KV
under a job:<id> key.
// on enqueue
await sdk.kv.set(`job:${id}`, { status: "queued", createdAt: Date.now() });
// in the handler
await sdk.kv.set(`job:${id}`, { status: "running" });
// …work…
await sdk.kv.set(`job:${id}`, { status: "done", result });
Dead-letter operations
Messages that exhaust max_attempts or return a non-retryable 4xx land in the dead-letter
queue. Manage them over the SDK:
| Method | Path | Purpose |
|---|---|---|
POST |
/__bext/sdk/queue/dead/list |
List dead-lettered messages |
POST |
/__bext/sdk/queue/dead/retry |
Re-enqueue dead-lettered messages |
POST |
/__bext/sdk/queue/dead/purge |
Drop dead-lettered messages |
Related: company-manager BullMQ queues
The queue described here is the bext SDK queue that a PRISM app owns. Separately, the
company-manager Next.js backend runs its own BullMQ (Redis) queues declared in a
QUEUE_REGISTRY — webhook, webhook-retry, email-*, seo-*, social-*,
woocommerce, prestashop, and the workflow-* automation queues. Those are
platform-internal and not registered through the SDK; see
Events Overview and
Inbound Webhooks (inbound events enqueue onto the webhook queue).
See also
- SDK → Queue — enqueuing, delays, the client
- SDK → Tasks — the warm task-executor for long-running jobs
- Scheduled Jobs — recurring work