Background jobs,
batteries included.
OpenQueue is a TypeScript job framework built on BullMQ and Redis. Typed tasks, retries, cron, flows, and a dashboard you didn't have to build — one config file, one CLI, zero glue code.
MIT · built on BullMQ · runs on Bun & Node
exports
47 active · 1,204 waiting · last 24h
completed
184,392
+12.4%
failed
287
-3.1%
active
47
+8/min
waiting
1,204
stable
Throughput · jobs/min
■ completed ■ failed
Tasks
If you can write a function, you can ship a job.
A task is an id, a schema, and a function. OpenQueue wires up the queue, the worker, validation, retries, backoff, and concurrency — then hands you a typed .trigger() to call from anywhere in your app.
- →Payloads validated by Zod before they ever hit Redis.
- →Logs and progress stream straight into the dashboard.
- →Defaults that make sense: 3 attempts, exponential backoff.
import { task } from '@openqueue/sdk';
import { z } from 'zod';
export const exportCsv = task({
id: 'export-csv',
schema: z.object({
reportId: z.string(),
format: z.enum(['csv', 'xlsx']),
}),
attempts: 5,
backoff: { type: 'exponential', delay: 1000 },
run: async (payload, ctx) => {
ctx.logger.info('building report', { id: payload.reportId });
const file = await build(payload);
await ctx.progress({ step: 'uploading' });
return { url: file.url };
},
});
// anywhere in your app — typed, validated, durable
await exportCsv.trigger({ reportId: 'rep_812', format: 'csv' });
Runs
Inspect every run. Replay any failure.
Full payloads, attempt history, logs, and progress for every job. Filter by status, search by ID, retry from the UI — and keep history in Postgres long after Redis forgets.
Runs 184,679
| Job ID | Name | Duration | Attempts | Age | |
|---|---|---|---|---|---|
| r_3aa18 | export-csv active | — | 1/3 | now | |
| r_3aa17 | send-invoice active | — | 1/3 | 2s | |
| r_3aa16 | export-csv completed | 284ms | 1/3 | 8s | |
| r_3aa15 | sync-contacts completed | 1.2s | 1/3 | 14s | |
| r_3aa14 | export-csv failed | 1.8s | 3/3 | 22s | |
| r_3aa13 | ocr-document completed | 612ms | 1/3 | 31s | |
| r_3aa12 | send-invoice completed | 4.7s | 2/3 | 44s | |
| r_3aa11 | send-receipt delayed | — | 0/3 | in 2m | |
| r_3aa10 | export-csv completed | 198ms | 1/3 | 1m | |
| r_3aa09 | sync-contacts waiting | — | 0/3 | 1m |
Errors
Triage failures, not log files.
Failures are grouped by error class, ranked by frequency, and trended over time — so a regression shows up as a spike the moment it ships, not as a support ticket three days later.
⊘Top errors · last 24h
287 events
- ECONNRESET184
- TimeoutError63
- ValidationError28
- S3AccessDenied12
ECONNRESET · exports · spiking +240% vs yesterday · first seen 4h ago
Flows
Pipelines as parent/child tasks.
enqueueFlow turns task.child trees into real DAGs with per-node status and duration. Drill into any node and see exactly where the time went.
order-fulfillment
flow_4f12 · 7 jobs · 2.4s total
validate-cart
completed 42ms
charge-card
completed 612ms
reserve-stock
completed 118ms
email-receipt
completed 287ms
generate-pdf
completed 894ms
notify-warehouse
completed 71ms
finalize
completed 33ms
Schedules
Cron you can change without redeploying.
Declare cron on a task for static schedules, or create and pause them at runtime with task.schedules.create(). See what runs next, what ran last, and how long it took.
Schedules
5 active · next: in 2m
| Name | Cron | Next | Last run | |
|---|---|---|---|---|
| ▶ | weekly-digest | 0 9 * * MON | in 2d 4h | ok · 1.2s |
| ▶ | cleanup-stale | */15 * * * * | in 4m | ok · 87ms |
| ▶ | reindex-search | 0 */6 * * * | in 1h 12m | ok · 4.7s |
| ▶ | trial-reminders | 0 10 * * * | in 18h | ok · 612ms |
| ❚❚ | rotate-creds | 0 0 1 * * | in 6d | ok · 28ms |
| ▶ | warm-cache | */5 * * * * | in 2m | ok · 142ms |
What's inside
Everything a job framework should do. Nothing it shouldn't.
No proprietary runtime, no per-job pricing, no second cloud. Your Redis, your Postgres, your code — OpenQueue just makes them feel like a product.
Typed payloads
Give a task a Zod schema and payloads are validated at trigger time and fully typed inside run — no any, no guessing.
Retries with taste
Exponential backoff by default. Throw NonRetryableError to stop, RetryableError to keep going — control flow, not string matching.
Cron + live schedules
Declare cron on the task, or create, list, and pause schedules at runtime through the schedules API. No redeploy to change a cadence.
Flows & DAGs
Compose parent/child trees with enqueueFlow and task.child. Per-node status and duration, rendered as a real graph.
Workbench built in
Overview, runs, flows, schedules, errors, metrics, and a test console — served by your worker, or mounted in your own app.
Postgres run history
The Drizzle adapter persists runs, logs, and alerts past Redis retention. Your audit trail survives a FLUSHALL.
OpenTelemetry native
Trace context propagates from enqueue to process automatically. Plug in any exporter and see jobs inside your existing traces.
Hot-reload dev
openqueue dev watches your task files and restarts the worker in milliseconds. The dashboard reconnects on its own.
CLI
Four commands. That's the whole workflow.
init scaffolds a config and your first task. dev runs the worker with hot reload. build bundles every task into one manifest, and start runs it in production. Failed runs retry with backoff and land in the dashboard — not in your logs at 3am.
$ bunx @openqueue/cli init
created worker.config.ts
created worker/example.ts
OpenQueue initialized
$ openqueue dev
[openqueue] 3 tasks · 2 queues · 1 schedule
[openqueue] workbench on http://localhost:8787/workbench
[openqueue] watching ./worker for changes
✓ export-csv r_3aa16 · 284ms
✓ sync-contacts r_3aa15 · 1.2s
✗ export-csv r_3aa14 · attempt 2/3 · retrying in 2s
Run it
One worker. Three ways to run it.
Develop
Next to your app
openqueue dev discovers tasks from your worker/ directory, hot-reloads on change, and serves Workbench locally. One process, no Docker, no YAML.
$ openqueue dev
Embed
Inside your server
Mount the dashboard in the app you already run. Adapters for Hono and Next.js App Router — one catch-all route and Workbench shares your auth.
$ import { workbench } from '…/next'
Ship
In a container
openqueue build bundles every task into a single manifest; openqueue start runs it. Or point the prebuilt Docker worker at your Redis and go.
$ openqueue build && openqueue start
Stop babysitting Redis.
You already run the infrastructure. OpenQueue turns it into a job platform — in about the time it takes your CI to go green.