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}
${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'
});
}
});