Add Resend mailer as primary with SMTP fallback for reliable email delivery
This commit is contained in:
1 parent
f386ae2b25
commit
bf4075233d
4 files changed
+74
-14
No files matched your search
@@ -32,6 +32,7 @@
|
|||||||
"otplib": "^12.0.1",
|
"otplib": "^12.0.1",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
|
"resend": "^6.17.1",
|
||||||
"sanitize-html": "^2.17.5",
|
"sanitize-html": "^2.17.5",
|
||||||
"zod": "^3.24.0"
|
"zod": "^3.24.0"
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+40
File diff suppressed because it is too large.
Load diff
@@ -11,6 +11,8 @@ const schema = z.object({
|
|||||||
DATABASE_CONNECT_TIMEOUT_MS: z.coerce.number().int().positive().default(10_000),
|
DATABASE_CONNECT_TIMEOUT_MS: z.coerce.number().int().positive().default(10_000),
|
||||||
HOTEL_NAME: z.string().default("Atom"),
|
HOTEL_NAME: z.string().default("Atom"),
|
||||||
APP_URL: z.string().url().default("http://localhost:3000"),
|
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 (password reset / notifications). Email features no-op if unset.
|
||||||
SMTP_HOST: z.string().optional(),
|
SMTP_HOST: z.string().optional(),
|
||||||
SMTP_PORT: z.coerce.number().int().positive().optional(),
|
SMTP_PORT: z.coerce.number().int().positive().optional(),
|
||||||
|
|||||||
+31
-14
@@ -1,7 +1,9 @@
|
|||||||
import nodemailer, { type Transporter } from "nodemailer";
|
import nodemailer, { type Transporter } from "nodemailer";
|
||||||
|
import { Resend } from "resend";
|
||||||
import { env } from "@/env";
|
import { env } from "@/env";
|
||||||
|
|
||||||
let transporter: Transporter | null = null;
|
let transporter: Transporter | null = null;
|
||||||
|
let resend: Resend | null = null;
|
||||||
|
|
||||||
function getTransport(): Transporter | null {
|
function getTransport(): Transporter | null {
|
||||||
if (!env.SMTP_HOST) return null;
|
if (!env.SMTP_HOST) return null;
|
||||||
@@ -16,23 +18,38 @@ function getTransport(): Transporter | null {
|
|||||||
return transporter;
|
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> {
|
export async function sendMail(to: string, subject: string, html: string): Promise<boolean> {
|
||||||
const t = getTransport();
|
const from = env.SMTP_FROM ?? `no-reply@${env.HOTEL_NAME}`;
|
||||||
if (!t) {
|
|
||||||
console.warn("[email] SMTP not configured — skipping mail to", to);
|
const r = getResend();
|
||||||
return false;
|
if (r) {
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
await t.sendMail({
|
await r.emails.send({ from, to, subject, html });
|
||||||
from: env.SMTP_FROM ?? `no-reply@${env.HOTEL_NAME}`,
|
|
||||||
to,
|
|
||||||
subject,
|
|
||||||
html,
|
|
||||||
});
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[email] send failed:", (e as Error).message);
|
console.error("[email] Resend failed:", (e as Error).message);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = getTransport();
|
||||||
|
if (t) {
|
||||||
|
try {
|
||||||
|
await t.sendMail({ from, to, subject, html });
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[email] SMTP failed:", (e as Error).message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!r && !t) {
|
||||||
|
console.warn("[email] No mailer configured — set RESEND_API_KEY or SMTP_HOST");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user