The queue that is a file.
bunqueue is a job queue for the Bun runtime. It stores jobs in SQLite, so there is no Redis and no extra server to run. Add a job now, a worker processes it in the background, and nothing is lost on restart.
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. bunqueue gives you that with one bun add, no external infrastructure.
See it in 10 lines
Section titled “See it in 10 lines”import { Queue, Worker } from 'bunqueue/client';
const queue = new Queue('emails', { embedded: true });
const worker = new Worker('emails', async (job) => { console.log('Sending to', job.data.to); return { sent: true };}, { embedded: true });
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 queue = new Queue('emails');
const worker = new Worker('emails', async (job) => { console.log('Sending to', job.data.to); return { sent: true };});
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 infrastructure. Jobs persist in SQLite, a single file on disk. No Redis, no broker. Only 2 runtime dependencies, a 5.5 MB install.
- Native Bun. Built on
bun:sqlitefor performance, around 100k jobs/sec in the default mode. - 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”| 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 | dataPath option | --data-path flag |
Embedded mode means the queue lives inside your app, like using SQLite instead of Postgres:
import { Queue, Worker } from 'bunqueue/client';
// Both must have embedded: trueconst queue = new Queue('tasks', { embedded: true });const worker = new Worker('tasks', async (job) => { /* ... */ }, { embedded: true });Server mode runs bunqueue as its own process, and any number of apps connect to it over TCP:
bunqueue start --data-path ./data/queue.db// No embedded option = connects to localhost:6789const queue = new Queue('tasks');const worker = new Worker('tasks', async (job) => { /* ... */ });import { Queue, Worker } from 'bunqueue-client';
const queue = new Queue('tasks'); // connects to localhost:6789const 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 | SQLite | Redis |
| External deps | None | Redis server |
| Priorities, delays, retries, cron | Yes | Yes |
| Rate limiting, stall detection, flows | Yes | Yes |
| Advanced DLQ (auto-retry, filters) | Yes | Basic |
| S3 backups | Yes | 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