typescript 50 lines · 7 steps

Processing background email jobs in NestJS

A BullMQ worker consumes queued email jobs, dispatches them by name, and reports progress and failures.

Explained by highlit
1import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq';
2import { Logger } from '@nestjs/common';
3import { Job } from 'bullmq';
4import { MailerService } from '../mailer/mailer.service';
5import { UsersService } from '../users/users.service';
6 
7interface WelcomeEmailData {
8 userId: string;
9 locale: string;
10}
11 
12@Processor('emails', { concurrency: 5 })
13export class EmailsProcessor extends WorkerHost {
14 private readonly logger = new Logger(EmailsProcessor.name);
15 
16 constructor(
17 private readonly mailer: MailerService,
18 private readonly users: UsersService,
19 ) {
20 super();
21 }
22 
23 async process(job: Job): Promise<void> {
24 switch (job.name) {
25 case 'welcome':
26 return this.handleWelcome(job as Job<WelcomeEmailData>);
27 default:
28 throw new Error(`Unhandled email job: ${job.name}`);
29 }
30 }
31 
32 private async handleWelcome(job: Job<WelcomeEmailData>): Promise<void> {
33 const { userId, locale } = job.data;
34 const user = await this.users.findByIdOrFail(userId);
35 
36 await job.updateProgress(50);
37 await this.mailer.send({
38 to: user.email,
39 template: 'welcome',
40 locale,
41 context: { name: user.firstName },
42 });
43 await job.updateProgress(100);
44 }
45 
46 @OnWorkerEvent('failed')
47 onFailed(job: Job, err: Error): void {
48 this.logger.error(`Job ${job.id} (${job.name}) failed: ${err.message}`, err.stack);
49 }
50}
01 / 01
STEP 01

Walkthrough

Space play ←→ step click any line
Three takeaways
  1. 1A single processor can route many job types by branching on the job's name.
  2. 2Reporting progress with updateProgress lets producers and dashboards track long-running work.
  3. 3Worker event hooks like failed centralize error logging across every job the queue handles.

Related explainers

typescript
import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';
import localeFrExtra from '@angular/common/locales/extra/fr';
import localeDe from '@angular/common/locales/de';

Locale-aware bootstrapping in Angular

i18n localization dependency-injection
Intermediate 8 steps
typescript
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import * as Joi from 'joi';
 

Validating env config at boot in NestJS

configuration schema-validation environment-variables
Intermediate 8 steps
java
@Component
@Converter
public class EncryptedStringConverter implements AttributeConverter<String, String> {
 

Transparent column encryption in Spring & JPA

encryption aes-gcm jpa-converter
Advanced 10 steps
typescript
import { Inject, Injectable, Logger } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { InjectRepository } from '@nestjs/typeorm';

A cache-aside country lookup in NestJS

cache-aside dependency-injection batch-lookup
Intermediate 8 steps
java
package com.acme.billing.config;
 
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;

Feature-flagged beans with Spring @ConditionalOnProperty

feature-flags conditional-beans strategy-pattern
Intermediate 5 steps
typescript
import { Injectable, effect, signal, computed } from '@angular/core';
 
interface Preferences {
  theme: 'light' | 'dark';

A signal-based preferences store in Angular

signals state-management persistence
Intermediate 7 steps

Share this explainer

Here's the card — post it anywhere.

Processing background email jobs in NestJS — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code