From 31b4a9480a495871b407c5882a756ccc7b992b69 Mon Sep 17 00:00:00 2001 From: Mannu Date: Sun, 24 May 2026 16:01:55 +0530 Subject: [PATCH] fix(email): use tia@tia.manohargupta.com + auto-fallback to shared domain - Primary sender: tia@tia.manohargupta.com (verified subdomain in Resend) - sendWithFallback() retries with onboarding@resend.dev if Resend rejects the primary domain (covers the window while SPF is still propagating) - Both sendFamilyInviteEmail() and sendVerificationEmail() use the fallback Update EMAIL_FROM in Dokploy to: Tia Co-Authored-By: Claude Sonnet 4.6 --- src/lib/email.ts | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/lib/email.ts b/src/lib/email.ts index 1bcce9f..c55301d 100644 --- a/src/lib/email.ts +++ b/src/lib/email.ts @@ -1,10 +1,28 @@ import { Resend } from "resend"; const RESEND_API_KEY = process.env.RESEND_API_KEY; -// Falls back to Resend's shared test domain if EMAIL_FROM env var not set or domain not verified yet -const EMAIL_FROM = process.env.EMAIL_FROM || "Tia "; +// Primary: verified subdomain (set EMAIL_FROM=Tia in Dokploy) +// Fallback: Resend shared domain — works without domain verification +const EMAIL_FROM = process.env.EMAIL_FROM || "Tia "; +const EMAIL_FROM_FALLBACK = "Tia "; const APP_URL = process.env.NEXT_PUBLIC_APP_URL || "https://tia.manohargupta.com"; +/** Send email with automatic fallback to shared domain if primary domain isn't verified yet */ +async function sendWithFallback(resend: Resend, payload: Parameters[0]) { + const result = await resend.emails.send(payload); + // Resend returns { error } object rather than throwing for domain issues + const err = (result as { error?: { message?: string; name?: string } }).error; + if (err) { + const msg = (err.message || err.name || "").toLowerCase(); + if (msg.includes("domain") || msg.includes("verif") || msg.includes("sender")) { + console.warn("[EMAIL-FALLBACK] Primary domain rejected, retrying with shared domain:", err.message); + return await resend.emails.send({ ...payload, from: EMAIL_FROM_FALLBACK }); + } + throw new Error(err.message || "Resend error"); + } + return result; +} + /** Sends a family invite email, or logs the link in dev. */ export async function sendFamilyInviteEmail(opts: { to: string; @@ -24,7 +42,7 @@ export async function sendFamilyInviteEmail(opts: { try { const resend = new Resend(RESEND_API_KEY); - await resend.emails.send({ + await sendWithFallback(resend, { from: EMAIL_FROM, to, subject: `${inviterName} invited you to join their family on Tia 🍼`, @@ -63,7 +81,7 @@ export async function sendVerificationEmail(email: string, token: string, userId try { const resend = new Resend(RESEND_API_KEY); - await resend.emails.send({ + await sendWithFallback(resend, { from: EMAIL_FROM, to: email, subject: "Verify your Tia email",