1- import { Resend } from "resend" ;
21import nodemailer from "nodemailer" ;
32import { logger } from "@/lib/logger" ;
43
5- const resendApiKey = process . env . RESEND_API_KEY ;
6- export const resend = resendApiKey ? new Resend ( resendApiKey ) : null ;
7-
84function getSmtpTransporter ( ) {
95 const rawUser = process . env . GMAIL_USER || process . env . SMTP_USER ;
106 const rawPass = process . env . GMAIL_APP_PASSWORD || process . env . SMTP_PASS || process . env . SMTP_PASSWORD ;
@@ -19,9 +15,9 @@ function getSmtpTransporter() {
1915 tls : {
2016 rejectUnauthorized : false ,
2117 } ,
22- connectionTimeout : 5000 , // 5s connection timeout to avoid hanging
23- greetingTimeout : 4000 , // 4s greeting timeout
24- socketTimeout : 6000 , // 6s socket timeout
18+ connectionTimeout : 8000 , // 8s connection timeout to avoid hanging
19+ greetingTimeout : 5000 , // 5s greeting timeout
20+ socketTimeout : 8000 , // 8s socket timeout
2521 } ) ;
2622 }
2723 return null ;
@@ -64,31 +60,19 @@ export function getBaseUrl(req?: Request): string {
6460 }
6561
6662 // 5. Default Canonical Production Domain Fallback
67- // Transactional links (Magic Links, Approval Links, Password Resets) sent to external
68- // clients/users must ALWAYS use a publicly reachable domain rather than localhost.
6963 return CANONICAL_APP_URL ;
7064}
7165
7266export function getFromAddress ( senderName ?: string ) : string {
7367 const rawUser = process . env . GMAIL_USER || process . env . SMTP_USER ;
74- const gmailUser = rawUser ? rawUser . replace ( / [ ' " ] / g, "" ) . trim ( ) : null ;
68+ const gmailUser = rawUser ? rawUser . replace ( / [ ' " ] / g, "" ) . trim ( ) : "ClientEcho.web@gmail.com" ;
7569 const displayName = senderName ? `${ senderName } via ClientEcho` : "ClientEcho" ;
76- if ( gmailUser ) {
77- return `${ displayName } <${ gmailUser } >` ;
78- }
79- const envFrom = ( process . env . EMAIL_FROM || process . env . RESEND_FROM_ADDRESS || "" ) . replace ( / [ ' " ] / g, "" ) . trim ( ) ;
80- if ( envFrom && ! envFrom . includes ( "@clientecho.com" ) ) {
81- return envFrom ;
82- }
83- // Default to Resend testing domain if no Gmail SMTP is configured
84- return `${ displayName } <onboarding@resend.dev>` ;
70+ return `${ displayName } <${ gmailUser } >` ;
8571}
8672
8773/**
88- * Universal email dispatcher: routes through Gmail SMTP if configured,
89- * otherwise falls back to Resend API.
74+ * Universal email dispatcher: routes through Gmail SMTP with verified credentials.
9075 * Uses strict timeouts to guarantee non-blocking execution.
91- * Avoids artificial spammy priority headers to ensure natural Primary Inbox delivery.
9276 */
9377async function sendEmailMessage ( options : {
9478 to : string ;
@@ -102,7 +86,6 @@ async function sendEmailMessage(options: {
10286 const fromAddress = options . from || getFromAddress ( ) ;
10387 const transporter = getSmtpTransporter ( ) ;
10488
105- // 1. Send via Gmail SMTP if configured (Primary Inbox Delivery)
10689 if ( transporter ) {
10790 try {
10891 const sendPromise = transporter . sendMail ( {
@@ -112,11 +95,14 @@ async function sendEmailMessage(options: {
11295 text : options . text ,
11396 html : options . html ,
11497 replyTo : options . replyTo ,
115- headers : options . headers ,
98+ headers : {
99+ "X-Auto-Response-Suppress" : "OOF, AutoReply" ,
100+ ...options . headers ,
101+ } ,
116102 } ) ;
117103
118104 const timeoutPromise = new Promise < { timeout : true } > ( ( resolve ) =>
119- setTimeout ( ( ) => resolve ( { timeout : true } ) , 6000 )
105+ setTimeout ( ( ) => resolve ( { timeout : true } ) , 8000 )
120106 ) ;
121107
122108 const result = await Promise . race ( [ sendPromise , timeoutPromise ] ) ;
@@ -133,38 +119,7 @@ async function sendEmailMessage(options: {
133119 }
134120 }
135121
136- // 2. Send via Resend API
137- if ( resend ) {
138- try {
139- const sendPromise = resend . emails . send ( {
140- from : fromAddress ,
141- to : options . to ,
142- subject : options . subject ,
143- text : options . text ,
144- html : options . html ,
145- replyTo : options . replyTo ,
146- headers : options . headers ,
147- } ) ;
148-
149- const timeoutPromise = new Promise < { timeout : true } > ( ( resolve ) =>
150- setTimeout ( ( ) => resolve ( { timeout : true } ) , 6000 )
151- ) ;
152-
153- const result = await Promise . race ( [ sendPromise , timeoutPromise ] ) ;
154- if ( result && "timeout" in result ) {
155- logger . error ( `[RESEND_TIMEOUT] Timeout while sending email via Resend to ${ options . to } ` ) ;
156- return { success : false , error : "Resend API request timed out" } ;
157- }
158-
159- return { success : true } ;
160- } catch ( err : any ) {
161- const errMsg = err ?. message || String ( err ) ;
162- logger . error ( `Failed to send email via Resend` , err , { recipient : options . to } ) ;
163- return { success : false , error : errMsg || "Failed to send email" } ;
164- }
165- }
166-
167- // 3. Local / Dev fallback logger
122+ // Local / Dev fallback logger if SMTP credentials not provided
168123 logger . info ( `[DEV / TEST] Email logged for ${ options . to } : [${ options . subject } ]` ) ;
169124 return { success : true } ;
170125}
0 commit comments