feat: Add email service for authentication flows, including email verification and password reset.

This commit is contained in:
2025-11-29 15:38:57 +03:00
parent f07420e418
commit b4b28fc347
5 changed files with 1573 additions and 2 deletions
+128
View File
@@ -0,0 +1,128 @@
import nodemailer from 'nodemailer';
import { config } from '../config/config';
export class EmailService {
private transporter: nodemailer.Transporter;
constructor() {
this.transporter = nodemailer.createTransport({
host: config.email.host,
port: config.email.port,
secure: config.email.port === 465,
auth: {
user: config.email.user,
pass: config.email.password,
},
});
}
async sendVerificationEmail(to: string, token: string) {
const verificationLink = `${config.frontendUrl}/verify/${token}`;
const content = `
<p>Please click the button below to verify your email address:</p>
<div style="text-align: center; margin: 30px 0;">
<a href="${verificationLink}" style="background-color: #4CAF50; color: white; padding: 14px 24px; text-align: center; text-decoration: none; display: inline-block; border-radius: 4px; font-weight: bold; font-size: 16px;">Verify Email</a>
</div>
<p>Or copy and paste this link into your browser:</p>
<p style="word-break: break-all; color: #666;">${verificationLink}</p>
<p>This link will expire in 24 hours.</p>
`;
const html = this.getHtmlTemplate('Verify your email address', content);
await this.sendEmail(to, 'Verify your email', html);
}
async sendPasswordResetEmail(to: string, token: string) {
const resetLink = `${config.frontendUrl}/reset-password/${token}`;
const content = `
<p>You requested a password reset. Click the button below to reset your password:</p>
<div style="text-align: center; margin: 30px 0;">
<a href="${resetLink}" style="background-color: #008CBA; color: white; padding: 14px 24px; text-align: center; text-decoration: none; display: inline-block; border-radius: 4px; font-weight: bold; font-size: 16px;">Reset Password</a>
</div>
<p>Or copy and paste this link into your browser:</p>
<p style="word-break: break-all; color: #666;">${resetLink}</p>
<p>If you didn't request this, please ignore this email.</p>
`;
const html = this.getHtmlTemplate('Reset your password', content);
await this.sendEmail(to, 'Reset your password', html);
}
private getHtmlTemplate(title: string, content: string): string {
const logoUrl = 'https://res.cloudinary.com/dmxoohiwo/image/upload/v1764315105/logo_fpczsd.png';
const currentYear = new Date().getFullYear();
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title}</title>
</head>
<body style="margin: 0; padding: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; background-color: #f4f4f4; color: #333; line-height: 1.6;">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: #f4f4f4; padding: 20px 0;">
<tr>
<td align="center">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="600" style="background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 4px 6px rgba(0,0,0,0.05);">
<!-- Header -->
<tr>
<td align="center" style="padding: 30px 0; background-color: #ffffff; border-bottom: 1px solid #eee;">
<img src="${logoUrl}" alt="Zemenu Logo" width="150" style="display: block; border: 0;">
</td>
</tr>
<!-- Content -->
<tr>
<td style="padding: 40px 30px;">
<h1 style="margin-top: 0; margin-bottom: 20px; font-size: 24px; color: #333; text-align: center;">${title}</h1>
${content}
</td>
</tr>
<!-- Footer -->
<tr>
<td style="background-color: #f8f9fa; padding: 20px 30px; text-align: center; border-top: 1px solid #eee;">
<p style="margin: 0; font-size: 14px; color: #888;">&copy; ${currentYear} Zemenu. All rights reserved.</p>
<p style="margin: 10px 0 0; font-size: 12px; color: #aaa;">
This is an automated message, please do not reply to this email.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
`;
}
private async sendEmail(to: string, subject: string, html: string) {
if (!config.email.user || !config.email.password) {
console.warn('Email credentials not provided. Skipping email sending.');
console.log(`To: ${to}, Subject: ${subject}`);
// For development, we might want to log the link
return;
}
try {
await this.transporter.sendMail({
from: config.email.from,
to,
subject,
html,
});
console.log(`Email sent to ${to}`);
} catch (error) {
console.error('Error sending email:', error);
throw error;
}
}
}
export const emailService = new EmailService();