@@ -27,6 +27,40 @@ function getSmtpTransporter() {
2727 return null ;
2828}
2929
30+ export function getBaseUrl ( req ?: Request ) : string {
31+ // 1. Explicit environment variable configured by user
32+ if ( process . env . NEXT_PUBLIC_APP_URL && ! process . env . NEXT_PUBLIC_APP_URL . includes ( "localhost" ) ) {
33+ return process . env . NEXT_PUBLIC_APP_URL . replace ( / \/ $ / , "" ) ;
34+ }
35+
36+ // 2. Request context (Headers from incoming HTTP request)
37+ if ( req ) {
38+ try {
39+ const proto = req . headers . get ( "x-forwarded-proto" ) || "https" ;
40+ const host = req . headers . get ( "x-forwarded-host" ) || req . headers . get ( "host" ) ;
41+ if ( host && ! host . includes ( "localhost" ) ) {
42+ return `${ proto } ://${ host } ` . replace ( / \/ $ / , "" ) ;
43+ }
44+ } catch { }
45+ }
46+
47+ // 3. Vercel Production / Deployment URLs
48+ if ( process . env . VERCEL_PROJECT_PRODUCTION_URL ) {
49+ return `https://${ process . env . VERCEL_PROJECT_PRODUCTION_URL } ` . replace ( / \/ $ / , "" ) ;
50+ }
51+ if ( process . env . VERCEL_URL ) {
52+ return `https://${ process . env . VERCEL_URL } ` . replace ( / \/ $ / , "" ) ;
53+ }
54+
55+ // 4. Fallback default production domain
56+ if ( process . env . NODE_ENV === "production" ) {
57+ return "https://client-echo-web.vercel.app" ;
58+ }
59+
60+ // 5. Development localhost fallback
61+ return process . env . NEXT_PUBLIC_APP_URL || "http://localhost:3000" ;
62+ }
63+
3064export function getFromAddress ( ) : string {
3165 const gmailUser = process . env . GMAIL_USER || process . env . SMTP_USER ;
3266 if ( gmailUser ) {
@@ -138,8 +172,9 @@ export async function sendMagicLinkApprovalEmail(params: {
138172 replyToEmail ?: string ;
139173 rawToken : string ;
140174 promptMessage ?: string ;
175+ appUrl ?: string ;
141176} ) : Promise < { success : boolean ; error ?: string } > {
142- const appUrl = process . env . NEXT_PUBLIC_APP_URL || "http://localhost:3000" ;
177+ const appUrl = params . appUrl || getBaseUrl ( ) ;
143178 const approvalUrl = `${ appUrl } /approve-testimonial?token=${ encodeURIComponent ( params . rawToken ) } ` ;
144179 const replyTo = params . replyToEmail || params . creatorEmail ;
145180
@@ -325,8 +360,9 @@ export async function sendSupportEmail(params: {
325360export async function sendPasswordResetEmail ( params : {
326361 toEmail : string ;
327362 rawToken : string ;
363+ appUrl ?: string ;
328364} ) : Promise < { success : boolean ; error ?: string } > {
329- const appUrl = process . env . NEXT_PUBLIC_APP_URL || "http://localhost:3000" ;
365+ const appUrl = params . appUrl || getBaseUrl ( ) ;
330366 const resetUrl = `${ appUrl } /reset-password?token=${ encodeURIComponent ( params . rawToken ) } ` ;
331367
332368 return sendEmailMessage ( {
@@ -354,8 +390,9 @@ export async function sendPasswordResetEmail(params: {
354390export async function sendEmailVerificationLink ( params : {
355391 toEmail : string ;
356392 rawToken : string ;
393+ appUrl ?: string ;
357394} ) : Promise < { success : boolean ; error ?: string } > {
358- const appUrl = process . env . NEXT_PUBLIC_APP_URL || "http://localhost:3000" ;
395+ const appUrl = params . appUrl || getBaseUrl ( ) ;
359396 const verifyUrl = `${ appUrl } /api/auth/verify-email?token=${ encodeURIComponent ( params . rawToken ) } ` ;
360397
361398 return sendEmailMessage ( {
0 commit comments