diff --git a/src/lib/services/email.ts b/src/lib/services/email.ts index 08dc72aa..7cc45f92 100644 --- a/src/lib/services/email.ts +++ b/src/lib/services/email.ts @@ -1,3 +1,4 @@ +import { exec } from "child_process"; import nodemailer, { type Transporter } from "nodemailer"; import { Resend } from "resend"; import { env } from "@/env"; @@ -24,7 +25,33 @@ function getResend(): Resend | null { return resend; } -/** Send an HTML email. Tries Resend first, then SMTP. Returns false if both are unconfigured. */ +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) { + console.error("[email] sendmail failed:", error.message); + resolve(false); + } else { + resolve(true); + } + }); + + if (child.stdin) { + child.stdin.write(`${headers}\r\n\r\n${html}`); + child.stdin.end(); + } + }); +} + +/** Send an HTML email. Tries Resend → SMTP → local sendmail. Returns false if all fail. */ export async function sendMail(to: string, subject: string, html: string): Promise { const from = env.SMTP_FROM ?? `no-reply@${env.HOTEL_NAME}`; @@ -48,8 +75,11 @@ export async function sendMail(to: string, subject: string, html: string): Promi } } + const ok = await sendViaSendmail(to, subject, html, from); + if (ok) return true; + if (!r && !t) { - console.warn("[email] No mailer configured — set RESEND_API_KEY or SMTP_HOST"); + console.warn("[email] No mailer configured — set RESEND_API_KEY or SMTP_HOST, or install sendmail"); } return false; }