phosphat-report-app/app/utils/mail.server.ts
2025-08-01 05:00:14 +03:00

207 lines
5.3 KiB
TypeScript

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 = `
<h2>${subject}</h2>
<p>Please find the report details below:</p>
<pre>${JSON.stringify(reportData, null, 2)}</pre>
`;
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;
}
}