Queue Worker Skill
BullMQ worker with retries and DLQ checklist - an Agent Skill for async job processing on Node.js 24 with Redis.
Search across all documentation pages
BullMQ worker with retries and DLQ checklist - an Agent Skill for async job processing on Node.js 24 with Redis.
Produces a worker bootstrap plan: queue definition, worker process entry, payload schema, retry/DLQ policy, observability hooks, and deployment split from the HTTP API.
| Input | Why |
|---|---|
| Queue name | email, webhooks-outbound |
| Job payload shape | Zod schema fields |
| Concurrency target | Worker pod CPU and upstream rate limits |
| Redis URL | Shared cluster or dedicated queue Redis |
| Idempotency needs | Payment-like jobs need dedup keys |
src/queues/<name>.queue.ts - Queue producersrc/workers/<name>.worker.ts - Worker consumersrc/schemas/<name>.job.ts - Zod payload schemapackage.json script: "worker:<name>": "node dist/workers/<name>.worker.js"new Worker() inside HTTP server.ts.maxRetriesPerRequest: null on ioredis connections used by BullMQ workers.parse throws → BullMQ retry; log poison pills.removeOnComplete: true - keep last N for debugging.await worker.close() before exit for in-flight jobs.jobId, attemptsMade, not full PII payload.Quick-reference recipe card - copy-paste ready.
# Verification after skill output
export REDIS_URL=redis://localhost:6379
npm run build
npm run worker:email &
WORKER_PID=$!
node -e "
import { emailQueue } from './dist/queues/email.queue.js';
const job = await emailQueue.add('send', { to: 'test@example.com', templateId: 'welcome' });
console.log('enqueued', job.id);
"
sleep 5
redis-cli LLEN bull:email:failed # expect 0 on success
kill -TERM $WORKER_PID// src/schemas/email.job.ts
import { z } from "zod";
export const emailJobSchema = z.object({
to: z.string().email(),
templateId: z.string(),
vars: z.record(z.string()).default({}),
});
export type EmailJob = z.infer<typeof emailJobSchema>;// src/workers/email.worker.ts
import { Worker } from "bullmq";
import { createRedis } from "../redis.js";
import { emailJobSchema } from "../schemas/email.job.js";
import { sendEmail } from "../services/email.js";
const worker = new Worker(
"email",
async (job) => {
const payload = emailJobSchema.parse(job.data);
await sendEmail(payload);
},
{
connection: createRedis(),
concurrency: 5,
}
);
async function shutdown() {
await worker.close();
process.exit(0);
}
process.on("SIGTERM", shutdown);
process.on("SIGINT", shutdown);attempts set per job type (3-5 typical)backoff: { type: "exponential", delay: 2000 }email-dlq queue OR retained in failed setfailed count threshold in Redis/Bull BoardUse Queue Worker Skill:
- Queue: webhooks-outbound
- Payload: { url, secret, eventType, body }
- Concurrency: 20
- Idempotency: eventId unique in Postgres
- Service: notifications-api (Fastify 5)Same repo is fine with separate npm run worker:* and K8s Deployment. Split packages when worker deps (PDF libs) bloat API image size.
Target under 30s. Longer work should checkpoint or split into chained jobs. BullMQ stalled job detection punishes blocking CPU.
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 16, 2026