Monitor Node.js Background Workers & Email Queues
A crashed background worker means unsent welcome emails, forgotten password resets, and lost revenue.
The Danger of Unmonitored Queues
In modern web architecture, time-consuming tasks like sending emails, processing images, or generating PDF reports are offloaded to background queues (like Redis, BullMQ, Celery, or standard Node.js background workers).
While your primary API might have 100% uptime, the separate worker process processing these queues can crash independently.
Why Background Workers Crash:
- Memory Leaks: A poorly optimized Node.js process slowly consumes RAM over several days until the Linux kernel OOM kills it.
- Unhandled Promise Rejections: A malformed email address causes your mail provider (SendGrid/Resend) to throw an error that isn't caught, crashing the entire worker thread.
- Redis Connection Drops: The worker loses connection to the queue broker and enters a zombie state where it remains running but processes zero jobs.
The PingPug Solution: Heartbeat Polling
Don't wait for users to complain that they aren't receiving their verification emails. Use PingPug to monitor the health of your worker loops.
By having your worker send a heartbeat ping to PingPug every few minutes, you create a continuous proof-of-life. If the worker crashes, the pings stop. PingPug's dead man's switch triggers an immediate SMS alert to your phone.
Implementing PingPug in a Node.js Worker
You can simply append a lightweight fetch request to your queue processing loop or schedule it alongside your worker's health checks.
JavaScript
import { Worker } from 'bullmq';
// Start processing the email queue
const worker = new Worker('email-queue', async job => {
await sendEmail(job.data);
});
// Set up a heartbeat interval (e.g., every 5 minutes)
setInterval(async () => {
try {
// Only ping if the worker is actively running and not paused
if (!worker.isPaused() && !worker.isRunning()) {
await fetch('https://pingpug.xyz/api/ping/YOUR_UNIQUE_ID', {
signal: AbortSignal.timeout(5000)
});
}
} catch (error) {
console.error('Heartbeat failed', error);
}
}, 5 * 60 * 1000);