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.

Get started →

MIT · built on BullMQ · runs on Bun & Node

openqueue · my-app · 4 queues

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

p50 wait12ms
p95 duration847ms
BullMQRedisZodBunHonoNext.jsDrizzleOpenTelemetryDocker

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.
worker/export-csv.tstypescript
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.

exports · last 50 runs

Runs 184,679

All 184kActive 47Completed 184kFailed 287
Job IDNameDurationAttemptsAge
r_3aa18export-csv active1/3now
r_3aa17send-invoice active1/32s
r_3aa16export-csv completed284ms1/38s
r_3aa15sync-contacts completed1.2s1/314s
r_3aa14export-csv failed1.8s3/322s
r_3aa13ocr-document completed612ms1/331s
r_3aa12send-invoice completed4.7s2/344s
r_3aa11send-receipt delayed0/3in 2m
r_3aa10export-csv completed198ms1/31m
r_3aa09sync-contacts waiting0/31m

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.

my-app · errors · last 24h

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

order-fulfillment

flow_4f12 · 7 jobs · 2.4s total

✓ completed

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.

my-app · 6 schedules

Schedules

5 active · next: in 2m

NameCronNextLast run
weekly-digest0 9 * * MONin 2d 4hok · 1.2s
cleanup-stale*/15 * * * *in 4mok · 87ms
reindex-search0 */6 * * *in 1h 12mok · 4.7s
trial-reminders0 10 * * *in 18hok · 612ms
❚❚rotate-creds0 0 1 * *in 6dok · 28ms
warm-cache*/5 * * * *in 2mok · 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.

zsh — my-app
$ 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.

Read the quickstart →