Skip to content
Open
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
56 changes: 56 additions & 0 deletions backend/src/services/__tests__/mailerService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import logger from '../../utils/logger.js';
import { MailerService } from '../notifications/mailerService.js';

const smtpKeys = ['SMTP_HOST', 'SMTP_USER', 'SMTP_PASS', 'SMTP_PORT', 'SMTP_SECURE', 'SMTP_FROM'] as const;

describe('MailerService', () => {
const originalEnv = { ...process.env };

beforeEach(() => {
jest.restoreAllMocks();
for (const key of smtpKeys) delete process.env[key];
});

afterAll(() => {
process.env = originalEnv;
});

it('logs a warning when SMTP is not configured', async () => {
const warn = jest.spyOn(logger, 'warn').mockImplementation(() => undefined);

await MailerService.sendMail({
to: ['recipient@example.com'],
subject: 'password-reset',
text: 'Reset requested',
});

expect(warn).toHaveBeenCalledWith(
'Email notification skipped: SMTP is not configured',
expect.objectContaining({
recipients: ['recipient@example.com'],
mailType: 'password-reset',
}),
);
});

it('logs an error when nodemailer cannot be loaded', async () => {
process.env.SMTP_HOST = 'smtp.example.com';
process.env.SMTP_USER = 'mailer@example.com';
process.env.SMTP_PASS = 'secret';
const error = jest.spyOn(logger, 'error').mockImplementation(() => undefined);

await MailerService.sendMail({
to: ['recipient@example.com'],
subject: 'payroll-notification',
text: 'Payroll ready',
});

expect(error).toHaveBeenCalledWith(
'Email notification skipped: nodemailer could not be loaded',
expect.objectContaining({
recipients: ['recipient@example.com'],
mailType: 'payroll-notification',
}),
);
});
});
38 changes: 30 additions & 8 deletions backend/src/services/notifications/mailerService.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import logger from '../../utils/logger.js';

export interface SendMailInput {
to: string[];
subject: string;
Expand All @@ -10,12 +12,23 @@ export class MailerService {
}

static async sendMail(input: SendMailInput): Promise<void> {
if (!this.isConfigured()) return;
if (!this.isConfigured()) {
logger.warn('Email notification skipped: SMTP is not configured', {
recipients: input.to,
mailType: input.subject,
});
return;
}

let nodemailer: any;
try {
nodemailer = (await import('nodemailer')).default;
} catch {
} catch (error) {
logger.error('Email notification skipped: nodemailer could not be loaded', {
error: error instanceof Error ? error.message : String(error),
recipients: input.to,
mailType: input.subject,
});
return;
}

Expand All @@ -31,11 +44,20 @@ export class MailerService {

const from = process.env.SMTP_FROM || process.env.SMTP_USER;

await transporter.sendMail({
from,
to: input.to.join(','),
subject: input.subject,
text: input.text,
});
try {
await transporter.sendMail({
from,
to: input.to.join(','),
subject: input.subject,
text: input.text,
});
} catch (error) {
logger.error('Email notification failed', {
error: error instanceof Error ? error.message : String(error),
recipients: input.to,
mailType: input.subject,
});
throw error;
}
}
}