Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .husky/commit-msg
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
#
# See commitlint.config.js for the full rule set.

. "$(dirname -- "$0")/_/husky.sh"
#. "$(dirname -- "$0")/_/husky.sh"

echo "📝 Validating commit message..."

Expand Down
16 changes: 15 additions & 1 deletion src/notifications/email/email.processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,21 @@ export class EmailProcessor {
this.logger.log(`Email job ${job.id} completed successfully`);
} catch (error) {
this.logger.error(`Email job ${job.id} failed: ${error.message}`, error.stack);
throw error;

// Check if job should be retried using built-in Bull retry settings
const maxAttempts = job.opts.attempts || 3;

if (job.attemptsMade >= maxAttempts) {
// Send to dead letter queue
this.logger.error(
`Job ${job.id} moved to dead letter queue after ${job.attemptsMade} attempts. Final error: ${error.message}`,
);
// In production, you would push to a dead letter queue here:
// await this.deadLetterQueue.add('failed-email', { ...job.data, error: error.message });
} else {
// Re-throw to let Bull handle the retry with exponential backoff
throw error;
}
}
}
}
25 changes: 18 additions & 7 deletions src/notifications/email/email.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export class EmailService {

async sendVerificationEmail(email: string, token: string): Promise<void> {
const appUrl = this.configService.get<string>('APP_URL') || 'http://localhost:3000';
const verificationUrl = `${appUrl}/auth/verify-email?token=${token}`;

await this.emailQueue.add(
'send-email',
Expand All @@ -50,12 +49,12 @@ export class EmailService {
subject: 'Verify Your Email - TeachLink',
template: 'verification',
context: {
verificationUrl,
verificationUrl: `${appUrl}/auth/verify-email?token=${token}`,
appUrl,
},
},
{
attempts: 3,
attempts: 5,
backoff: {
type: 'exponential',
delay: 2000,
Expand All @@ -68,7 +67,6 @@ export class EmailService {

async sendPasswordResetEmail(email: string, token: string): Promise<void> {
const appUrl = this.configService.get<string>('APP_URL') || 'http://localhost:3000';
const resetUrl = `${appUrl}/auth/reset-password?token=${token}`;

await this.emailQueue.add(
'send-email',
Expand All @@ -77,12 +75,12 @@ export class EmailService {
subject: 'Reset Your Password - TeachLink',
template: 'reset-password',
context: {
resetUrl,
resetUrl: `${appUrl}/auth/reset-password?token=${token}`,
appUrl,
},
},
{
attempts: 3,
attempts: 5,
backoff: {
type: 'exponential',
delay: 2000,
Expand All @@ -109,7 +107,20 @@ export class EmailService {
this.logger.log(`Email sent successfully to ${options.to}`);
} catch (error) {
this.logger.error(`Failed to send email to ${options.to}`, error.stack);
throw error;

// Check if we should retry or send to dead letter queue
const maxAttempts = 5;
const attemptNumber = 1;

if (attemptNumber >= maxAttempts) {
this.logger.error(
`Email to ${options.to} moved to dead letter queue. Error: ${error.message}`,
);
// Here you would push to a dead letter queue:
// await this.deadLetterQueue.add('failed-email', { ...options, error: error.message });
} else {
throw error;
}
}
}

Expand Down
Loading