353 lines
10 KiB
JavaScript

const nodemailer = require('nodemailer');
const emailTemplates = require('./emailTemplates');
class EmailService {
constructor() {
this.transporter = null;
this.initializeTransporter();
}
initializeTransporter() {
try {
const transporterConfig = {
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT) || 587,
secure: process.env.SMTP_PORT === '465', // true for 465, false for other ports
tls: {
rejectUnauthorized: false
}
};
// Add auth if credentials are provided
if (process.env.SMTP_USER && process.env.SMTP_PASS) {
transporterConfig.auth = {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
};
}
this.transporter = nodemailer.createTransporter(transporterConfig);
console.log('Email transporter initialized successfully');
} catch (error) {
console.error('Failed to initialize email transporter:', error);
}
}
initializeTransporter() {
try {
this.transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT) || 587,
secure: process.env.SMTP_PORT === '465', // true for 465, false for other ports
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
},
tls: {
rejectUnauthorized: false
}
});
console.log('Email transporter initialized successfully');
} catch (error) {
console.error('Failed to initialize email transporter:', error);
}
}
async testConnection() {
try {
if (!this.transporter) {
throw new Error('Email transporter not initialized');
}
await this.transporter.verify();
return {
success: true,
message: 'SMTP connection verified successfully'
};
} catch (error) {
return {
success: false,
message: 'SMTP connection failed',
error: error.message
};
}
}
async sendEmail(emailOptions, isTest = false) {
try {
if (!this.transporter) {
throw new Error('Email transporter not initialized');
}
let to = emailOptions.to;
let subject = emailOptions.subject;
// Test mode - redirect emails and modify subject
const testMode = isTest || process.env.EMAIL_TEST_MODE === 'true';
if (testMode) {
const originalTo = to;
to = process.env.TEST_EMAIL_RECIPIENT || 'admin@example.com';
subject = `[TEST - Original: ${originalTo}] ${subject}`;
console.log(`Email redirected from ${originalTo} to ${to} (Test Mode)`);
}
const mailOptions = {
from: `"${emailOptions.from.name}" <${emailOptions.from.email}>`,
to: to,
replyTo: emailOptions.replyTo,
subject: subject,
text: emailOptions.text,
html: emailOptions.html
};
// Log email details in development
if (process.env.NODE_ENV === 'development') {
console.log('Email Preview:', {
to: mailOptions.to,
subject: mailOptions.subject,
preview: emailOptions.text ? emailOptions.text.substring(0, 200) + '...' : 'No text content',
testMode: testMode
});
}
const info = await this.transporter.sendMail(mailOptions);
console.log('Email sent successfully:', info.messageId);
// Log email to database if NocoDB service is available
await this.logEmailSent({
to: emailOptions.to, // Log original recipient
subject: emailOptions.subject, // Log original subject
status: 'sent',
messageId: info.messageId,
testMode: testMode,
senderName: emailOptions.from?.name || 'System',
senderEmail: emailOptions.from?.email || process.env.SMTP_FROM_EMAIL
});
return {
success: true,
messageId: info.messageId,
response: info.response,
testMode: testMode,
originalRecipient: testMode ? emailOptions.to : undefined
};
} catch (error) {
console.error('Email send error:', error);
// Log failed email attempt
await this.logEmailSent({
to: emailOptions.to,
subject: emailOptions.subject,
status: 'failed',
error: error.message,
testMode: isTest || process.env.EMAIL_TEST_MODE === 'true',
senderName: emailOptions.from?.name || 'System',
senderEmail: emailOptions.from?.email || process.env.SMTP_FROM_EMAIL
});
return {
success: false,
error: error.message
};
}
}
async sendBulkEmails(emails) {
const results = [];
for (const email of emails) {
try {
const result = await this.sendEmail(email);
results.push({
to: email.to,
success: result.success,
messageId: result.messageId,
error: result.error
});
// Add a small delay between emails to avoid overwhelming the SMTP server
await new Promise(resolve => setTimeout(resolve, 1000));
} catch (error) {
results.push({
to: email.to,
success: false,
error: error.message
});
}
}
return results;
}
formatEmailTemplate(template, data) {
let formattedTemplate = template;
// Replace placeholders with actual data
Object.keys(data).forEach(key => {
const placeholder = `{{${key}}}`;
formattedTemplate = formattedTemplate.replace(new RegExp(placeholder, 'g'), data[key]);
});
return formattedTemplate;
}
validateEmailAddress(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
async logEmailSent(emailData) {
try {
// Use the existing logEmailSend method with the correct field structure
const nocodbService = require('./nocodb');
if (nocodbService && process.env.NOCODB_TABLE_EMAILS) {
await nocodbService.logEmailSend({
recipientEmail: emailData.to,
senderName: emailData.senderName || 'System',
senderEmail: emailData.senderEmail || process.env.SMTP_FROM_EMAIL,
subject: emailData.subject,
postalCode: emailData.postalCode || 'N/A',
status: emailData.status || 'sent',
timestamp: new Date().toISOString(),
senderIP: emailData.senderIP || 'localhost'
});
}
} catch (error) {
console.error('Failed to log email:', error);
// Don't throw - logging failure shouldn't prevent email sending
}
}
async previewEmail(emailOptions) {
// Generate email preview without sending
return {
to: emailOptions.to,
subject: emailOptions.subject,
body: emailOptions.text,
html: emailOptions.html,
from: `"${emailOptions.from.name}" <${emailOptions.from.email}>`,
replyTo: emailOptions.replyTo,
timestamp: new Date().toISOString(),
testMode: process.env.EMAIL_TEST_MODE === 'true',
redirectTo: process.env.EMAIL_TEST_MODE === 'true' ? process.env.TEST_EMAIL_RECIPIENT : null
};
}
// Template-based email methods
async sendTemplatedEmail(templateName, templateVariables, emailOptions, isTest = false) {
try {
// Render the template
const { html, text } = await emailTemplates.render(templateName, templateVariables);
// Prepare email options with rendered content
const mailOptions = {
...emailOptions,
text: text,
html: html
};
// Send the email using existing sendEmail method
return await this.sendEmail(mailOptions, isTest);
} catch (error) {
console.error('Failed to send templated email:', error);
return {
success: false,
error: error.message
};
}
}
async sendRepresentativeEmail(recipientEmail, senderName, senderEmail, subject, message, postalCode) {
const templateVariables = {
MESSAGE: message,
SENDER_NAME: senderName,
SENDER_EMAIL: senderEmail,
POSTAL_CODE: postalCode
};
const emailOptions = {
to: recipientEmail,
from: {
email: process.env.SMTP_FROM_EMAIL,
name: process.env.SMTP_FROM_NAME
},
replyTo: senderEmail,
subject: subject
};
return await this.sendTemplatedEmail('representative-contact', templateVariables, emailOptions);
}
async sendCampaignEmail(recipientEmail, userEmail, userName, postalCode, subject, message, campaignTitle, recipientName = null, recipientLevel = null) {
const templateVariables = {
MESSAGE: message,
USER_NAME: userName,
USER_EMAIL: userEmail,
POSTAL_CODE: postalCode,
CAMPAIGN_TITLE: campaignTitle,
RECIPIENT_NAME: recipientName,
RECIPIENT_LEVEL: recipientLevel
};
const emailOptions = {
to: recipientEmail,
from: {
email: process.env.SMTP_FROM_EMAIL,
name: process.env.SMTP_FROM_NAME
},
replyTo: userEmail,
subject: subject
};
return await this.sendTemplatedEmail('campaign-email', templateVariables, emailOptions);
}
async sendTestEmail(subject, message, testRecipient = null) {
const recipient = testRecipient || process.env.TEST_EMAIL_RECIPIENT || 'admin@example.com';
const templateVariables = {
MESSAGE: message
};
const emailOptions = {
to: recipient,
from: {
email: process.env.SMTP_FROM_EMAIL,
name: process.env.SMTP_FROM_NAME
},
replyTo: process.env.SMTP_FROM_EMAIL,
subject: `[TEST EMAIL] ${subject}`
};
return await this.sendTemplatedEmail('test-email', templateVariables, emailOptions, true);
}
async previewTemplatedEmail(templateName, templateVariables, emailOptions) {
try {
const { html, text } = await emailTemplates.render(templateName, templateVariables);
return {
to: emailOptions.to,
subject: emailOptions.subject,
body: text,
html: html,
from: `"${emailOptions.from.name}" <${emailOptions.from.email}>`,
replyTo: emailOptions.replyTo,
timestamp: new Date().toISOString(),
testMode: process.env.EMAIL_TEST_MODE === 'true',
redirectTo: process.env.EMAIL_TEST_MODE === 'true' ? process.env.TEST_EMAIL_RECIPIENT : null,
templateName: templateName,
templateVariables: templateVariables
};
} catch (error) {
console.error('Failed to preview templated email:', error);
throw error;
}
}
}
module.exports = new EmailService();