Why enterprise NestJS apps decouple email from request lifecycles
Synchronous email sending inside user registration HTTP controllers leads to sluggish 800ms response times and lost messages if external APIs hiccup.
By pushing email jobs to a BullMQ Redis queue, HTTP controllers return in < 5ms while background worker processors handle dispatch, rate limits, and retries asynchronously.
NestJS Worker Processor (email.processor.ts)
Using NestJS BullMQ decorators, define the job processor with exponential backoff.
import { Processor, WorkerHost } from '@nestjs/bullmq';
import { Job } from 'bullmq';
import { Injectable, Logger } from '@nestjs/common';
@Injectable()
@Processor('email-queue')
export class EmailProcessor extends WorkerHost {
private readonly logger = new Logger(EmailProcessor.name);
async process(job: Job<{ to: string; subject: string; text: string }>): Promise<any> {
this.logger.log(`Processing email job ${job.id} to ${job.data.to}`);
const res = await fetch('https://api.sadasend.com/v1/emails', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SADASEND_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(job.data),
});
if (!res.ok) {
const err = await res.text();
this.logger.error(`Job ${job.id} failed: ${err}`);
throw new Error(`Dispatch failed: ${err}`);
}
return await res.json();
}
}BullMQ Queue Configuration with Dead Letter Queues (DLQ)
- Configure attempts: 5 with backoff: { type: "exponential", delay: 1000 }.
- Route permanently failing jobs to a dead letter queue for engineering triage.
- Enforce rate limiting: { max: 100, duration: 1000 } to stay within tier quotas.
Building AI agents that send email?
Join the SadaSend early access waitlist to get scoped API keys, recipient allowlists, and Model Context Protocol (MCP) servers upon launch.