NextMQ

Reliability & delivery guarantees

NextMQ runs real BullMQ workers, but your processor runs over a signed webhook. This page is the production contract for that boundary.

Delivery is at-least-once#

A job may be delivered more than once. The common causes are webhook timeouts, platform function timeouts, network failures, retries, and replay of a recently signed request. Treat processors as idempotent units of work.

jobs/payouts.ts
export const payoutWorker = new Worker('payouts', async (job) => {
  if (await payouts.hasProcessed(job.id)) {
    return { skipped: true }
  }

  const transfer = await bank.transfer(job.data)
  await payouts.recordProcessed(job.id, transfer.id)
  return { transferId: transfer.id }
})
Tip
Use job.id for business idempotency. Use job.deliveryId when you need per-attempt logs or deduplication.

Retries and outcomes#

NextMQ maps the result of your processor to BullMQ job state. Attempts and backoff apply the same way they do for a BullMQ worker, but the signal crosses the webhook response.

Processor behaviorServer result
Return a valueComplete the job with that return value.
Throw an ErrorFail this attempt; attempts and backoff decide whether it retries.
UnrecoverableJobErrorFail the job immediately without using remaining attempts.
RateLimitErrorRate-limit the queue and re-queue the job without burning an attempt.
job.moveToDelayed(timestamp)Move the job back to delayed until the absolute timestamp.

Retry example

await emailQueue.add('welcome', { userId }, {
  attempts: 5,
  backoff: { type: 'exponential', delay: 1000 },
})

Timeouts can overlap#

timeoutMs is how long NextMQ waits for your webhook response. Your hosting platform has its own function timeout. Keep timeoutMs below the platform timeout, and still expect overlap: a timed-out invocation may continue while BullMQ retries the job.

Heads up
Timeout safety comes from idempotency, not from assuming there is only one running invocation.

Stalled jobs#

NextMQ owns BullMQ locks and stall recovery on the server. If a delivery times out, the worker process dies, or the server loses the active lock, BullMQ can retry the job according to its stalled-job rules. The worker options lockDuration, lockRenewTime,stalledInterval, and maxStalledCount exist for advanced tuning, but most apps should leave them at the defaults and focus on idempotency.

Signatures prove recency, not uniqueness#

The webhook handler verifies the request signature and timestamp before running your processor. That proves the request was signed by NextMQ recently. It does not make the request one-time; a captured request can be replayed inside the signature tolerance window.

Waiting for completion#

waitUntilFinished() polls the job endpoint until the job reaches completed or failed. Native Redis pub/sub QueueEvents is not available in the serverless model.

Note
If a job is removed immediately with removeOnComplete or removeOnFail, NextMQ keeps a short terminal-result snapshot, about 60 seconds. After that, polling raises JobRemovedError.

Backpressure and 503s#

When Redis storage is near capacity, job-creating routes can return 503. Reads, callbacks, cleanup, and worker registration keep working so queues can recover. Treat enqueue 503s as retryable with backoff.

Where to go next#