@nextmq/sdk
v1.0.0
Published
BullMQ-compatible SDK for NextMQ — a remote, managed job queue for Next.js and server-side apps.
Maintainers
Readme
@nextmq/sdk
Managed BullMQ for serverless Next.js. Schedule background jobs with auto-retry, parallel execution, rate-limiting, deduplication, and parent-child flows — without running Redis or a worker process yourself.
Website · Docs · Quickstart · Dashboard
npm install @nextmq/sdkWhy NextMQ
BullMQ is the battle-tested Redis-backed job queue that's processed billions of jobs a day. But a BullMQ Worker needs a long-lived process with a blocking Redis connection and timers that run continuously — exactly what a serverless function, frozen between requests, can't hold.
NextMQ runs that engine for you. You keep the same Queue, Worker, and FlowProducer API; we run the real BullMQ instances against a managed, isolated Redis, own the scheduler and retries, and call your processor back over a signed webhook.
- Drop-in BullMQ API — the same primitives you already know. Coming from BullMQ? There's almost nothing new to learn.
- No infrastructure — no Redis to provision, no worker dyno to keep alive, no connection pooling. One
npm installand a connection string. - Serverless-native — built for Next.js on Vercel (and any Node host). Preview deployments route automatically.
- Fully featured — auto-retry & backoff, delayed & cron jobs, per-queue concurrency, rate limiting, deduplication, and parent-child flows.
- Observable — inspect, debug, and replay every queue and job from the dashboard in real time.
How it works
You write a queue and a processor. NextMQ runs the BullMQ server and Redis, schedules the work, and calls your processor back over a signed webhook.
Your side. @nextmq/sdk gives you the BullMQ-shaped Queue, Worker, and FlowProducer classes. There's no Redis connection in your app — calling queue.add() sends an authenticated HTTP request to NextMQ. A Worker declares the processor function that should run when a job is triggered. The one piece of plumbing you add is a webhook route built with createNextMQHandler; it verifies every signed request, registers your workers, and dispatches incoming jobs.
Our side. NextMQ runs the real BullMQ Queue and Worker instances against a managed Redis. We own the scheduler, lock handling, retries, backoff, delays, priority, concurrency, and rate limiting. Your jobs and their state are stored durably and isolated to your project.
The lifecycle of a job
- Enqueue. Your route calls
queue.add(); the SDK sends it to NextMQ, which stores the job. - Schedule. Our BullMQ worker applies delay, priority, and rate limits, then picks the job up when it's due.
- Dispatch. NextMQ signs the job and calls your webhook route.
- Process. The handler verifies the signature and runs your processor function.
- Finalize. We read your response and mark the job
completed,failed(with retries/backoff), or rate-limited.
Job data, progress, logs, return values, errors, and state live in NextMQ storage. Your processor source code and server secrets stay in your app; NextMQ calls them over the signed webhook route.
Quickstart
1. Create a project
Sign up and create a project in the NextMQ dashboard. You'll get one connection string — it bundles your server URL, API key, and webhook secret.
2. Install the SDK
npm install @nextmq/sdk3. Add your environment
# .env.local
NEXTMQ_CONNECTION_STRING=nextmq://v1.... # server-only secret — never expose to the browser
NEXT_PUBLIC_APP_URL=https://your-app.com # where NextMQ calls back to run your jobsThe SDK is configured entirely by environment variables — there's no configure() call. It reads them lazily on first use, in every server runtime.
4. Define a queue and a worker
The same primitives as BullMQ. The processor is the code NextMQ calls back to run.
// jobs/images.ts
import { Queue, Worker } from '@nextmq/sdk'
export const imageQueue = new Queue<{ uploadId: string }>('images')
export const imageWorker = new Worker('images', async (job) => {
await job.updateProgress(25)
const thumb = await makeThumbnail(job.data.uploadId)
return { thumbnailUrl: thumb.url }
})5. Add the webhook route
One catch-all route handles every queue. It verifies signatures, dispatches jobs to the right worker, and exposes a /health path. It must run on the Node.js runtime.
// app/api/nextmq/[...path]/route.ts
import { createNextMQHandler } from '@nextmq/sdk/next'
import { imageWorker } from '@/jobs/images'
export const runtime = 'nodejs'
export const { GET, POST } = createNextMQHandler({
workers: [imageWorker],
})6. Enqueue a job
From a route handler or server action:
// app/api/upload/route.ts
import { imageQueue } from '@/jobs/images'
export async function POST(req: Request) {
const { uploadId } = await req.json()
const job = await imageQueue.add('thumbnail', { uploadId }, {
attempts: 5,
backoff: { type: 'exponential', delay: 1000 },
})
return Response.json({ queued: true, jobId: job.id })
}Open the dashboard and watch the images queue, or await a short job's result directly:
const job = await imageQueue.add('thumbnail', { uploadId })
const result = await job.waitUntilFinished({ timeoutMs: 15_000, pollIntervalMs: 500 })Vercel: set the same
NEXTMQ_CONNECTION_STRINGacross all environments andNEXT_PUBLIC_APP_URLto your production origin. Previews route automatically via per-job callback URLs. See the Vercel guide.
SDK reference
Every public class, method, option, error, and limit. The guides teach the common paths with real code; this section is the exhaustive source of truth.
Imports
import {
FlowProducer,
JobRemovedError,
NextMQHttpError,
NextMQRequestTimeoutError,
Queue,
RateLimitError,
UnrecoverableJobError,
Worker,
isNextMQControlFlowError,
resetNextMQConfig,
type Job,
type Processor,
type RemoteJobOptions,
type RemoteWorkerOptions,
} from '@nextmq/sdk'
import {
WorkerRegistrationError,
createNextMQHandler,
ensureWorkersRegistered,
getWorkerRegistrationStatuses,
} from '@nextmq/sdk/next'Classes at a glance
| Class | What it is | Guide |
| --- | --- | --- |
| Queue | Produce jobs and inspect/control a queue. Creating one is local; methods are HTTP calls. | Queues |
| Worker | Declares the processor that runs a queue's jobs, plus its execution options. | Workers |
| Job | A single job — read its state, mutate it, or signal an outcome from a processor. | Jobs |
| FlowProducer | Create and inspect parent/child job graphs. | Flows |
| createNextMQHandler | Builds the signed webhook route that runs your workers. | Quickstart |
QueueandFlowProducertake no Redis connection — the SDK reads its config from the environment and talks to NextMQ over HTTP. Every method below is anasyncHTTP call unless noted.
Queue
const queue = new Queue<TData, TReturnValue>(name)Produce
| Method | Description |
| --- | --- |
| add(name, data, opts?) | Enqueue one job. opts is the job-options set below. Returns the created Job. |
| addBulk(jobs) | Enqueue many jobs in one call ({ name, data, opts? }[]). Up to 1,000 per call. |
Read
| Method | Description |
| --- | --- |
| getJob(jobId) | Fetch one job by id, or undefined if it no longer exists. |
| getJobs(states, start?, end?) | Fetch jobs in the given states (paged; default 0–99, max 1,000 per request). |
| getWaiting / getActive / getCompleted / getFailed / getDelayed / getPrioritized / getWaitingChildren (start?, end?) | Paged fetch of jobs in a single state. |
| count() / getJobCounts(...states?) | Total job count, or counts per state. |
| getWaitingCount / getActiveCount / … | Count for a single state. |
| getJobLogs(jobId, start?, end?) | Read a job's log lines (paged, retained up to keepLogs). |
| getSummary() | Counts and paused state in one call — handy for a dashboard row. |
Control & maintenance
| Method | Description |
| --- | --- |
| pause() | Stop the queue from starting new jobs; in-flight jobs finish. |
| resume() | Resume a paused queue. |
| isPaused() | Whether the queue is currently paused. |
| drain(delayed?) | Remove waiting jobs (and delayed when delayed=true), keeping active ones. |
| clean(grace, limit, state) | Bulk-remove jobs in a state older than grace ms. limit caps how many per call. |
| retryJobs(opts?) | Move failed (or completed) jobs back to waiting — recover after a downstream outage. |
| promoteJobs(opts?) | Move delayed jobs to waiting now. |
| obliterate(opts?) | Delete the queue and all its data. Destructive; pass { force: true } to skip the active-jobs guard. |
Control methods can be destructive and run under your full-scope key. Keep
NEXTMQ_CONNECTION_STRINGserver-only.
Schedulers
| Method | Description |
| --- | --- |
| upsertJobScheduler(id, repeat, template?) | Create or update a scheduler by id (idempotent). repeat = the scheduler options below; template sets the produced job's name/data/opts. |
| getJobSchedulers(start?, end?) | List the queue's schedulers (paged). |
| getJobScheduler(id) | Fetch one scheduler, or undefined. |
| removeJobScheduler(id) | Delete a scheduler; returns whether one was removed. |
Queue-wide throughput — runtime, queue-level throttles you can change without redeploying.
| Method | Description |
| --- | --- |
| setGlobalConcurrency(n) | Cap how many of the queue's jobs run at once, across the board. |
| getGlobalConcurrency() | Current global concurrency, or null if unset. |
| removeGlobalConcurrency() | Clear the global concurrency cap. |
| setGlobalRateLimit(max, duration) | Cap how many jobs start per duration ms window. |
| getGlobalRateLimit() | Current global rate limit { max, duration }, or null. |
| removeGlobalRateLimit() | Clear the global rate limit. |
Deduplication
| Method | Description |
| --- | --- |
| getDeduplicationJobId(dedupId) | The job id currently holding a deduplication key, or null. |
| removeDeduplicationKey(dedupId) | Release a deduplication key early so the next add is no longer collapsed. |
Worker
const worker = new Worker<TData, TReturnValue>(queueName, processor, options?)| Member | Description |
| --- | --- |
| processor(job) | Your function. Return a value to complete the job; throw to fail it (retries apply). |
| run() | Start processing. The webhook handler calls this for you; rarely needed directly. |
| close() | Local only — stops this declaration. It does not unregister the durable server-side worker. |
| autorun | Whether the handler registers this worker by default (set { autorun: false } to opt out). |
| options | The resolved RemoteWorkerOptions (below). |
Outcome signals —
moveToCompleted,moveToFailed,moveToDelayed— and the errorsUnrecoverableJobError/RateLimitErrorare how a processor reports results.
Job
Properties
| Property | Description |
| --- | --- |
| id | Job id. Use it as the idempotency key for side effects. |
| name | Job name passed to add(). |
| data | The job payload (typed as TData). |
| opts | The applied job options, including clamped retention — what was actually stored. |
| progress | Last value reported by updateProgress(). |
| returnvalue | The processor's return value once completed. |
| failedReason | Error message of the last failed attempt. |
| stacktrace | Captured stack traces for failed attempts. |
| attemptsMade | How many attempts have run. |
| timestamp / processedOn / finishedOn | Created, first-processed, and finished epoch ms. |
| parent / parentKey | For a flow child, a reference to its parent. |
| deliveryId | Identifies one delivery attempt — finer-grained than id for per-attempt logging/dedup. |
State
| Method | Description |
| --- | --- |
| getState() | Current state: 'waiting' | 'active' | 'completed' | 'failed' | 'delayed' | … |
| refresh() | Re-fetch the latest job data from the server, mutating this instance. |
| isCompleted / isFailed / isActive / isWaiting / isDelayed / isWaitingChildren () | Convenience booleans over the last known state. |
Mutate & act
| Method | Description |
| --- | --- |
| updateProgress(value) | Report progress (a number or an object). Call from inside a processor. |
| log(line) | Append a log line, retained up to keepLogs. Returns the new line count. |
| updateData(data) | Replace the job's data payload. |
| retry(state?) | Move a failed (or completed) job back to waiting to run again. |
| promote() | Move a delayed job to waiting now. |
| changeDelay(ms) | Reschedule a delayed job. |
| changePriority({ priority?, lifo? }) | Change a waiting job's priority or LIFO placement. |
| remove({ removeChildren? }) | Delete the job; pass removeChildren for flow trees. |
Outcome signals (inside a processor)
| Method | Description |
| --- | --- |
| moveToCompleted(value) | Complete the job — same as returning value. Execution stops after the call. |
| moveToFailed(error) | Fail the job — same as throwing. UnrecoverableJobError skips remaining attempts. |
| moveToDelayed(timestamp) | Re-run the job at an absolute epoch-ms time. |
| moveToWaitingChildren() | Not supported — model graphs with FlowProducer. Throws a clear error. |
Flows & waiting
| Method | Description |
| --- | --- |
| getChildrenValues() | In a flow parent, the children's return values keyed by '<queue>:<jobId>'. |
| waitUntilFinished({ timeoutMs?, pollIntervalMs? }) | Poll until the job completes or fails. The serverless-safe replacement for QueueEvents. |
FlowProducer
| Method | Description |
| --- | --- |
| add(flow) | Create one parent/child graph. Children run first; the parent runs after they finish. |
| addBulk(flows) | Create multiple independent flows in one call (up to 100). |
| getFlow({ id, queueName, depth?, maxChildren? }) | Fetch a flow tree for inspection. |
@nextmq/sdk/next
| Export | Description |
| --- | --- |
| createNextMQHandler({ workers }) | Returns { GET, POST } for the catch-all webhook route. Verifies signatures, dispatches jobs, serves /health. Node runtime only. |
| ensureWorkersRegistered(workers, opts?) | Register workers' callback URLs up front (e.g. from instrumentation.ts). Idempotent. { includeNonAutorun: true } registers autorun:false workers too. |
| getWorkerRegistrationStatuses(workers) | Per-worker registration status — useful in a health check or smoke test. |
Job options (add)
The third argument to add(). Unsupported options are rejected, never silently dropped.
| Option | Type / bound | Description |
| --- | --- | --- |
| delay | ms ≥ 0 | Wait this long before the job becomes eligible. Cannot be combined with repeat. |
| attempts | int ≥ 0 | Total tries before the job is marked failed. |
| backoff | { type, delay } | Retry spacing: type 'fixed' or 'exponential', delay in ms. |
| priority | 0 – 2,097,152 | Lower runs first; 0 means unprioritized. |
| jobId | string | Custom id. Cannot contain ':' or be an integer string. One canonical job per business object. |
| lifo | boolean | Push to the front of the queue instead of the back. |
| keepLogs | int ≥ 0 | Max log lines to retain (clamped to the project cap). |
| sizeLimit | bytes ≥ 1 | Reject the job if its serialized data exceeds this. |
| removeOnComplete | bool \| n \| { age, count? } | Retention for completed jobs. Clamped to project caps. |
| removeOnFail | bool \| n \| { age, count? } | Retention for failed jobs. Clamped to project caps. |
| deduplication | { id, … } | Collapse duplicate work within a window — see below. |
| repeat | { pattern \| every, … } | Inline repeatable config; prefer Job Schedulers. |
Flow child options
| Option | Description |
| --- | --- |
| failParentOnFailure | If this child fails, fail the parent immediately. |
| continueParentOnFailure | Let the parent proceed even if this child fails. |
| ignoreDependencyOnFailure | Don't block the parent on this child's result if it fails. |
| removeDependencyOnFailure | Drop this child from the parent's dependency set on failure. |
Worker options
| Option | Bound / default | Description |
| --- | --- | --- |
| concurrency | 1 – 100; default 1 | Max jobs this worker runs in parallel. |
| limiter | { max ≥ 1, duration ≥ 100 } | Rate limit: at most max jobs per duration ms. |
| timeoutMs | 1,000 – 300,000; default 30,000 | How long NextMQ waits for your webhook response. Keep below your platform's function timeout. |
| autorun | default true | Whether the handler registers this worker by default. |
| lockDuration | 1,000 – 300,000 | How long a job's lock is held while processing. Advanced. |
| lockRenewTime | 500 – 300,000 | Lock renew interval; must be < lockDuration. Advanced. |
| stalledInterval | 1,000 – 300,000 | How often stalled jobs are checked. Advanced. |
| maxStalledCount | 0 – 100 | Times a job may stall before failing. Advanced. |
| drainDelay | 1 – 300,000 | Delay used while the queue drains. Advanced. |
Scheduler options
The repeat argument to upsertJobScheduler. Provide exactly one of pattern or every.
| Option | Description |
| --- | --- |
| pattern | Cron expression (e.g. '0 9 * * *'). |
| every | Fixed interval in ms (alternative to pattern). |
| limit | Stop after this many runs. |
| tz | Timezone for cron evaluation (follows DST). |
| startDate / endDate | Don't fire before / after these times. |
| immediately | Produce the first job now instead of after one interval (not with startDate). |
Deduplication options
| Option | Description |
| --- | --- |
| id | Deduplication key. A second add with the same id is collapsed. |
| ttl | Window in ms during which duplicates collapse. |
| extend | Refresh the ttl on each duplicate add. |
| replace | Replace the pending job's data with the newer add. |
| keepLastIfActive | Keep deduplicating against a job that's currently active. |
Errors & control flow
| Export | Use |
| --- | --- |
| UnrecoverableJobError | Throw in a processor to fail permanently without using remaining attempts. |
| RateLimitError | Throw with a retry-after delay to pause/requeue on a provider rate limit, without burning an attempt. |
| JobRemovedError | Thrown by waitUntilFinished when the job is gone and no result snapshot remains. |
| NextMQHttpError | A non-2xx response to an SDK request — inspect .status and body. |
| NextMQRequestTimeoutError | An SDK request exceeded NEXTMQ_REQUEST_TIMEOUT_MS. |
| WorkerRegistrationError | ensureWorkersRegistered could not register one or more workers. |
| isNextMQControlFlowError(err) | Re-throw internal moveTo* signals from a broad try/catch so they aren't swallowed. |
| resetNextMQConfig() | Clear cached env-derived config — mainly for tests that change process.env between cases. |
Delivery semantics
Webhook processing is at least once. A timed-out function can continue running while NextMQ retries, and a captured signed request can be replayed within the signature tolerance window. Use durable application storage keyed by job.id for idempotent side effects. job.deliveryId exposes the signed per-attempt identifier when finer-grained observability or deduplication is needed.
Limits
Fixed technical limits the SDK and server enforce. Plan quotas (queues, jobs, throughput) are separate — see pricing.
| Limit | Value |
| --- | --- |
| Queue name | 1–128 characters; letters, numbers, _, -, and . only. |
| jobId | Cannot contain ':' and cannot be an integer string. |
| priority | 0 to 2,097,152. |
| addBulk() | At most 1,000 jobs per call. |
| Job list, log, and scheduler pages | At most 1,000 items per request. |
| Flow depth | At most 20 levels. |
| Children per flow node | At most 1,000. |
| FlowProducer.addBulk() | At most 100 independent flows per call. |
| concurrency | 1 to 100; default 1. |
| timeoutMs | 1,000 to 300,000 ms; default 30,000 ms. |
| limiter.duration | Minimum 100 ms. |
Retention defaults — completed jobs are capped at 1,000 or 1 day; failed jobs at 5,000 or 7 days; keepLogs at 100 lines. Explicit retention values are clamped to the project caps.
Configuration
The SDK reads everything from environment variables on first use:
# One copy-paste value from the dashboard (server URL + API key + webhook secret).
# Server-only — never expose it to the browser.
NEXTMQ_CONNECTION_STRING=nextmq://v1....
NEXT_PUBLIC_APP_URL=https://your-app.com
# Manual mode (e.g. local dev against a bootstrap tenant) — instead of a
# connection string, set the transport values directly:
# NEXTMQ_SERVER_URL=http://localhost:3001
# NEXTMQ_API_KEY=nextmq_dev_key
# NEXTMQ_WEBHOOK_SECRET=dev_secret_1234567890Both the API key and the webhook secret rotate together with zero downtime: a rotation issues a new connection string while the previous credentials keep working through a grace window. Update NEXTMQ_CONNECTION_STRING and redeploy before the window closes.
Links
- Website — nextmq.com
- Documentation — nextmq.com/docs
- Quickstart — nextmq.com/docs/quickstart
- Coming from BullMQ — nextmq.com/docs/from-bullmq
- Dashboard — app.nextmq.com
- Status — status page
License
MIT
