import * as nodemailer from "nodemailer"; import { prisma } from "~/utils/db.server"; import { decryptMailSettings } from "~/utils/encryption.server"; interface EmailOptions { to: string | string[]; subject: string; text?: string; html?: string; attachments?: Array<{ filename: string; content: Buffer | string; contentType?: string; }>; } export async function sendEmail(options: EmailOptions) { try { // Get mail settings from database const encryptedMailSettings = await prisma.mailSettings.findFirst(); if (!encryptedMailSettings) { throw new Error("Mail settings not configured. Please configure SMTP settings first."); } // Decrypt the mail settings const mailSettings = decryptMailSettings(encryptedMailSettings); // Create transporter with enhanced configuration const transportConfig: any = { host: mailSettings.host, port: mailSettings.port, secure: mailSettings.secure, auth: { user: mailSettings.username, pass: mailSettings.password, // Now decrypted }, }; // Add additional options for better compatibility if (!mailSettings.secure && mailSettings.port === 587) { transportConfig.requireTLS = true; transportConfig.tls = { ciphers: 'SSLv3' }; } // Add timeout settings transportConfig.connectionTimeout = 10000; transportConfig.greetingTimeout = 5000; transportConfig.socketTimeout = 10000; const transporter = nodemailer.createTransport(transportConfig); // Verify connection await transporter.verify(); // Send email const result = await transporter.sendMail({ from: `"${mailSettings.fromName}" <${mailSettings.fromEmail}>`, to: Array.isArray(options.to) ? options.to.join(", ") : options.to, subject: options.subject, text: options.text, html: options.html, attachments: options.attachments, }); return { success: true, messageId: result.messageId, response: result.response, }; } catch (error) { console.error("Failed to send email:", error); return { success: false, error: error instanceof Error ? error.message : "Unknown error occurred", }; } } export async function testEmailConnection() { try { const encryptedMailSettings = await prisma.mailSettings.findFirst(); if (!encryptedMailSettings) { return { success: false, error: "Mail settings not configured", }; } // Decrypt the mail settings const mailSettings = decryptMailSettings(encryptedMailSettings); const transportConfig: any = { host: mailSettings.host, port: mailSettings.port, secure: mailSettings.secure, // true for 465, false for other ports auth: { user: mailSettings.username, pass: mailSettings.password, // Now decrypted }, }; // Add additional options for better compatibility if (!mailSettings.secure && mailSettings.port === 587) { transportConfig.requireTLS = true; transportConfig.tls = { ciphers: 'SSLv3' }; } // Add timeout settings transportConfig.connectionTimeout = 10000; // 10 seconds transportConfig.greetingTimeout = 5000; // 5 seconds transportConfig.socketTimeout = 10000; // 10 seconds console.log('Testing SMTP connection with config:', { host: mailSettings.host, port: mailSettings.port, secure: mailSettings.secure, user: mailSettings.username }); const transporter = nodemailer.createTransport(transportConfig); await transporter.verify(); return { success: true, message: "SMTP connection successful", }; } catch (error) { console.error('SMTP connection error:', error); return { success: false, error: error instanceof Error ? error.message : "Connection failed", }; } } // Helper function to send notification emails export async function sendNotificationEmail( to: string | string[], subject: string, message: string, isHtml: boolean = false ) { const emailOptions: EmailOptions = { to, subject, }; if (isHtml) { emailOptions.html = message; } else { emailOptions.text = message; } return await sendEmail(emailOptions); } // Helper function to send report emails with attachments export async function sendReportEmail( to: string | string[], subject: string, reportData: any, attachments?: Array<{ filename: string; content: Buffer | string; contentType?: string; }> ) { const htmlContent = `

${subject}

Please find the report details below:

${JSON.stringify(reportData, null, 2)}
`; return await sendEmail({ to, subject, html: htmlContent, attachments, }); } // Utility function to clean up expired password reset tokens export async function cleanupExpiredTokens() { try { const result = await prisma.passwordResetToken.deleteMany({ where: { OR: [ { expiresAt: { lt: new Date() } }, { used: true } ] } }); console.log(`Cleaned up ${result.count} expired/used password reset tokens`); return result.count; } catch (error) { console.error("Failed to cleanup expired tokens:", error); return 0; } }