207 lines
5.3 KiB
TypeScript
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;
|
|
}
|
|
} |