69b56ed582
- Add language columns to email_templates table (subject_en/de, body_html_en/de, body_text_en/de) - Update adminEmail.js routes to support language-specific templates - Create EmailProcessor service to handle language selection based on recipient - Update EmailConfigPage component with language tabs similar to CMS pages - Add German translations for all email templates - Update all email queue usage to use proper template keys - Add missing email templates (gallery_expired, archive_complete) - Integrate email processor service into main server startup The system now automatically selects the appropriate language (English/German) based on the recipient's email domain or preferences. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
228 lines
6.3 KiB
JavaScript
228 lines
6.3 KiB
JavaScript
const nodemailer = require('nodemailer');
|
|
const { db } = require('../database/db');
|
|
const logger = require('../utils/logger');
|
|
|
|
let transporter = null;
|
|
|
|
// Initialize transporter from database config
|
|
async function initializeTransporter() {
|
|
try {
|
|
const config = await db('email_configs').first();
|
|
|
|
if (!config) {
|
|
logger.warn('No email configuration found');
|
|
return null;
|
|
}
|
|
|
|
transporter = nodemailer.createTransport({
|
|
host: config.smtp_host,
|
|
port: config.smtp_port,
|
|
secure: config.smtp_secure,
|
|
auth: config.smtp_user ? {
|
|
user: config.smtp_user,
|
|
pass: config.smtp_pass
|
|
} : undefined
|
|
});
|
|
|
|
// Verify configuration
|
|
await transporter.verify();
|
|
logger.info('Email transporter initialized successfully');
|
|
|
|
return transporter;
|
|
} catch (error) {
|
|
logger.error('Failed to initialize email transporter:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Get the appropriate language for a recipient
|
|
async function getRecipientLanguage(email) {
|
|
// For now, check if the email domain ends with .de
|
|
// In the future, this could check user preferences
|
|
if (email && email.endsWith('.de')) {
|
|
return 'de';
|
|
}
|
|
|
|
// Check if there's a saved preference for this email
|
|
// This could be expanded to check user preferences in the database
|
|
|
|
return 'en'; // Default to English
|
|
}
|
|
|
|
// Process email template with variables
|
|
function processTemplate(template, variables, language = 'en') {
|
|
// Get the appropriate language fields
|
|
const subjectField = language === 'de' ? 'subject_de' : 'subject_en';
|
|
const htmlField = language === 'de' ? 'body_html_de' : 'body_html_en';
|
|
const textField = language === 'de' ? 'body_text_de' : 'body_text_en';
|
|
|
|
// Fall back to non-language-specific fields for backward compatibility
|
|
let subject = template[subjectField] || template.subject || '';
|
|
let htmlBody = template[htmlField] || template.body_html || '';
|
|
let textBody = template[textField] || template.body_text || '';
|
|
|
|
// Replace variables
|
|
Object.entries(variables).forEach(([key, value]) => {
|
|
const regex = new RegExp(`{{${key}}}`, 'g');
|
|
subject = subject.replace(regex, value || '');
|
|
htmlBody = htmlBody.replace(regex, value || '');
|
|
textBody = textBody.replace(regex, value || '');
|
|
});
|
|
|
|
return { subject, htmlBody, textBody };
|
|
}
|
|
|
|
// Send email using template
|
|
async function sendTemplateEmail(to, templateKey, variables) {
|
|
try {
|
|
if (!transporter) {
|
|
transporter = await initializeTransporter();
|
|
if (!transporter) {
|
|
throw new Error('Email service not configured');
|
|
}
|
|
}
|
|
|
|
// Get email template
|
|
const template = await db('email_templates')
|
|
.where('template_key', templateKey)
|
|
.first();
|
|
|
|
if (!template) {
|
|
throw new Error(`Email template '${templateKey}' not found`);
|
|
}
|
|
|
|
// Get email config for from address
|
|
const config = await db('email_configs').first();
|
|
if (!config) {
|
|
throw new Error('Email configuration not found');
|
|
}
|
|
|
|
// Determine recipient language
|
|
const language = await getRecipientLanguage(to);
|
|
|
|
// Process template with variables
|
|
const { subject, htmlBody, textBody } = processTemplate(template, variables, language);
|
|
|
|
// Send email
|
|
const info = await transporter.sendMail({
|
|
from: `${config.from_name} <${config.from_email}>`,
|
|
to: to,
|
|
subject: subject,
|
|
html: htmlBody,
|
|
text: textBody || htmlBody.replace(/<[^>]*>/g, '') // Strip HTML if no text version
|
|
});
|
|
|
|
logger.info(`Email sent successfully: ${info.messageId} (${language})`);
|
|
return { success: true, messageId: info.messageId, language };
|
|
} catch (error) {
|
|
logger.error('Error sending template email:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Process email queue
|
|
async function processEmailQueue() {
|
|
try {
|
|
const pendingEmails = await db('email_queue')
|
|
.where('status', 'pending')
|
|
.where('retry_count', '<', 3)
|
|
.orderBy('created_at', 'asc')
|
|
.limit(10);
|
|
|
|
if (pendingEmails.length === 0) {
|
|
return;
|
|
}
|
|
|
|
logger.info(`Processing ${pendingEmails.length} emails from queue`);
|
|
|
|
for (const email of pendingEmails) {
|
|
try {
|
|
const emailData = JSON.parse(email.email_data || '{}');
|
|
|
|
await sendTemplateEmail(
|
|
email.recipient_email,
|
|
email.email_type,
|
|
emailData
|
|
);
|
|
|
|
// Mark as sent
|
|
await db('email_queue')
|
|
.where('id', email.id)
|
|
.update({
|
|
status: 'sent',
|
|
sent_at: new Date()
|
|
});
|
|
|
|
logger.info(`Email ${email.id} sent successfully`);
|
|
} catch (error) {
|
|
// Increment retry count
|
|
await db('email_queue')
|
|
.where('id', email.id)
|
|
.update({
|
|
retry_count: email.retry_count + 1,
|
|
error_message: error.message,
|
|
updated_at: new Date()
|
|
});
|
|
|
|
logger.error(`Failed to send email ${email.id}:`, error);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
logger.error('Error processing email queue:', error);
|
|
}
|
|
}
|
|
|
|
// Queue an email for sending
|
|
async function queueEmail(eventId, recipientEmail, emailType, emailData) {
|
|
try {
|
|
await db('email_queue').insert({
|
|
event_id: eventId,
|
|
recipient_email: recipientEmail,
|
|
email_type: emailType,
|
|
email_data: JSON.stringify(emailData),
|
|
status: 'pending',
|
|
retry_count: 0,
|
|
created_at: new Date()
|
|
});
|
|
|
|
logger.info(`Email queued: ${emailType} to ${recipientEmail}`);
|
|
} catch (error) {
|
|
logger.error('Error queueing email:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Start email queue processor
|
|
let emailQueueInterval = null;
|
|
|
|
function startEmailQueueProcessor() {
|
|
if (!emailQueueInterval) {
|
|
// Process immediately on start
|
|
processEmailQueue();
|
|
|
|
// Then process every minute
|
|
emailQueueInterval = setInterval(processEmailQueue, 60000);
|
|
logger.info('Email queue processor started');
|
|
}
|
|
}
|
|
|
|
function stopEmailQueueProcessor() {
|
|
if (emailQueueInterval) {
|
|
clearInterval(emailQueueInterval);
|
|
emailQueueInterval = null;
|
|
logger.info('Email queue processor stopped');
|
|
}
|
|
}
|
|
|
|
// Initialize on module load
|
|
initializeTransporter().then(() => {
|
|
startEmailQueueProcessor();
|
|
});
|
|
|
|
module.exports = {
|
|
sendTemplateEmail,
|
|
processEmailQueue,
|
|
queueEmail,
|
|
startEmailQueueProcessor,
|
|
stopEmailQueueProcessor
|
|
}; |