This commit is contained in:
1 parent
8efd032cc6
commit
df38dccbf1
735 files changed
+128321
-120870
No files matched your search
+106
-78
@@ -1,6 +1,6 @@
|
||||
import { exec } from "child_process";
|
||||
import { writeFile, mkdir } from "fs/promises";
|
||||
import { resolve } from "path";
|
||||
import { exec } from "node:child_process";
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import nodemailer, { type Transporter } from "nodemailer";
|
||||
import { Resend } from "resend";
|
||||
import { env } from "@/env";
|
||||
@@ -10,95 +10,123 @@ 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;
|
||||
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;
|
||||
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<boolean> {
|
||||
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");
|
||||
function sendViaSendmail(
|
||||
to: string,
|
||||
subject: string,
|
||||
html: string,
|
||||
from: string,
|
||||
): Promise<boolean> {
|
||||
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);
|
||||
}
|
||||
});
|
||||
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();
|
||||
}
|
||||
});
|
||||
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<boolean> {
|
||||
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 = `<!-- To: ${to} | From: ${from} | Subject: ${subject} -->\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;
|
||||
}
|
||||
async function writeToFile(
|
||||
to: string,
|
||||
subject: string,
|
||||
html: string,
|
||||
from: string,
|
||||
): Promise<boolean> {
|
||||
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 = `<!-- To: ${to} | From: ${from} | Subject: ${subject} -->\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<boolean> {
|
||||
const from = env.SMTP_FROM ?? `no-reply@${env.HOTEL_NAME}`;
|
||||
export async function sendMail(
|
||||
to: string,
|
||||
subject: string,
|
||||
html: string,
|
||||
): Promise<boolean> {
|
||||
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 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 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;
|
||||
const ok = await sendViaSendmail(to, subject, html, from);
|
||||
if (ok) return true;
|
||||
|
||||
await writeToFile(to, subject, html, from);
|
||||
return true;
|
||||
await writeToFile(to, subject, html, from);
|
||||
return true;
|
||||
}
|
||||
Reference in new issue
Block a user