A Postgres-Backed Job Queue You Don't Have to Run
Move slow work off the request path without standing up Redis and a worker fleet. A durable, Postgres-backed job queue with retries, backoff, a dead-letter path, and visibility-timeout reaping — built in.
There is a moment in most backends when a request starts doing too much. The HTTP handler that used to just write a row now also resizes an image, calls a third-party API, and sends an email — and suddenly your p99 is three seconds and a downstream outage takes your endpoint down with it.
The fix is old and well understood: put the slow work on a queue and answer the request immediately. The catch is that running a queue is its own project. You stand up Redis or SQS, write a worker process, keep it alive, drain it on deploy, add retry logic, and build a dead-letter path for the jobs that never succeed. That is a lot of infrastructure to move one function call off the request path.
Inquir gives you the queue without the infrastructure.
Enqueue and answer
From any handler — HTTP, webhook, or cron — you hand work to a durable background job and return right away:
export async function handler(event) {
const payload = JSON.parse(event.body);
await global.durable.startNew('processUpload', undefined, payload);
return { statusCode: 202, body: JSON.stringify({ accepted: true }) };
}
The request is done in milliseconds. processUpload runs later, on a managed worker pool, in its own isolated container. There is no Redis connection string, no worker service to keep running, and no drain script to run on deploy.
What “durable” buys you
The queue is Postgres-backed, and that is the point — the jobs outlive the process that created them. Three properties come out of that:
- Retries with backoff. A job carries an attempt count and a ceiling. A failure with attempts remaining is requeued with exponential backoff, not dropped. You set the ceiling; the platform does the waiting and re-delivery.
- A dead-letter path. When a job exhausts its attempts, it does not vanish into a log line — it lands in a dead-letter state with its last error recorded, so you can inspect poison messages and replay them after a fix instead of losing them.
- Visibility-timeout reaping. If a worker claims a job and then crashes mid-run, the job does not sit “in progress” forever. Its lease expires and it is re-driven, so an unlucky restart costs you a retry, not a lost job.
Those are exactly the three things people bolt onto Redis by hand, and here they are the default behavior of the queue.
Every run is a record
Because the queue lives in the database, each job is also a row you can look at. Input payload, attempt count, last error, final status — all queryable. When on-call gets paged with “did the receipt for order 5512 actually send?”, that is a lookup, not a grep through worker stdout. Compare that to a Redis queue, where a processed job is a key that expired an hour ago.
Being honest about what it is not
This is a durable job queue, not a high-throughput message broker. If you need millions of strictly-ordered messages per second, or exactly-once delivery guarantees, you want Kafka and a team to run it. What this replaces is the operational queue most applications actually have: “run this work reliably, off the request path, with retries and a place for failures to go.”
And retries mean a job can run more than once, so the usual rule applies — make handlers idempotent where a second run could double an effect. The queue promises not to lose your job; it does not promise to call it exactly once.
The shape it encourages
Put together, the pattern for almost any “do it later” workload becomes small:
- The entry point (HTTP, webhook, cron) validates and enqueues, then returns fast.
- The job function does the real work in isolation, with retries and backoff handled for it.
- Failures that survive every attempt wait in the dead-letter state for a human.
- The whole thing is inspectable per run, with no separate queue dashboard to host.
If you have been carrying a Redis instance and a worker deployment just to move email and image processing off your API, that is the infrastructure this is meant to delete. You write the job; the queue is already running.