Skip to content

Commit

Permalink
Merge pull request #1245 from timiwritescode/fix/email-service
Browse files Browse the repository at this point in the history
[FIX] Email Service: Missing Contexts & Incorrect Template Names
  • Loading branch information
incredible-phoenix246 authored Feb 28, 2025
2 parents 4611b6d + a2e3304 commit 43a7d74
Show file tree
Hide file tree
Showing 7 changed files with 383 additions and 35 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -405,3 +405,8 @@ dist
# User specific ignores
todo.txt
.vscode/


docker-compose.yml
data/
.dev.env
15 changes: 14 additions & 1 deletion src/modules/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ export default class AuthenticationService {
oranisations: userOranisations,
};

// send welcome mail
await this.emailService.sendUserConfirmationMail(
user.email,
user.first_name,
`${process.env.FRONTEND_URL}/confirm-email`,
token
);

return {
message: SYS_MSG.USER_CREATED_SUCCESSFULLY,
access_token,
Expand All @@ -98,7 +106,12 @@ export default class AuthenticationService {
}

const token = (await this.otpService.createOtp(user.id)).token;
await this.emailService.sendForgotPasswordMail(user.email, `${process.env.FRONTEND_URL}/reset-password`, token);
await this.emailService.sendForgotPasswordMail(
user.email,
user.first_name,
`${process.env.FRONTEND_URL}/reset-password`,
token
);

return {
message: SYS_MSG.EMAIL_SENT,
Expand Down
1 change: 1 addition & 0 deletions src/modules/auth/tests/auth.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ describe('AuthenticationService', () => {
useValue: {
sendForgotPasswordMail: jest.fn(),
sendUserEmailConfirmationOtp: jest.fn(),
sendUserConfirmationMail: jest.fn(),
sendEmail: jest.fn(),
},
},
Expand Down
179 changes: 179 additions & 0 deletions src/modules/email/email.consumer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { Test, TestingModule } from '@nestjs/testing';
import { MailerService } from '@nestjs-modules/mailer';
import { Logger } from '@nestjs/common';
import { Job } from 'bull';
import EmailQueueConsumer from './email.consumer';

describe('EmailQueueConsumer', () => {
let emailQueueConsumer: EmailQueueConsumer;
let mailerService: MailerService;
let logger: Logger;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
EmailQueueConsumer,
{
provide: MailerService,
useValue: {
sendMail: jest.fn().mockResolvedValue('Email sent'),
},
},
{
provide: Logger,
useValue: {
log: jest.fn(),
error: jest.fn(),
},
},
],
}).compile();

emailQueueConsumer = module.get<EmailQueueConsumer>(EmailQueueConsumer);
mailerService = module.get<MailerService>(MailerService);
logger = module.get<Logger>(Logger);
});

it('should be defined', () => {
expect(emailQueueConsumer).toBeDefined();
});

const mockJob = (data: any): Job<any> =>
({
data,
}) as Job<any>;

it('should send a welcome email and log success', async () => {
const job = mockJob({ mail: { to: '[email protected]' } });
const loggerSpy = jest.spyOn(emailQueueConsumer['logger'], 'log');

await emailQueueConsumer.sendWelcomeEmailJob(job);

expect(mailerService.sendMail).toHaveBeenCalledWith({
to: '[email protected]',
subject: 'Welcome to My App! Confirm your Email',
template: 'Welcome-Template',
});

expect(loggerSpy).toHaveBeenCalledWith('Welcome email sent successfully to [email protected]');
});

it('should send a waitlist email and log success', async () => {
const job = mockJob({ mail: { to: '[email protected]' } });
const loggerSpy = jest.spyOn(emailQueueConsumer['logger'], 'log');

await emailQueueConsumer.sendWaitlistEmailJob(job);

expect(mailerService.sendMail).toHaveBeenCalledWith({
to: '[email protected]',
subject: 'Waitlist Confirmation',
template: 'waitlist',
});

expect(loggerSpy).toHaveBeenCalledWith('Waitlist email sent successfully to [email protected]');
});

