@@ -7,6 +7,14 @@ interface SmtpConfig {
77 pass : string ;
88 from : string ;
99 secure : boolean ;
10+ connectionTimeout : number ;
11+ socketTimeout : number ;
12+ greetingTimeout : number ;
13+ pool : boolean ;
14+ maxConnections : number ;
15+ maxMessages : number ;
16+ requireTLS : boolean ;
17+ rejectUnauthorized : boolean ;
1018}
1119
1220function getConfig ( ) : SmtpConfig {
@@ -20,7 +28,70 @@ function getConfig(): SmtpConfig {
2028 'SMTP configuration is missing. Set SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_FROM' ,
2129 ) ;
2230 }
23- return { host, port, user, pass, from, secure : port === 465 } ;
31+ const num = ( value : string | undefined , fallback : number ) => {
32+ const parsed = Number ( value ) ;
33+ return Number . isFinite ( parsed ) && parsed > 0 ? parsed : fallback ;
34+ } ;
35+ const bool = ( value : string | undefined , fallback : boolean ) => {
36+ if ( value === undefined ) return fallback ;
37+ return [ '1' , 'true' , 'yes' , 'on' ] . includes ( value . toLowerCase ( ) ) ;
38+ } ;
39+ return {
40+ host,
41+ port,
42+ user,
43+ pass,
44+ from,
45+ secure : port === 465 ,
46+ connectionTimeout : num ( process . env . SMTP_CONNECTION_TIMEOUT , 10000 ) ,
47+ socketTimeout : num ( process . env . SMTP_SOCKET_TIMEOUT , 10000 ) ,
48+ greetingTimeout : num ( process . env . SMTP_GREETING_TIMEOUT , 10000 ) ,
49+ pool : bool ( process . env . SMTP_POOL , true ) ,
50+ maxConnections : num ( process . env . SMTP_MAX_CONNECTIONS , 1 ) ,
51+ maxMessages : num ( process . env . SMTP_MAX_MESSAGES , 20 ) ,
52+ requireTLS : bool ( process . env . SMTP_REQUIRE_TLS , false ) ,
53+ rejectUnauthorized : bool ( process . env . SMTP_TLS_REJECT_UNAUTHORIZED , true ) ,
54+ } ;
55+ }
56+
57+ function maskEmail ( email : string ) {
58+ const [ local , domain ] = email . split ( '@' ) ;
59+ if ( ! domain ) return email ;
60+ const visible = local . slice ( 0 , 2 ) ;
61+ return `${ visible } ${ local . length > 2 ? '***' : '' } @${ domain } ` ;
62+ }
63+
64+ function describeSmtpError ( err : unknown ) {
65+ if ( ! err || typeof err !== 'object' ) {
66+ return {
67+ logMessage : 'Unknown SMTP error' ,
68+ userMessage : 'No se pudo enviar el correo (error desconocido).' ,
69+ } ;
70+ }
71+ const error = err as { code ?: string ; command ?: string ; responseCode ?: number ; message ?: string } ;
72+ if ( error . code === 'ETIMEDOUT' ) {
73+ return {
74+ logMessage : 'SMTP connection timed out' ,
75+ userMessage : 'No se pudo contactar con el servidor SMTP (timeout).' ,
76+ } ;
77+ }
78+ if ( error . code === 'EAUTH' ) {
79+ return {
80+ logMessage : 'SMTP authentication failed' ,
81+ userMessage : 'Credenciales SMTP inválidas.' ,
82+ } ;
83+ }
84+ if ( error . code === 'ECONNECTION' ) {
85+ return {
86+ logMessage : 'SMTP connection refused' ,
87+ userMessage : 'El servidor SMTP rechazó la conexión.' ,
88+ } ;
89+ }
90+ const base = error . message || 'Error SMTP no especificado.' ;
91+ return {
92+ logMessage : base ,
93+ userMessage : base ,
94+ } ;
2495}
2596
2697export async function sendOtpEmail ( to : string , code : string ) {
@@ -33,15 +104,41 @@ export async function sendOtpEmail(to: string, code: string) {
33104 user : cfg . user ,
34105 pass : cfg . pass ,
35106 } ,
107+ pool : cfg . pool ,
108+ maxConnections : cfg . maxConnections ,
109+ maxMessages : cfg . maxMessages ,
110+ connectionTimeout : cfg . connectionTimeout ,
111+ socketTimeout : cfg . socketTimeout ,
112+ greetingTimeout : cfg . greetingTimeout ,
113+ requireTLS : cfg . requireTLS ,
114+ tls : {
115+ rejectUnauthorized : cfg . rejectUnauthorized ,
116+ } ,
36117 } ) ;
37118
38119 const text = `Tu código de verificación es: ${ code } ` ;
39120 const subject = 'Código de verificación' ;
121+ const startedAt = Date . now ( ) ;
40122
41- await transporter . sendMail ( {
42- from : cfg . from ,
43- to,
44- subject,
45- text,
46- } ) ;
123+ try {
124+ const info = await transporter . sendMail ( {
125+ from : cfg . from ,
126+ to,
127+ subject,
128+ text,
129+ } ) ;
130+ console . info (
131+ `[mailer] OTP sent to ${ maskEmail ( to ) } via ${ cfg . host } :${ cfg . port } in ${ Date . now ( ) - startedAt } ms (id=${ info . messageId } )` ,
132+ ) ;
133+ return info ;
134+ } catch ( error ) {
135+ const { logMessage, userMessage } = describeSmtpError ( error ) ;
136+ console . error (
137+ `[mailer] Failed to send OTP to ${ maskEmail ( to ) } via ${ cfg . host } :${ cfg . port } after ${ Date . now ( ) - startedAt } ms - ${ logMessage } ` ,
138+ error ,
139+ ) ;
140+ const wrapped = new Error ( userMessage ) ;
141+ ( wrapped as any ) . cause = error ;
142+ throw wrapped ;
143+ }
47144}
0 commit comments