@imqueue/job vs BullMQ: a simple Redis job queue vs a feature-rich one
Both are Redis-backed job queues for Node.js. @imqueue/job is small, safe-by-default and scheduling-capable; BullMQ is the feature-rich one. Here's an honest split — plus the thing @imqueue does that BullMQ doesn't.
If you need to run background jobs on Redis in Node.js, BullMQ is the name everyone reaches for. But @imqueue also ships a job queue — @imqueue/job — built on the same Redis-backed core as the rest of the framework. This is an honest comparison of the two, plus one thing @imqueue does that BullMQ doesn't. (BullMQ details reflect its documented behavior at the time of writing.)
The one-line version
@imqueue/job is a deliberately simple, safe-by-default job queue; BullMQ is the feature-rich one. Both run on Redis, both do concurrent workers, delayed jobs, and automatic retries. BullMQ adds a much larger job-lifecycle surface (a declarative retry/backoff policy with dead-lettering, priorities, repeatable/cron jobs, rate limiting, flows, a dashboard). @imqueue/job keeps a tiny footprint and leans on guaranteed delivery being on by default.
What @imqueue/job gives you
@imqueue/job is a Redis-backed job queue with a small, focused feature set:
- Guaranteed processing by default. Safe delivery is on out of the box: if a worker grabs a job and dies mid-processing, the job is re-queued for another worker (a per-worker lock TTL,
safeLockTtl, controls how long before it's considered dead). No data loss. - Concurrent workers on one queue — competing consumers with natural load balancing, no separate balancer.
- Delayed / scheduled jobs to millisecond granularity:
push(data, { delay }). - Job expiration (TTL) — a job can live forever or expire after a set time.
- Publisher / worker / both roles, so you can split producers and consumers across processes.
- gzip payload compression, and it's TypeScript-first.
- One dependency (
@imqueue/core), event-driven (no polling), low idle cost.
import JobQueue from '@imqueue/job';
new JobQueue<string>({ name: 'Emails' })
.onPop(job => sendEmail(job))
.start()
.then(q => q
.push('[email protected]')
.push('[email protected]', { delay: 60_000 })); // run in 1 minute
Delayed & scheduled delivery is first-class — everywhere in @imqueue
This is worth stating plainly, because it's easy to assume a "simple" queue can't schedule: delayed delivery is a core capability across the whole framework, not an afterthought.
- In
@imqueue/core, the fundamentalsend(toQueue, message, delay?)takes a delay, implemented with a dedicated delayed-set scored by due time, promoted by Redis keyspace notifications (with a polling fallback) — a proper scheduler, not a hack. - In
@imqueue/rpc, anIMQDelayvalue (withms/s/m/h/dunits) lets you delay any remote call. - In
@imqueue/job, that surfaces aspush(data, { delay }).
So scheduling to the millisecond is built in at every layer.
Retries and backoff
@imqueue/job retries failed jobs automatically, and the retry timing is under your control:
- A handler that throws re-schedules the job (with its original delay) — an automatic retry on failure.
- A handler that returns a delay in milliseconds re-runs the job after that delay — so you can shape the backoff (return a growing delay for exponential backoff), and finish by returning nothing.
- If a worker dies mid-job, safe delivery re-queues it after
safeLockTtl.
new JobQueue<string>({ name: 'Fetch' })
.onPop(async (url) => {
await fetchAndStore(url); // if this throws, the job is retried
})
.start();
So retries and backoff do exist. What BullMQ adds on top is a declarative policy — a fixed attempts cap plus a named backoff strategy — with the job automatically dead-lettered once attempts run out. In @imqueue/job you cap attempts yourself (e.g. re-push the job with an incremented counter and stop re-scheduling).
Where BullMQ goes further
Be fair about this — if you need these, BullMQ is the better fit and @imqueue/job isn't trying to compete:
- A declarative retry policy —
attemptslimit + named backoff strategy + automatic dead-lettering (vs the programmable retries above). - Priorities across queued jobs.
- Repeatable / cron jobs on a recurring schedule.
- Rate limiting of processing.
- Flows — parent/child job dependencies.
- Progress reporting, events, and a dashboard ecosystem (e.g. Bull Board).
@imqueue/job gives you durable, concurrent, schedulable jobs with almost no surface area; BullMQ gives you a full job-orchestration platform.
The thing BullMQ doesn't do: typed RPC
BullMQ is a job queue, full stop. @imqueue is a whole framework on one Redis-backed core, and jobs are just one part of it. The same stack also gives you typed request/response RPC via @imqueue/rpc: call another service like a local function and await a typed result, with the client generated from the service.
So if your system needs both "do this later" (jobs) and "give me this now" (service-to-service calls), @imqueue covers both with one dependency and one mental model. With BullMQ you'd pair it with a separate RPC/HTTP layer for the synchronous half.
Quick comparison
| @imqueue/job | BullMQ | |
|---|---|---|
| Backing store | Redis | Redis |
| Guaranteed processing | ✅ on by default (re-queue on worker death) | ✅ (stalled-job recovery) |
| Concurrent workers | ✅ competing consumers | ✅ |
| Delayed / scheduled jobs | ✅ millisecond granularity | ✅ |
| Job expiration (TTL) | ✅ | ✅ (retention policies) |
| Retries + backoff | ✅ programmable (retry on error; return a delay to back off) | ✅ declarative (attempts + backoff strategy + dead-letter) |
| Priorities | ❌ | ✅ |
| Repeatable / cron | ❌ | ✅ |
| Rate limiting / flows / dashboard | ❌ | ✅ |
| Typed request/response RPC | ✅ (via @imqueue/rpc, same core) | ❌ (jobs only) |
| Dependencies | 1 (@imqueue/core) |
several |
How to choose
- Reach for BullMQ when you need the rich job lifecycle — a declarative retry/backoff policy with dead-lettering, priorities, cron/repeatable jobs, rate limiting, flows, or a ready-made dashboard.
- Reach for
@imqueue/jobwhen you want a small, dependency-light, safe-by-default job queue with delayed/scheduled jobs — especially if you're already using@imqueuefor service communication and want one stack for both jobs and RPC.
To try the @imqueue side, Getting Started covers the framework, and @imqueue/job has the job-queue API.
Building on @imqueue? The open-source packages live on GitHub and the docs at imqueue.org. Shipping inside a closed-source product? See commercial licensing & support.