import * as nodemailer from "nodemailer"; import { prisma } from "~/utils/db.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 mailSettings = await prisma.mailSettings.findFirst(); if (!mailSettings) { throw new Error("Mail settings not configured. Please configure SMTP settings first."); } // Create transporter with enhanced configuration const transportConfig: any = { host: mailSettings.host, port: mailSettings.port, secure: mailSettings.secure, auth: { user: mailSettings.username, pass: mailSettings.password, }, }; // 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 mailSettings = await prisma.mailSettings.findFirst(); if (!mailSettings) { return { success: false, error: "Mail settings not configured", }; } 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, }, }; // 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 = `
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;
}
}