- Docs
- Start Here
- Introduction
A job queue for your language.
bunqueue is a free, MIT-licensed job queue for Node.js, Deno, Python, PHP, Go, Rust, Elixir and Bun. The name comes from the core engine, which runs on Bun. Your application and workers use their own runtime through an official client.
A job queue lets your app hand off slow work (sending emails, processing images, calling APIs) to run in the background instead of blocking a request.
Choose the setup that matches your application:
- Node.js, Deno, Python, PHP, Go, Rust or Elixir: run the included bunqueue server, install your language’s client, then connect it to the server. Bun is required on the server, not in your application. Follow the server and client setup.
- Bun: use the server in the same way, or run the queue and worker together with
embedded: true. Follow the embedded quickstart.
Both options are free and include the queue features. No account, paid plan or Redis service is required. Use memory for ephemeral jobs, SQLite for one-process persistence, or PostgreSQL for several active brokers.
See it in 10 lines
Section titled “See it in 10 lines”import { Queue, Worker } from 'bunqueue/client';
const storage = { embedded: true, dataPath: './data/bunq.db' } as const;const queue = new Queue('emails', storage);
const worker = new Worker( 'emails', async (job) => { console.log('Sending to', job.data.to); return { sent: true }; }, storage);
await queue.add('welcome', { to: 'user@example.com' });Run it with bun run app.ts. No server: the queue runs embedded in your process.
import { Queue, Worker } from 'bunqueue-client';
const storage = { embedded: false } as const;const queue = new Queue('emails', storage);
const worker = new Worker( 'emails', async (job) => { console.log('Sending to', job.data.to); return { sent: true }; }, storage);
await queue.add('welcome', { to: 'user@example.com' });Start the server once (bunx bunqueue start), then run the file with
node --experimental-strip-types app.ts (Node 22+) or deno run -A app.ts.
from bunqueue import Queue, Worker
queue = Queue("emails")queue.add("welcome", {"to": "user@example.com"})
def process(job): print("Sending to", job.data["to"]) return {"sent": True}
Worker("emails", process).run()Start the server once (bunx bunqueue start), then python app.py.
use Bunqueue\Queue;use Bunqueue\Worker;
$queue = new Queue('emails');$queue->add('welcome', ['to' => 'user@example.com']);
$worker = new Worker('emails', function (Bunqueue\Job $job) { return ['sent' => true];});$worker->run();Start the server once (bunx bunqueue start), then php worker.php.
queue := bunqueue.NewQueue("emails", bunqueue.Options{})defer queue.Close()queue.Add("welcome", map[string]any{"to": "user@example.com"}, nil)
worker := bunqueue.NewWorker("emails", func(job *bunqueue.Job) (any, error) { return map[string]any{"sent": true}, nil}, bunqueue.WorkerOptions{})worker.Run()Start the server once (bunx bunqueue start), then go run ..
use bunqueue_client::{ConnectionOptions, JobOptions, Queue, Value, Worker, WorkerOptions};
let queue = Queue::new("emails", ConnectionOptions::default());let data = Value::Map(vec![(Value::from("to"), Value::from("user@example.com"))]);queue.add("welcome", data, JobOptions::default())?;
let worker = Worker::new("emails", |_job| Ok(Value::from(true)), WorkerOptions::default());worker.run()?;Start the server once (bunx bunqueue start), then cargo run.
queue = Bunqueue.queue("emails"){:ok, _job} = Bunqueue.Queue.add(queue, "welcome", %{to: "user@example.com"})
worker = Bunqueue.Worker.new("emails", fn _job -> {:ok, %{sent: true}} end)
Bunqueue.Worker.run(worker)Start the server once (bunx bunqueue start), then mix run app.exs.
That is a complete, working queue. The Quick Start walks through it step by step.
Why bunqueue?
Section titled “Why bunqueue?”- Zero external infrastructure. Add
dataPathfor one-file SQLite persistence, or omit it for memory-only queues. No Redis, no separate broker, and only one runtime dependency. - Multi-broker when needed. PostgreSQL 15–18 coordinates independent standalone servers with transactional claims and fenced leases; 18.6 is recommended.
- Native Bun. Local persistence uses
bun:sqlite; PostgreSQL uses Bun’s built-inSQLclient directly, with no ORM or third-party database driver. - Familiar API. If you know BullMQ, you already know most of bunqueue.
- Production features. Retries with backoff (automatic waiting between retry attempts), a dead letter queue (a holding area for jobs that keep failing), cron scheduling, rate limiting, stall detection, and S3 backups.
- AI-agent ready. A built-in MCP server with 73 tools lets agents like Claude add jobs, manage crons, and monitor queues via natural language.
Two ways to run it
Section titled “Two ways to run it”| Comparison | Embedded | Server |
|---|---|---|
| What it is | A library inside your process | A standalone bunqueue process |
| Best for | Single-process apps, scripts, serverless | Multiple services sharing one queue |
| Setup | Pass embedded: true | Run bunqueue start, then connect |
| Persistence | Memory or SQLite via dataPath | Memory/SQLite, or PostgreSQL via URL |
| Broker scaling | One process | One SQLite broker or several PostgreSQL brokers |
Embedded mode means the queue lives inside your app, like using SQLite instead of Postgres:
import { Queue, Worker } from 'bunqueue/client';
// Queue and Worker must use the same embedded storage configuration.const storage = { embedded: true, dataPath: './data/bunq.db' } as const;const queue = new Queue('tasks', storage);const worker = new Worker( 'tasks', async (job) => { /* ... */ }, storage);Server mode runs bunqueue as a standalone service, and any number of apps connect to it over TCP. Use SQLite for one broker:
bunqueue start --data-path ./data/queue.dbOr point independent servers at the same PostgreSQL namespace, with a unique broker ID for each process:
BUNQUEUE_POSTGRES_URL='postgres://bunqueue:secret@postgres:5432/bunqueue' \BUNQUEUE_POSTGRES_NAMESPACE=production \BUNQUEUE_BROKER_ID=broker-a \bunqueue startSee Storage backends before choosing a production topology.
// No embedded option = connects to localhost:6789const queue = new Queue('tasks');const worker = new Worker('tasks', async (job) => { /* ... */});// No embedded option = connects to localhost:6789const queue = new Queue('tasks');const worker = new Worker('tasks', async (job) => { /* ... */});from bunqueue import Queue, Worker
queue = Queue("tasks") # connects to localhost:6789
def process(job): ...
Worker("tasks", process).run()use Bunqueue\Queue;use Bunqueue\Worker;
$queue = new Queue('tasks'); // connects to localhost:6789$worker = new Worker('tasks', function (Bunqueue\Job $job) { // ...});$worker->run();queue := bunqueue.NewQueue("tasks", bunqueue.Options{}) // localhost:6789
worker := bunqueue.NewWorker("tasks", func(job *bunqueue.Job) (any, error) { // ... return nil, nil}, bunqueue.WorkerOptions{})worker.Run()use bunqueue_client::{ConnectionOptions, Queue, Value, Worker, WorkerOptions};
let queue = Queue::new("tasks", ConnectionOptions::default()); // localhost:6789let worker = Worker::new("tasks", |_job| Ok(Value::Nil), WorkerOptions::default());worker.run()?;queue = Bunqueue.queue("tasks") # connects to localhost:6789
worker = Bunqueue.Worker.new("tasks", fn _job -> {:ok, %{}} end)Bunqueue.Worker.run(worker)Server mode also unlocks clients in other runtimes: Node.js, Deno, Python, PHP, Go, Rust, Elixir, and Cloudflare Workers via the client SDKs.
Compared to BullMQ
Section titled “Compared to BullMQ”| Feature | bunqueue | BullMQ |
|---|---|---|
| Runtime | Bun | Node.js |
| Storage | Memory/SQLite; optional PostgreSQL | Redis |
| External deps | None by default; PostgreSQL when selected | Redis server |
| Priorities, delays, retries, cron | Yes | Yes |
| Rate limiting, stall detection, flows | Yes | Yes |
| Pro-style groups and processor batches | Yes | Pro package |
| Pro telemetry / NestJS integration | No | Pro package |
| Advanced DLQ (auto-retry, filters) | Yes | Basic |
| S3 backups | SQLite mode | No |
| MCP server for AI agents | Yes (73 tools) | No |
| Built-in workflow engine | Yes | No |
Migrating? The API is intentionally close to BullMQ, see the migration guide.
Beyond jobs: workflows
Section titled “Beyond jobs: workflows”For multi-step processes (validate an order, charge, notify, ship) bunqueue ships a workflow engine with retries, parallel steps, branching, rollback on failure, and human-in-the-loop signals:
import { Workflow, Engine } from 'bunqueue/workflow';
const flow = new Workflow('order') .step('validate', async (ctx) => ({ ok: true })) .step('charge', async (ctx) => ({ txId: 'tx_123' }), { retry: 3 }) .waitFor('approval') .step('ship', async (ctx) => ({ shipped: true }));No Temporal, no extra service. See the Workflow Engine guide.
Next steps
Section titled “Next steps”- Installation, get bunqueue installed
- Quick Start, build your first queue in a minute
- Server Mode, run bunqueue as a standalone service
- MCP Server, connect AI agents to your queues