it('should handle errors in sendWelcomeEmailJob', async () => {
jest.spyOn(mailerService, 'sendMail').mockRejectedValue(new Error('Failed to send email'));
const loggerSpy = jest.spyOn(emailQueueConsumer['logger'], 'error');

const job = mockJob({ mail: { to: '[email protected]' } });

await emailQueueConsumer.sendWelcomeEmailJob(job);

expect(loggerSpy).toHaveBeenCalledWith(expect.stringContaining('EmailQueueConsumer ~ sendWelcomeEmailJobError:'));
});

it('should send a reset password email and log success', async () => {
const payload = {
mail: {
to: '[email protected]',
context: {
name: 'name',
link: 'link',
email: 'email',
},
},
};

const job = mockJob({ ...payload });
const loggerSpy = jest.spyOn(emailQueueConsumer['logger'], 'log');

await emailQueueConsumer.sendResetPasswordEmailJob(job);

expect(mailerService.sendMail).toHaveBeenCalledWith(
expect.objectContaining({
to: '[email protected]',
subject: 'Reset Password',
template: 'Reset-Password-Template',
context: expect.objectContaining({
name: 'name',
link: 'link',
email: 'email',
}),
})
);

expect(loggerSpy).toHaveBeenCalledWith('Reset password email sent successfully to [email protected]');
});

it('should send a newsletter email and log success', async () => {
const job = mockJob({ mail: { to: '[email protected]' } });
const loggerSpy = jest.spyOn(emailQueueConsumer['logger'], 'log');

await emailQueueConsumer.sendNewsletterEmailJob(job);

expect(mailerService.sendMail).toHaveBeenCalledWith({
to: '[email protected]',
subject: 'Monthly Newsletter',
template: 'newsletter',
});

expect(loggerSpy).toHaveBeenCalledWith('Newsletter email sent successfully to [email protected]');
});

it('should send a register OTP email and log success', async () => {
const job = mockJob({ mail: { to: '[email protected]' } });
const loggerSpy = jest.spyOn(emailQueueConsumer['logger'], 'log');

await emailQueueConsumer.sendTokenEmailJob(job);

expect(mailerService.sendMail).toHaveBeenCalledWith({
to: '[email protected]',
subject: 'Welcome to My App! Confirm your Email',
template: 'register-otp',
});

expect(loggerSpy).toHaveBeenCalledWith('Register OTP email sent successfully to [email protected]');
});

it('should send a login OTP email and log success', async () => {
const job = mockJob({ mail: { to: '[email protected]' } });
const loggerSpy = jest.spyOn(emailQueueConsumer['logger'], 'log');

await emailQueueConsumer.sendLoginOtpEmailJob(job);

expect(mailerService.sendMail).toHaveBeenCalledWith({
to: '[email protected]',
subject: 'Login with OTP',
template: 'login-otp',
});

expect(loggerSpy).toHaveBeenCalledWith('Login OTP email sent successfully to [email protected]');
});

