import nodemailer from 'nodemailer'; import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); // Email templates for different notification types const emailTemplates = { info: { color: '#3b82f6', backgroundColor: '#eff6ff', icon: 'ℹ️' }, success: { color: '#10b981', backgroundColor: '#ecfdf5', icon: '✅' }, warning: { color: '#f59e0b', backgroundColor: '#fffbeb', icon: '⚠️' }, error: { color: '#ef4444', backgroundColor: '#fef2f2', icon: '❌' }, reminder: { color: '#8b5cf6', backgroundColor: '#f3e8ff', icon: '⏰' }, status: { color: '#6366f1', backgroundColor: '#eef2ff', icon: '📊' } }; // Create Nodemailer transporter with configurable email provider settings function createTransporter() { // Default configuration for common providers const providerConfigs = { gmail: { host: 'smtp.gmail.com', port: 587, secure: false }, outlook: { host: 'smtp-mail.outlook.com', port: 587, secure: false }, yahoo: { host: 'smtp.mail.yahoo.com', port: 587, secure: false }, proton: { host: 'mail.protonmail.ch', port: 587, secure: false, tls: { ciphers: 'SSLv3', rejectUnauthorized: false } } }; // Get provider from environment or default to custom const provider = process.env.EMAIL_PROVIDER?.toLowerCase() || 'custom'; // Use provider-specific config or custom config let config; if (providerConfigs[provider]) { config = { ...providerConfigs[provider], auth: { user: process.env.EMAIL_USER, pass: process.env.EMAIL_PASSWORD } }; } else { // Custom SMTP configuration config = { host: process.env.SMTP_HOST, port: parseInt(process.env.SMTP_PORT) || 587, secure: process.env.SMTP_SECURE === 'true', // true for SSL (port 465), false for STARTTLS (port 587) auth: { user: process.env.EMAIL_USER, pass: process.env.EMAIL_PASSWORD } }; // Add custom TLS settings if provided if (process.env.SMTP_TLS_CIPHERS || process.env.SMTP_TLS_REJECT_UNAUTHORIZED) { config.tls = {}; if (process.env.SMTP_TLS_CIPHERS) { config.tls.ciphers = process.env.SMTP_TLS_CIPHERS; } if (process.env.SMTP_TLS_REJECT_UNAUTHORIZED) { config.tls.rejectUnauthorized = process.env.SMTP_TLS_REJECT_UNAUTHORIZED === 'true'; } } } return nodemailer.createTransport(config); } // Generate HTML email template function generateEmailTemplate(notificationType, subject, message, processName) { const template = emailTemplates[notificationType] || emailTemplates.info; return ` ${subject}
${template.icon}

${subject}

