smtp integration and password recovery.
This commit is contained in:
99
map/app/services/email.js
Normal file
99
map/app/services/email.js
Normal file
@@ -0,0 +1,99 @@
|
||||
const nodemailer = require('nodemailer');
|
||||
const logger = require('../utils/logger');
|
||||
const emailTemplates = require('./emailTemplates');
|
||||
const config = require('../config');
|
||||
|
||||
// Create reusable transporter
|
||||
let transporter;
|
||||
|
||||
const initializeEmailService = () => {
|
||||
const emailConfig = {
|
||||
host: process.env.SMTP_HOST,
|
||||
port: parseInt(process.env.SMTP_PORT),
|
||||
secure: process.env.SMTP_SECURE === 'true', // true for 465, false for other ports
|
||||
auth: {
|
||||
user: process.env.SMTP_USER,
|
||||
pass: process.env.SMTP_PASS
|
||||
}
|
||||
};
|
||||
|
||||
// Additional settings for specific providers
|
||||
if (process.env.SMTP_SERVICE) {
|
||||
emailConfig.service = process.env.SMTP_SERVICE; // 'gmail' for example
|
||||
}
|
||||
|
||||
// Handle TLS for port 587
|
||||
if (emailConfig.port === 587) {
|
||||
emailConfig.secure = false;
|
||||
emailConfig.requireTLS = true;
|
||||
}
|
||||
|
||||
transporter = nodemailer.createTransport(emailConfig);
|
||||
|
||||
// Verify connection
|
||||
transporter.verify((error, success) => {
|
||||
if (error) {
|
||||
logger.error('SMTP connection failed:', error);
|
||||
} else {
|
||||
logger.info('SMTP server ready to send emails');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const sendEmail = async ({ to, subject, text, html }) => {
|
||||
if (!transporter) {
|
||||
throw new Error('Email service not initialized');
|
||||
}
|
||||
|
||||
const mailOptions = {
|
||||
from: `${process.env.EMAIL_FROM_NAME} <${process.env.EMAIL_FROM_ADDRESS}>`,
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
html
|
||||
};
|
||||
|
||||
try {
|
||||
const info = await transporter.sendMail(mailOptions);
|
||||
logger.info(`Email sent: ${info.messageId}`);
|
||||
return info;
|
||||
} catch (error) {
|
||||
logger.error('Failed to send email:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const sendPasswordRecovery = async (user) => {
|
||||
try {
|
||||
const baseUrl = config.isProduction ?
|
||||
`https://map.${config.domain}` :
|
||||
`http://localhost:${config.port}`;
|
||||
|
||||
const variables = {
|
||||
APP_NAME: process.env.APP_NAME || 'CMlite Map',
|
||||
USER_NAME: user.Name || user.name || user.Email || user.email,
|
||||
USER_EMAIL: user.Email || user.email,
|
||||
PASSWORD: user.Password || user.password,
|
||||
LOGIN_URL: `${baseUrl}/login.html`,
|
||||
TIMESTAMP: new Date().toLocaleString()
|
||||
};
|
||||
|
||||
const { html, text } = await emailTemplates.render('password-recovery', variables);
|
||||
|
||||
return await sendEmail({
|
||||
to: user.Email || user.email,
|
||||
subject: `Password Recovery - ${variables.APP_NAME}`,
|
||||
text,
|
||||
html
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Failed to send password recovery email:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
initializeEmailService,
|
||||
sendEmail,
|
||||
sendPasswordRecovery
|
||||
};
|
||||
69
map/app/services/emailTemplates.js
Normal file
69
map/app/services/emailTemplates.js
Normal file
@@ -0,0 +1,69 @@
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
class EmailTemplateService {
|
||||
constructor() {
|
||||
this.templatesDir = path.join(__dirname, '../templates/email');
|
||||
this.cache = new Map();
|
||||
}
|
||||
|
||||
async loadTemplate(templateName, type = 'html') {
|
||||
const cacheKey = `${templateName}.${type}`;
|
||||
|
||||
// Check cache first
|
||||
if (this.cache.has(cacheKey)) {
|
||||
return this.cache.get(cacheKey);
|
||||
}
|
||||
|
||||
try {
|
||||
const templatePath = path.join(this.templatesDir, `${templateName}.${type}`);
|
||||
const template = await fs.readFile(templatePath, 'utf-8');
|
||||
|
||||
// Cache the template
|
||||
this.cache.set(cacheKey, template);
|
||||
|
||||
return template;
|
||||
} catch (error) {
|
||||
logger.error(`Failed to load email template ${templateName}.${type}:`, error);
|
||||
throw new Error(`Email template not found: ${templateName}.${type}`);
|
||||
}
|
||||
}
|
||||
|
||||
renderTemplate(template, variables) {
|
||||
let rendered = template;
|
||||
|
||||
// Replace all {{VARIABLE}} with actual values
|
||||
Object.entries(variables).forEach(([key, value]) => {
|
||||
const regex = new RegExp(`{{${key}}}`, 'g');
|
||||
rendered = rendered.replace(regex, value || '');
|
||||
});
|
||||
|
||||
return rendered;
|
||||
}
|
||||
|
||||
async render(templateName, variables) {
|
||||
try {
|
||||
// Load both HTML and text versions
|
||||
const [htmlTemplate, textTemplate] = await Promise.all([
|
||||
this.loadTemplate(templateName, 'html'),
|
||||
this.loadTemplate(templateName, 'txt')
|
||||
]);
|
||||
|
||||
return {
|
||||
html: this.renderTemplate(htmlTemplate, variables),
|
||||
text: this.renderTemplate(textTemplate, variables)
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Failed to render email template:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear template cache (useful for development)
|
||||
clearCache() {
|
||||
this.cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new EmailTemplateService();
|
||||
Reference in New Issue
Block a user