it('should send an in-app notification email and log success', async () => {
const job = mockJob({ mail: { to: '[email protected]' } });
const loggerSpy = jest.spyOn(emailQueueConsumer['logger'], 'log');

await emailQueueConsumer.sendNotificationMail(job);

expect(mailerService.sendMail).toHaveBeenCalledWith({
to: '[email protected]',
subject: 'In-App, Notification',
template: 'login-otp',
});

expect(loggerSpy).toHaveBeenCalledWith('Notification email sent successfully to [email protected]');
});
});
15 changes: 12 additions & 3 deletions src/modules/email/email.consumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ export default class EmailQueueConsumer {
await this.mailerService.sendMail({
...mail,
subject: 'Welcome to My App! Confirm your Email',
template: 'welcome',
template: 'Welcome-Template',
});
this.logger.log(`Welcome email sent successfully to ${mail.to}`);
} catch (sendWelcomeEmailJobError) {
this.logger.error(`EmailQueueConsumer ~ sendWelcomeEmailJobError: ${sendWelcomeEmailJobError}`);
}
Expand All @@ -37,6 +38,7 @@ export default class EmailQueueConsumer {
subject: 'Waitlist Confirmation',
template: 'waitlist',
});
this.logger.log(`Waitlist email sent successfully to ${mail.to}`);
} catch (sendWaitlistEmailJobError) {
this.logger.error(`EmailQueueConsumer ~ sendWaitlistEmailJobError: ${sendWaitlistEmailJobError}`);
}
Expand All @@ -48,11 +50,13 @@ export default class EmailQueueConsumer {
const {
data: { mail },
} = job;

await this.mailerService.sendMail({
...mail,
subject: 'Reset Password',
template: 'reset-password',
template: 'Reset-Password-Template',
});
this.logger.log(`Reset password email sent successfully to ${mail.to}`);
} catch (sendResetPasswordEmailJobError) {
this.logger.error(`EmailQueueConsumer ~ sendResetPasswordEmailJobError: ${sendResetPasswordEmailJobError}`);
}
Expand All @@ -69,6 +73,7 @@ export default class EmailQueueConsumer {
subject: 'Monthly Newsletter',
template: 'newsletter',
});
this.logger.log(`Newsletter email sent successfully to ${mail.to}`);
} catch (sendNewsletterEmailJobError) {
this.logger.error(`EmailQueueConsumer ~ sendNewsletterEmailJobError: ${sendNewsletterEmailJobError}`);
}
Expand All @@ -85,6 +90,7 @@ export default class EmailQueueConsumer {
subject: 'Welcome to My App! Confirm your Email',
template: 'register-otp',
});
this.logger.log(`Register OTP email sent successfully to ${mail.to}`);
} catch (sendTokenEmailJobError) {
this.logger.error(`EmailQueueConsumer ~ sendTokenEmailJobError: ${sendTokenEmailJobError}`);
}
Expand All @@ -101,6 +107,7 @@ export default class EmailQueueConsumer {
subject: 'Login with OTP',
template: 'login-otp',
});
this.logger.log(`Login OTP email sent successfully to ${mail.to}`);
} catch (sendLoginOtpEmailJobError) {
this.logger.error(`EmailQueueConsumer ~ sendLoginOtpEmailJobError: ${sendLoginOtpEmailJobError}`);
}
Expand All @@ -112,11 +119,13 @@ export default class EmailQueueConsumer {
const {
data: { mail },
} = job;

await this.mailerService.sendMail({
...mail,
subject: 'In-App, Notification',
template: 'notification',
template: 'login-otp',
});
this.logger.log(`Notification email sent successfully to ${mail.to}`);
} catch (sendLoginOtpEmailJobError) {
this.logger.error(`EmailQueueConsumer ~ sendLoginOtpEmailJobError: ${sendLoginOtpEmailJobError}`);
}
Expand Down
8 changes: 5 additions & 3 deletions src/modules/email/email.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ import { getFile, createFile, deleteFile } from '@shared/helpers/fileHelpers';
export class EmailService {
constructor(private readonly mailerService: QueueService) {}

async sendUserConfirmationMail(email: string, url: string, token: string) {
async sendUserConfirmationMail(email: string, name: string, url: string, token: string) {
const link = `${url}?token=${token}`;
const mailPayload: MailInterface = {
to: email,
context: {
link,
name,
email,
},
};
Expand All @@ -42,11 +43,12 @@ export class EmailService {
await this.mailerService.sendMail({ variant: 'register-otp', mail: mailPayload });
}

async sendForgotPasswordMail(email: string, url: string, token: string) {
async sendForgotPasswordMail(email: string, name: string, url: string, token: string) {
const link = `${url}?token=${token}`;
const mailPayload: MailInterface = {
to: email,
context: {
name,
link,
email,
},
Expand Down Expand Up @@ -84,7 +86,7 @@ export class EmailService {
to: email,
context: {
email,
token,
otp: token,
},
};

Expand Down
Loading

0 comments on commit 43a7d74

Please sign in to comment.