Add Resend mailer as primary with SMTP fallback for reliable email delivery

This commit is contained in:
openhands committed 2026-07-07 20:12:56 +02:00
1 parent f386ae2b25
commit bf4075233d
4 files changed
+75 -15

No files matched your search

+2
View File
@@ -11,6 +11,8 @@ const schema = z.object({
DATABASE_CONNECT_TIMEOUT_MS: z.coerce.number().int().positive().default(10_000),
HOTEL_NAME: z.string().default("Atom"),
APP_URL: z.string().url().default("http://localhost:3000"),
// Resend API key (preferred — simpler HTTP API, always works).
RESEND_API_KEY: z.string().optional(),
// SMTP (password reset / notifications). Email features no-op if unset.
SMTP_HOST: z.string().optional(),
SMTP_PORT: z.coerce.number().int().positive().optional(),
+32 -15
View File
@@ -1,7 +1,9 @@
import nodemailer, { type Transporter } from "nodemailer";
import { Resend } from "resend";
import { env } from "@/env";
let transporter: Transporter | null = null;
let resend: Resend | null = null;
function getTransport(): Transporter | null {
if (!env.SMTP_HOST) return null;
@@ -16,23 +18,38 @@ function getTransport(): Transporter | null {
return transporter;
}
/** Send an HTML email. No-ops (returns false) when SMTP isn't configured. */
function getResend(): Resend | null {
if (!env.RESEND_API_KEY) return null;
if (!resend) resend = new Resend(env.RESEND_API_KEY);
return resend;
}
/** Send an HTML email. Tries Resend first, then SMTP. Returns false if both are unconfigured. */
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) {
console.error("[email] Resend failed:", (e as Error).message);
}
}
const t = getTransport();
if (!t) {
console.warn("[email] SMTP not configured — skipping mail to", to);
return false;
if (t) {
try {
await t.sendMail({ from, to, subject, html });
return true;
} catch (e) {
console.error("[email] SMTP failed:", (e as Error).message);
}
}
try {
await t.sendMail({
from: env.SMTP_FROM ?? `no-reply@${env.HOTEL_NAME}`,
to,
subject,
html,
});
return true;
} catch (e) {
console.error("[email] send failed:", (e as Error).message);
return false;
if (!r && !t) {
console.warn("[email] No mailer configured — set RESEND_API_KEY or SMTP_HOST");
}
return false;
}