feat(EmailService): add worker concurrency settings and improve email queue prioritization

This commit is contained in:
Dries Augustyns
2026-05-27 17:53:30 +02:00
parent 6ab4d77ca9
commit 80beb2bb99
7 changed files with 102 additions and 12 deletions
+5 -5
View File
@@ -108,7 +108,7 @@ export class EmailService {
await BillingLimitService.incrementUsage(params.projectId, EmailSourceType.TRANSACTIONAL);
// Queue email for sending
await this.queueEmail(email.id);
await this.queueEmail(email.id, EmailSourceType.TRANSACTIONAL);
return email;
}
@@ -172,7 +172,7 @@ export class EmailService {
await BillingLimitService.incrementUsage(params.projectId, sourceType);
// Queue email for sending
await this.queueEmail(email.id);
await this.queueEmail(email.id, sourceType);
return email;
}
@@ -278,7 +278,7 @@ export class EmailService {
await BillingLimitService.incrementUsage(params.projectId, sourceType);
// Queue email for sending
await this.queueEmail(email.id);
await this.queueEmail(email.id, sourceType);
return email;
}
@@ -1137,7 +1137,7 @@ export class EmailService {
* Queue an email for sending
* Adds email to the BullMQ queue for processing by workers
*/
private static async queueEmail(emailId: string, delay?: number): Promise<void> {
await QueueService.queueEmail(emailId, delay);
private static async queueEmail(emailId: string, sourceType: EmailSourceType, delay?: number): Promise<void> {
await QueueService.queueEmail(emailId, sourceType, delay);
}
}
+28 -5
View File
@@ -1,4 +1,4 @@
import {CampaignStatus, EmailStatus} from '@plunk/db';
import {CampaignStatus, EmailSourceType, EmailStatus} from '@plunk/db';
import {type Job, Queue} from 'bullmq';
import type {RedisOptions} from 'ioredis';
import signale from 'signale';
@@ -174,20 +174,43 @@ export const meterQueue = new Queue<MeterEventJobData>('meter', {
},
});
function emailPriorityFor(sourceType: EmailSourceType): number {
switch (sourceType) {
case EmailSourceType.TRANSACTIONAL:
return 1;
case EmailSourceType.WORKFLOW:
return 5;
case EmailSourceType.CAMPAIGN:
return 10;
default:
return 5;
}
}
/**
* Queue Service - Centralized queue management
*/
export class QueueService {
/**
* Add email to queue for sending
* Add email to queue for sending.
*
* Transactional emails jump the queue ahead of workflow and campaign sends
* via BullMQ's priority (lower number = higher precedence). This prevents
* latency-sensitive sends (login codes, password resets) from queuing behind
* large campaign bursts on the shared `email` queue.
*/
public static async queueEmail(emailId: string, delay?: number): Promise<Job<SendEmailJobData>> {
public static async queueEmail(
emailId: string,
sourceType: EmailSourceType,
delay?: number,
): Promise<Job<SendEmailJobData>> {
return emailQueue.add(
'send-email',
{emailId},
{
delay, // Optional delay in milliseconds
jobId: `email-${emailId}`, // Prevent duplicate jobs
delay,
jobId: `email-${emailId}`,
priority: emailPriorityFor(sourceType),
},
);
}