@@ -430,32 +430,39 @@ export class AuthManager {
430430 sendResetPassword : async ( { user, url, token } : { user : { id : string ; email : string ; name ?: string } ; url : string ; token : string } ) => {
431431 const email = this . getEmailService ( ) ;
432432 if ( ! email ) {
433- console . warn (
434- `[AuthManager] Password-reset requested for ${ user . email } but no email service is wired. URL: ${ url } ` ,
433+ // No transport wired but password reset is enabled — a
434+ // misconfiguration. THROW (don't silently drop): better-auth
435+ // invokes this via `runInBackgroundOrAwait` and the forget-password
436+ // route always returns `{status:true}`, so this never leaks whether
437+ // an address exists AND never turns the request into a 500 — it just
438+ // surfaces the failure in the logs instead of vanishing.
439+ throw new Error (
440+ `Password-reset email could not be sent to ${ user . email } : no email service is configured for this deployment.` ,
435441 ) ;
436- return ;
437442 }
438443 const ttlSec = this . config . emailAndPassword ?. resetPasswordTokenExpiresIn ?? 60 * 60 ;
439- try {
440- await email . sendTemplate ( {
441- template : 'auth.password_reset' ,
442- to : { address : user . email , ...( user . name ? { name : user . name } : { } ) } ,
443- data : {
444- user : { name : user . name || user . email , email : user . email , id : user . id } ,
445- resetUrl : url ,
446- token,
447- expiresInMinutes : Math . round ( ttlSec / 60 ) ,
448- appName : this . getAppName ( ) ,
449- } ,
450- relatedObject : 'sys_user' ,
451- relatedId : user . id ,
452- } ) ;
453- } catch ( err : any ) {
454- // Do NOT rethrow: the user account exists; an email-transport failure
455- // (missing template, bad credentials, network blip) must not turn
456- // the user-facing reset request into a 500. The user can retry via
457- // the "forgot password" flow.
458- console . error ( `[AuthManager] sendResetPassword failed (swallowed): ${ err ?. message ?? err } ` ) ;
444+ // Surface both template-resolution throws and transport failures
445+ // (status:'failed'); resilience is preserved by better-auth's
446+ // background-task handling (see sendVerificationEmail) and the
447+ // forget-password route always returns {status:true}, so this never
448+ // leaks whether an address exists nor turns the request into a 500.
449+ const result = await email . sendTemplate ( {
450+ template : 'auth.password_reset' ,
451+ to : { address : user . email , ...( user . name ? { name : user . name } : { } ) } ,
452+ data : {
453+ user : { name : user . name || user . email , email : user . email , id : user . id } ,
454+ resetUrl : url ,
455+ token,
456+ expiresInMinutes : Math . round ( ttlSec / 60 ) ,
457+ appName : this . getAppName ( ) ,
458+ } ,
459+ relatedObject : 'sys_user' ,
460+ relatedId : user . id ,
461+ } ) ;
462+ if ( result ?. status === 'failed' ) {
463+ throw new Error (
464+ `Password-reset email could not be sent to ${ user . email } : ${ result . error ?? 'delivery failed' } ` ,
465+ ) ;
459466 }
460467 } ,
461468 } ;
@@ -475,31 +482,43 @@ export class AuthManager {
475482 sendVerificationEmail : async ( { user, url, token } : { user : { id : string ; email : string ; name ?: string } ; url : string ; token : string } ) => {
476483 const email = this . getEmailService ( ) ;
477484 if ( ! email ) {
478- console . warn (
479- `[AuthManager] Verification email requested for ${ user . email } but no email service is wired. URL: ${ url } ` ,
485+ // Verification is enabled (this callback only exists when it is)
486+ // but no email transport is wired — a misconfiguration, not a
487+ // transient blip. THROW so the explicit `/send-verification-email`
488+ // resend endpoint (which awaits this) surfaces a real error
489+ // instead of a false "email sent" success. Sign-up stays
490+ // resilient regardless: better-auth runs the sendOnSignUp call
491+ // through `runInBackgroundOrAwait`, which logs (never rethrows)
492+ // a failure, so the account is still created and the user lands
493+ // on the verify screen (where an honest resend now reports the
494+ // problem). Previously this was swallowed, leaving every user
495+ // permanently stuck with no signal and no resend that could work.
496+ throw new Error (
497+ `Verification email could not be sent to ${ user . email } : no email service is configured for this deployment.` ,
480498 ) ;
481- return ;
482499 }
483500 const ttlSec = this . config . emailVerification ?. expiresIn ?? 60 * 60 ;
484- try {
485- await email . sendTemplate ( {
486- template : 'auth.verify_email' ,
487- to : { address : user . email , ...( user . name ? { name : user . name } : { } ) } ,
488- data : {
489- user : { name : user . name || user . email , email : user . email , id : user . id } ,
490- verificationUrl : url ,
491- token,
492- expiresInMinutes : Math . round ( ttlSec / 60 ) ,
493- appName : this . getAppName ( ) ,
494- } ,
495- relatedObject : 'sys_user' ,
496- relatedId : user . id ,
497- } ) ;
498- } catch ( err : any ) {
499- // Do NOT rethrow: the user account exists; an email-transport
500- // failure must not turn signup or /send-verification-email into
501- // a 500. The "Resend verification email" UI lets the user retry.
502- console . error ( `[AuthManager] sendVerificationEmail failed (swallowed): ${ err ?. message ?? err } ` ) ;
501+ // Let send failures propagate (see above): sendTemplate THROWS on
502+ // template/loader errors, and returns status:'failed' on transport
503+ // errors — surface both so resend is honest and signup stays
504+ // resilient via better-auth's background-task error handling.
505+ const result = await email . sendTemplate ( {
506+ template : 'auth.verify_email' ,
507+ to : { address : user . email , ...( user . name ? { name : user . name } : { } ) } ,
508+ data : {
509+ user : { name : user . name || user . email , email : user . email , id : user . id } ,
510+ verificationUrl : url ,
511+ token,
512+ expiresInMinutes : Math . round ( ttlSec / 60 ) ,
513+ appName : this . getAppName ( ) ,
514+ } ,
515+ relatedObject : 'sys_user' ,
516+ relatedId : user . id ,
517+ } ) ;
518+ if ( result ?. status === 'failed' ) {
519+ throw new Error (
520+ `Verification email could not be sent to ${ user . email } : ${ result . error ?? 'delivery failed' } ` ,
521+ ) ;
503522 }
504523 } ,
505524 } ,
0 commit comments