import { exec } from "child_process"; import { writeFile, mkdir } from "fs/promises"; import { resolve } from "path"; import nodemailer, { type Transporter } from "nodemailer"; import { Resend } from "resend"; import { env } from "@/env"; import { logger } from "@/lib/logger"; let transporter: Transporter | null = null; let resend: Resend | null = null; function getTransport(): Transporter | null { if (!env.SMTP_HOST) return null; if (!transporter) { transporter = nodemailer.createTransport({ host: env.SMTP_HOST, port: env.SMTP_PORT ?? 587, secure: env.SMTP_SECURE, auth: env.SMTP_USER ? { user: env.SMTP_USER, pass: env.SMTP_PASSWORD } : undefined, }); } return transporter; } function getResend(): Resend | null { if (!env.RESEND_API_KEY) return null; if (!resend) resend = new Resend(env.RESEND_API_KEY); return resend; } function sendViaSendmail(to: string, subject: string, html: string, from: string): Promise { return new Promise((resolve) => { const headers = [ `From: ${from}`, `To: ${to}`, `Subject: ${subject}`, "MIME-Version: 1.0", 'Content-Type: text/html; charset="UTF-8"', ].join("\r\n"); const child = exec("sendmail -t", (error) => { if (error) { logger.error("Sendmail failed", { module: "email", error: error.message }); resolve(false); } else { resolve(true); } }); if (child.stdin) { child.stdin.write(`${headers}\r\n\r\n${html}`); child.stdin.end(); } }); } async function writeToFile(to: string, subject: string, html: string, from: string): Promise { try { const logDir = resolve(process.cwd(), "storage", "logs"); // eslint-disable-next-line security/detect-non-literal-fs-filename await mkdir(logDir, { recursive: true }); const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); const filename = `email-${timestamp}.html`; const content = `\n${html}`; // eslint-disable-next-line security/detect-non-literal-fs-filename await writeFile(resolve(logDir, filename), content, "utf-8"); logger.info("Email written to file", { module: "email", filename }); return true; } catch (e) { logger.error("Failed to write email to file", { module: "email", error: (e as Error).message }); return false; } } /** Send an HTML email. Tries Resend → SMTP → local sendmail → file fallback. Always returns true. */ export async function sendMail(to: string, subject: string, html: string): Promise { const from = env.SMTP_FROM ?? `no-reply@${env.HOTEL_NAME}`; const r = getResend(); if (r) { try { await r.emails.send({ from, to, subject, html }); return true; } catch (e) { logger.error("Resend API failed", { module: "email", error: (e as Error).message }); } } const t = getTransport(); if (t) { try { await t.sendMail({ from, to, subject, html }); return true; } catch (e) { logger.error("SMTP failed", { module: "email", error: (e as Error).message }); } } const ok = await sendViaSendmail(to, subject, html, from); if (ok) return true; await writeToFile(to, subject, html, from); return true; }