${message.replace(/\n/g, '
')}
${processName ? `
Process: ${processName}
` : ''}
`; } // Resolve recipients based on recipient type async function resolveRecipients(recipientType, recipientData, processVariables) { const recipients = []; try { switch (recipientType) { case 'email': // Direct email address if (recipientData.recipientEmail && isValidEmail(recipientData.recipientEmail)) { recipients.push({ email: recipientData.recipientEmail, name: recipientData.recipientEmail }); } break; case 'user': // Look up user from database if (recipientData.recipientUser) { const user = await prisma.user.findUnique({ where: { id: recipientData.recipientUser }, select: { email: true, name: true, firstname: true, lastname: true } }); if (user && user.email && isValidEmail(user.email)) { const displayName = user.name || `${user.firstname || ''} ${user.lastname || ''}`.trim() || user.email; recipients.push({ email: user.email, name: displayName }); } } break; case 'role': // Look up users by role if (recipientData.recipientRole) { const usersWithRole = await prisma.user.findMany({ where: { userrole: { some: { roleid: recipientData.recipientRole } } }, select: { email: true, name: true, firstname: true, lastname: true } }); usersWithRole.forEach(user => { if (user.email && isValidEmail(user.email)) { const displayName = user.name || `${user.firstname || ''} ${user.lastname || ''}`.trim() || user.email; recipients.push({ email: user.email, name: displayName }); } }); } break; case 'variable': // Get recipient from process variable if (recipientData.recipientVariable && processVariables[recipientData.recipientVariable]) { const variableValue = processVariables[recipientData.recipientVariable]; if (typeof variableValue === 'string' && isValidEmail(variableValue)) { recipients.push({ email: variableValue, name: variableValue }); } else if (typeof variableValue === 'object' && variableValue.email && isValidEmail(variableValue.email)) { recipients.push({ email: variableValue.email, name: variableValue.name || variableValue.email }); } } break; case 'group': // For future implementation - group-based recipients console.warn('Group-based recipients not yet implemented'); break; default: console.warn(`Unknown recipient type: ${recipientType}`); } } catch (error) { console.error('Error resolving recipients:', error); } return recipients; } // Validate email address function isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } // Substitute variables in text function substituteVariables(text, variables) { if (typeof text !== 'string') return text; // Replace {{variable}} format text = text.replace(/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g, (match, varName) => { const value = variables[varName]; if (value === undefined || value === null) return ''; if (typeof value === 'object') return JSON.stringify(value); return String(value); }); // Replace {variable} format text = text.replace(/\{([a-zA-Z0-9_]+)\}/g, (match, varName) => { const value = variables[varName]; if (value === undefined || value === null) return ''; if (typeof value === 'object') return JSON.stringify(value); return String(value); }); return text; } export default defineEventHandler(async (event) => { try { // Only allow POST requests if (event.node.req.method !== 'POST') { throw createError({ statusCode: 405, statusMessage: 'Method Not Allowed' }); } // Parse request body const body = await readBody(event); const { notificationType = 'info', recipientType = 'email', recipientData = {}, subject = '', message = '', processVariables = {}, processName = '', deliveryOptions = { email: true }, priority = 'medium', htmlMessage = '', messageFormat = 'text' } = body; // Validate required fields if (!subject && !message) { throw createError({ statusCode: 400, statusMessage: 'Subject or message is required' }); } // Check if email delivery is enabled if (!deliveryOptions.email) { return { success: true, message: 'Email delivery disabled for this notification', sent: 0 }; } // Check environment variables if (!process.env.EMAIL_USER || !process.env.EMAIL_PASSWORD) { throw createError({ statusCode: 500, statusMessage: 'Email configuration missing. Please set EMAIL_USER and EMAIL_PASSWORD environment variables.' }); } // For custom SMTP, also check required SMTP settings const provider = process.env.EMAIL_PROVIDER?.toLowerCase() || 'custom'; if (provider === 'custom' && !process.env.SMTP_HOST) { throw createError({ statusCode: 500, statusMessage: 'SMTP configuration missing. Please set SMTP_HOST for custom email provider.' }); } // Substitute variables in content const processedSubject = substituteVariables(subject, processVariables); const processedMessage = substituteVariables(message, processVariables); const processedHtmlMessage = htmlMessage ? substituteVariables(htmlMessage, processVariables) : ''; // Resolve recipients const recipients = await resolveRecipients(recipientType, recipientData, processVariables); if (recipients.length === 0) { throw createError({ statusCode: 400, statusMessage: 'No valid recipients found' }); } // Create transporter const transporter = createTransporter(); // Verify connection try { await transporter.verify(); } catch (error) { console.error('SMTP connection failed:', error); throw createError({ statusCode: 500, statusMessage: 'Failed to connect to email server' }); } // Prepare email content let emailHtml; if (messageFormat === 'html' && processedHtmlMessage) { emailHtml = processedHtmlMessage; } else { emailHtml = generateEmailTemplate(notificationType, processedSubject, processedMessage, processName); } // Send emails const results = []; let successCount = 0; let failureCount = 0; for (const recipient of recipients) { try { const mailOptions = { from: { name: process.env.EMAIL_FROM_NAME || 'Corrad Process System', address: process.env.EMAIL_USER }, to: { name: recipient.name, address: recipient.email }, subject: processedSubject, text: processedMessage, html: emailHtml, priority: priority === 'high' ? 'high' : priority === 'low' ? 'low' : 'normal' }; const info = await transporter.sendMail(mailOptions); results.push({ recipient: recipient.email, success: true, messageId: info.messageId }); successCount++; console.log(`Email sent successfully to ${recipient.email}:`, info.messageId); } catch (error) { results.push({ recipient: recipient.email, success: false, error: error.message }); failureCount++; console.error(`Failed to send email to ${recipient.email}:`, error.message); } } // Close transporter transporter.close(); // Return results return { success: successCount > 0, message: `Email notification processed. Sent: ${successCount}, Failed: ${failureCount}`, sent: successCount, failed: failureCount, recipients: recipients.length, details: results }; } catch (error) { console.error('Notification API Error:', error); // Handle createError objects if (error.statusCode) { throw error; } // Handle other errors throw createError({ statusCode: 500, statusMessage: error.message || 'Failed to send notification' }); } });