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.
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 }
})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 behavior | Server result |
|---|---|
| Return a value | Complete the job with that return value. |
| Throw an Error | Fail this attempt; attempts and backoff decide whether it retries. |
UnrecoverableJobError | Fail the job immediately without using remaining attempts. |
RateLimitError | Rate-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.
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.
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#
- SDK reference for technical bounds and the full surface.
- Workers & processing for worker options.