Skip to content
← Back to blog
NestJSBullMQBackground JobsRedis

NestJS BullMQ Background Jobs Tutorial

Firas Sayah·June 10, 2026·5 min read

Why Background Jobs Matter for SaaS

Every production SaaS application needs background processing. Whether it is sending emails, generating reports, processing uploads, or syncing third-party data, you cannot block your API responses waiting for slow operations. BullMQ paired with NestJS gives you a battle-tested solution backed by Redis.

Setting Up BullMQ in NestJS

First, install the required packages:

npm install @nestjs/bullmq bullmq

Register the BullMQ module in your app:

import { BullModule } from '@nestjs/bullmq';

@Module({
  imports: [
    BullModule.forRoot({
      connection: {
        host: 'localhost',
        port: 6379,
      },
    }),
    BullModule.registerQueue({
      name: 'email',
    }),
  ],
})
export class AppModule {}

Creating a Producer

Inject the queue into any service to add jobs:

@Injectable()
export class EmailService {
  constructor(@InjectQueue('email') private emailQueue: Queue) {}

  async sendWelcomeEmail(userId: string) {
    await this.emailQueue.add('welcome', { userId }, {
      attempts: 3,
      backoff: { type: 'exponential', delay: 2000 },
      removeOnComplete: 100,
    });
  }
}

Cloudrix SaaS Starter ships with this pre-configured and tested.

Skip weeks of boilerplate — auth, payments, multi-tenancy, and deployment included out of the box.

Try the live demo →

The attempts and backoff options mean BullMQ will automatically retry failed jobs with exponential delay. This is critical for unreliable external services like email providers.

Creating a Consumer

Processors handle jobs from the queue:

@Processor('email')
export class EmailProcessor extends WorkerHost {
  async process(job: Job<{ userId: string }>) {
    const { userId } = job.data;
    const user = await this.userService.findById(userId);
    await this.mailer.send({
      to: user.email,
      subject: 'Welcome!',
      template: 'welcome',
    });
  }
}

Job Priority and Scheduling

BullMQ supports priority queues and delayed jobs out of the box:

// High-priority job
await this.queue.add('critical-alert', data, { priority: 1 });

// Scheduled job — runs in 1 hour
await this.queue.add('reminder', data, { delay: 3600000 });

// Recurring job — runs every day at midnight
await this.queue.upsertJobScheduler('daily-report', {
  pattern: '0 0 * * *',
}, { name: 'generate-report', data: {} });

Monitoring with Bull Board

Add a dashboard to monitor your queues in real time:

import { BullBoardModule } from '@bull-board/nestjs';
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';

BullBoardModule.forFeature({ name: 'email', adapter: BullMQAdapter });

This gives you visibility into completed, failed, delayed, and active jobs without any custom tooling.

Multi-Tenant Job Isolation

In a SaaS context, you want to ensure one tenant's heavy workload does not starve others. Use named queues per job type and set concurrency limits:

@Processor('email', { concurrency: 5 })
export class EmailProcessor extends WorkerHost { ... }

You can also add tenant IDs to job data and implement fair scheduling by checking active job counts per tenant before adding new ones.

Error Handling Best Practices

Always implement a failed-job handler so issues are visible:

@OnWorkerEvent('failed')
onFailed(job: Job, error: Error) {
  this.logger.error(`Job ${job.id} failed: ${error.message}`);
  this.alertService.notify('job-failed', { jobId: job.id, error });
}

Get Started Today

All of these patterns are fully implemented in SaaS Starter. Skip 2-6 weeks of setup.

F

Firas Sayah

Senior Software Engineer

Full-stack developer with 5+ years building production SaaS applications with NestJS and Angular.