@runnerpro/backend 1.23.0 → 1.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,18 +14,40 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.sendMail = void 0;
16
16
  const nodemailer_1 = __importDefault(require("nodemailer"));
17
+ /*
18
+ Transporter con POOL.
19
+
20
+ Sin `pool`, nodemailer abre conexión + AUTH nuevos en CADA sendMail. Un cron que
21
+ envía cientos de correos seguidos encadena cientos de logins contra Gmail y acaba
22
+ en `454-4.7.0 Too many login attempts`, que además deja la cuenta bloqueada un rato
23
+ y tumba también a los demás correos de esa ventana (incidente T-172:
24
+ sendWorkoutsByMail, 728 correos de golpe, caído todos los domingos desde el
25
+ 05/07/2026).
26
+
27
+ Con pool la conexión se reutiliza: un solo AUTH para toda la tanda.
28
+ - maxConnections 1: una sola conexión viva, así el número de logins no depende del
29
+ número de correos.
30
+ - maxMessages 100: Gmail corta la sesión por número de mensajes; al llegar al tope
31
+ nodemailer reconecta solo (728 correos ≈ 8 logins, no 728).
32
+ - rateDelta/rateLimit: como mucho 2 correos por segundo, para no disparar tampoco
33
+ los límites por volumen.
34
+ */
17
35
  const transporter = nodemailer_1.default.createTransport({
18
36
  service: 'gmail',
19
37
  auth: {
20
38
  user: process.env.GMAIL_EMAIL_USER,
21
39
  pass: process.env.GMAIL_EMAIL_PWD,
22
40
  },
41
+ pool: true,
42
+ maxConnections: 1,
43
+ maxMessages: 100,
44
+ rateDelta: 1000,
45
+ rateLimit: 2,
23
46
  connectionTimeout: 10000,
24
47
  greetingTimeout: 10000,
25
48
  socketTimeout: 20000,
26
49
  });
27
50
  const sendMail = ({ subject, title, body, to, link, attachments, bcc, signoff }) => __awaiter(void 0, void 0, void 0, function* () {
28
- var _a;
29
51
  const toMapped = process.env.NODE_ENV === 'PROD' ? to.map((correo) => correo).join(',') : process.env.GMAIL_EMAIL_USER;
30
52
  if ((toMapped === null || toMapped === void 0 ? void 0 : toMapped.length) === 0)
31
53
  return;
@@ -39,29 +61,28 @@ const sendMail = ({ subject, title, body, to, link, attachments, bcc, signoff })
39
61
  html: bodyHTML,
40
62
  attachments: attachments || [],
41
63
  });
42
- const transport = (_a = transporter.transporter) !== null && _a !== void 0 ? _a : transporter;
43
- const errorPromise = new Promise((_, reject) => {
44
- const onError = (err) => {
45
- transport.removeListener('error', onError);
46
- transport.removeListener('close', onClose);
47
- reject(err);
48
- };
49
- const onClose = () => {
50
- transport.removeListener('error', onError);
51
- transport.removeListener('close', onClose);
52
- reject(new Error('SMTP connection closed'));
53
- };
54
- transport.once('error', onError);
55
- transport.once('close', onClose);
56
- sendPromise.finally(() => {
57
- transport.removeListener('error', onError);
58
- transport.removeListener('close', onClose);
59
- });
60
- });
64
+ /*
65
+ Antes había aquí una red de seguridad que escuchaba 'error'/'close' del transporte
66
+ para no quedarse colgado si el socket moría. Se quita porque:
67
+
68
+ - Con `pool`, el SMTPPool solo emite 'idle': los fallos de conexión los devuelve por
69
+ el callback del propio mensaje, así que `sendPromise` ya se resuelve o rechaza
70
+ siempre; el timeout de abajo sigue siendo el tope duro.
71
+ - Aquel código creaba una cadena huérfana (`sendPromise.finally(...)`) que heredaba
72
+ el rechazo de `sendPromise` y no la escuchaba nadie: CADA envío fallido dejaba un
73
+ `[FATAL] Unhandled Rejection` en el log, que es justo lo que se veía en el
74
+ incidente T-172 junto al `454-4.7.0 Too many login attempts`.
75
+ */
76
+ let timeoutId;
61
77
  const timeoutPromise = new Promise((_, reject) => {
62
- setTimeout(() => reject(new Error('sendMail timeout')), 30000);
78
+ timeoutId = setTimeout(() => reject(new Error('sendMail timeout')), 30000);
63
79
  });
64
- return Promise.race([sendPromise, errorPromise, timeoutPromise]);
80
+ try {
81
+ return yield Promise.race([sendPromise, timeoutPromise]);
82
+ }
83
+ finally {
84
+ clearTimeout(timeoutId);
85
+ }
65
86
  });
66
87
  exports.sendMail = sendMail;
67
88
  function getBodyHTML(title, body, link, signoff) {
@@ -21,6 +21,6 @@ interface Mail {
21
21
  */
22
22
  signoff?: string;
23
23
  }
24
- declare const sendMail: ({ subject, title, body, to, link, attachments, bcc, signoff }: Mail) => Promise<import("nodemailer/lib/smtp-transport").SentMessageInfo>;
24
+ declare const sendMail: ({ subject, title, body, to, link, attachments, bcc, signoff }: Mail) => Promise<import("nodemailer/lib/smtp-pool").SentMessageInfo>;
25
25
  export { sendMail };
26
26
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/sendMail/index.ts"],"names":[],"mappings":"AAaA,UAAU,IAAI;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,IAAI,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACzD,WAAW,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjE,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,QAAA,MAAM,QAAQ,kEAAyE,IAAI,qEAsC1F,CAAC;AAiEF,OAAO,EAAE,QAAQ,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/sendMail/index.ts"],"names":[],"mappings":"AAoCA,UAAU,IAAI;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,IAAI,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACzD,WAAW,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjE,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,QAAA,MAAM,QAAQ,kEAAyE,IAAI,gEAmC1F,CAAC;AAiEF,OAAO,EAAE,QAAQ,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runnerpro/backend",
3
- "version": "1.23.0",
3
+ "version": "1.23.1",
4
4
  "description": "A collection of common backend functions",
5
5
  "exports": {
6
6
  ".": "./lib/cjs/index.js"