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 async 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 || ''; // Get branding settings for logo let logoUrl = ''; let companyName = 'PicPeak'; try { const brandingSettings = await db('app_settings') .whereIn('setting_key', ['branding_logo_url', 'branding_company_name']) .select('setting_key', 'setting_value'); brandingSettings.forEach(setting => { if (setting.setting_key === 'branding_logo_url' && setting.setting_value) { try { logoUrl = JSON.parse(setting.setting_value); } catch (e) { logoUrl = setting.setting_value; } } else if (setting.setting_key === 'branding_company_name' && setting.setting_value) { try { companyName = JSON.parse(setting.setting_value); } catch (e) { companyName = setting.setting_value; } } }); } catch (error) { logger.error('Error fetching branding settings:', error); } // If no custom logo, use default PicPeak logo const apiUrl = process.env.API_URL || 'http://localhost:3001'; const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:3005'; const logoFullUrl = logoUrl ? `${apiUrl}${logoUrl}` : `${frontendUrl}/picpeak-logo-transparent.png`; // Process welcome message section if present let welcomeMessageSection = ''; if (variables.welcome_message && variables.welcome_message.trim() !== '') { const welcomeTitle = language === 'de' ? 'Persönliche Nachricht:' : 'Personal Message:'; welcomeMessageSection = `

${welcomeTitle}

${variables.welcome_message}

`; } // 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 || ''); }); // Replace welcome message section placeholder htmlBody = htmlBody.replace(/{{welcome_message_section}}/g, welcomeMessageSection); // Wrap HTML body in styled template const styledHtmlBody = ` ${subject}
`; return { subject, htmlBody: styledHtmlBody, 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 } = await 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 - DISABLED for production startup // This will be called from server.js after database is ready // initializeTransporter().then(() => { // startEmailQueueProcessor(); // }); module.exports = { initializeTransporter, startEmailQueueProcessor, sendTemplateEmail, processEmailQueue, queueEmail, startEmailQueueProcessor, stopEmailQueueProcessor };