@mavogel/awscdk-rootmail 0.1.7 → 0.1.9

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.
Files changed (167) hide show
  1. package/.jsii +10 -10
  2. package/lib/rootmail.js +1 -1
  3. package/lib/ses-receive.js +1 -1
  4. package/node_modules/@aws-sdk/client-cloudwatch-logs/dist-cjs/index.js +5 -5
  5. package/node_modules/@aws-sdk/client-cloudwatch-logs/dist-es/schemas/schemas_0.js +4 -4
  6. package/node_modules/@aws-sdk/client-cloudwatch-logs/dist-types/commands/DescribeDeliveryDestinationsCommand.d.ts +1 -0
  7. package/node_modules/@aws-sdk/client-cloudwatch-logs/dist-types/commands/GetDeliveryDestinationCommand.d.ts +1 -0
  8. package/node_modules/@aws-sdk/client-cloudwatch-logs/dist-types/commands/PutAccountPolicyCommand.d.ts +4 -1
  9. package/node_modules/@aws-sdk/client-cloudwatch-logs/dist-types/commands/PutDeliveryDestinationCommand.d.ts +2 -0
  10. package/node_modules/@aws-sdk/client-cloudwatch-logs/dist-types/models/models_0.d.ts +80 -36
  11. package/node_modules/@aws-sdk/client-cloudwatch-logs/dist-types/ts3.4/models/models_0.d.ts +2 -0
  12. package/node_modules/@aws-sdk/client-cloudwatch-logs/package.json +1 -1
  13. package/node_modules/@aws-sdk/client-route-53/dist-cjs/index.js +1 -1
  14. package/node_modules/@aws-sdk/client-route-53/package.json +1 -1
  15. package/node_modules/@aws-sdk/client-s3/dist-cjs/index.js +2 -1
  16. package/node_modules/@aws-sdk/client-s3/dist-es/models/enums.js +1 -0
  17. package/node_modules/@aws-sdk/client-s3/dist-types/commands/GetBucketInventoryConfigurationCommand.d.ts +1 -1
  18. package/node_modules/@aws-sdk/client-s3/dist-types/commands/ListBucketInventoryConfigurationsCommand.d.ts +1 -1
  19. package/node_modules/@aws-sdk/client-s3/dist-types/commands/PutBucketInventoryConfigurationCommand.d.ts +1 -1
  20. package/node_modules/@aws-sdk/client-s3/dist-types/models/enums.d.ts +1 -0
  21. package/node_modules/@aws-sdk/client-s3/dist-types/ts3.4/models/enums.d.ts +1 -0
  22. package/node_modules/@aws-sdk/client-s3/package.json +2 -2
  23. package/node_modules/@aws-sdk/client-ses/dist-cjs/index.js +1 -1
  24. package/node_modules/@aws-sdk/client-ses/package.json +1 -1
  25. package/node_modules/@aws-sdk/client-ssm/dist-cjs/index.js +9 -3
  26. package/node_modules/@aws-sdk/client-ssm/dist-es/models/enums.js +4 -0
  27. package/node_modules/@aws-sdk/client-ssm/dist-es/schemas/schemas_0.js +3 -2
  28. package/node_modules/@aws-sdk/client-ssm/dist-types/commands/DeleteResourcePolicyCommand.d.ts +6 -0
  29. package/node_modules/@aws-sdk/client-ssm/dist-types/commands/PutResourcePolicyCommand.d.ts +12 -0
  30. package/node_modules/@aws-sdk/client-ssm/dist-types/models/enums.d.ts +12 -0
  31. package/node_modules/@aws-sdk/client-ssm/dist-types/models/models_0.d.ts +20 -1
  32. package/node_modules/@aws-sdk/client-ssm/dist-types/models/models_1.d.ts +2 -2
  33. package/node_modules/@aws-sdk/client-ssm/dist-types/ts3.4/models/enums.d.ts +5 -0
  34. package/node_modules/@aws-sdk/client-ssm/dist-types/ts3.4/models/models_0.d.ts +2 -0
  35. package/node_modules/@aws-sdk/client-ssm/package.json +1 -1
  36. package/node_modules/@zone-eu/mailsplit/lib/flowed-decoder.js +1 -5
  37. package/node_modules/@zone-eu/mailsplit/lib/headers.js +35 -25
  38. package/node_modules/@zone-eu/mailsplit/lib/message-splitter.js +126 -79
  39. package/node_modules/@zone-eu/mailsplit/lib/mime-node.js +27 -5
  40. package/node_modules/@zone-eu/mailsplit/lib/node-rewriter.js +3 -2
  41. package/node_modules/@zone-eu/mailsplit/lib/types.d.ts +0 -3
  42. package/node_modules/@zone-eu/mailsplit/package.json +5 -5
  43. package/node_modules/libbase64/.github/workflows/release.yaml +12 -10
  44. package/node_modules/libbase64/.ncurc.js +7 -1
  45. package/node_modules/libbase64/CHANGELOG.md +7 -0
  46. package/node_modules/libbase64/lib/libbase64.js +27 -0
  47. package/node_modules/libbase64/package.json +4 -4
  48. package/node_modules/libmime/CHANGELOG.md +14 -0
  49. package/node_modules/libmime/lib/libmime.js +41 -21
  50. package/node_modules/libmime/package.json +3 -3
  51. package/node_modules/libqp/.github/workflows/release.yaml +12 -10
  52. package/node_modules/libqp/CHANGELOG.md +7 -0
  53. package/node_modules/libqp/lib/libqp.js +6 -4
  54. package/node_modules/libqp/package.json +2 -2
  55. package/node_modules/libqp/test/libqp-test.js +71 -14
  56. package/node_modules/mailparser/.release-please-manifest.json +1 -1
  57. package/node_modules/mailparser/CHANGELOG.md +42 -0
  58. package/node_modules/mailparser/README.md +5 -0
  59. package/node_modules/mailparser/lib/mail-parser.js +107 -32
  60. package/node_modules/mailparser/lib/simple-parser.js +12 -0
  61. package/node_modules/mailparser/lib/stream-hash.js +1 -1
  62. package/node_modules/mailparser/package.json +6 -6
  63. package/node_modules/nodemailer/CHANGELOG.md +38 -0
  64. package/node_modules/nodemailer/README.md +1 -1
  65. package/node_modules/nodemailer/dist/cjs/addressparser/index.d.ts +16 -2
  66. package/node_modules/nodemailer/dist/cjs/addressparser/index.js +147 -28
  67. package/node_modules/nodemailer/dist/cjs/base64/index.js +2 -1
  68. package/node_modules/nodemailer/dist/cjs/dkim/index.d.ts +13 -1
  69. package/node_modules/nodemailer/dist/cjs/dkim/index.js +25 -8
  70. package/node_modules/nodemailer/dist/cjs/dkim/message-parser.d.ts +10 -1
  71. package/node_modules/nodemailer/dist/cjs/dkim/message-parser.js +28 -4
  72. package/node_modules/nodemailer/dist/cjs/dkim/relaxed-body.d.ts +10 -1
  73. package/node_modules/nodemailer/dist/cjs/dkim/sign.d.ts +3 -0
  74. package/node_modules/nodemailer/dist/cjs/dkim/sign.js +18 -1
  75. package/node_modules/nodemailer/dist/cjs/errors.d.ts +3 -2
  76. package/node_modules/nodemailer/dist/cjs/fetch/cookies.d.ts +12 -1
  77. package/node_modules/nodemailer/dist/cjs/fetch/index.d.ts +15 -1
  78. package/node_modules/nodemailer/dist/cjs/fetch/index.js +39 -64
  79. package/node_modules/nodemailer/dist/cjs/json-transport/index.d.ts +14 -4
  80. package/node_modules/nodemailer/dist/cjs/json-transport/index.js +2 -0
  81. package/node_modules/nodemailer/dist/cjs/mail-composer/index.d.ts +8 -3
  82. package/node_modules/nodemailer/dist/cjs/mailer/index.d.ts +43 -25
  83. package/node_modules/nodemailer/dist/cjs/mailer/mail-message.d.ts +11 -4
  84. package/node_modules/nodemailer/dist/cjs/mime-funcs/index.d.ts +7 -3
  85. package/node_modules/nodemailer/dist/cjs/mime-funcs/index.js +198 -84
  86. package/node_modules/nodemailer/dist/cjs/mime-node/index.d.ts +5 -2
  87. package/node_modules/nodemailer/dist/cjs/mime-node/index.js +2 -1
  88. package/node_modules/nodemailer/dist/cjs/nodemailer.d.ts +38 -16
  89. package/node_modules/nodemailer/dist/cjs/nodemailer.js +1 -0
  90. package/node_modules/nodemailer/dist/cjs/package-info.d.ts +1 -1
  91. package/node_modules/nodemailer/dist/cjs/package-info.js +1 -1
  92. package/node_modules/nodemailer/dist/cjs/punycode/index.js +1 -0
  93. package/node_modules/nodemailer/dist/cjs/qp/index.d.ts +2 -0
  94. package/node_modules/nodemailer/dist/cjs/qp/index.js +10 -1
  95. package/node_modules/nodemailer/dist/cjs/sendmail-transport/index.d.ts +2 -0
  96. package/node_modules/nodemailer/dist/cjs/ses-transport/index.d.ts +39 -2
  97. package/node_modules/nodemailer/dist/cjs/shared/index.d.ts +43 -7
  98. package/node_modules/nodemailer/dist/cjs/shared/index.js +5 -2
  99. package/node_modules/nodemailer/dist/cjs/smtp-connection/data-stream.d.ts +2 -1
  100. package/node_modules/nodemailer/dist/cjs/smtp-connection/data-stream.js +32 -24
  101. package/node_modules/nodemailer/dist/cjs/smtp-connection/http-proxy-client.d.ts +13 -5
  102. package/node_modules/nodemailer/dist/cjs/smtp-connection/http-proxy-client.js +23 -11
  103. package/node_modules/nodemailer/dist/cjs/smtp-connection/index.d.ts +59 -24
  104. package/node_modules/nodemailer/dist/cjs/smtp-connection/index.js +156 -75
  105. package/node_modules/nodemailer/dist/cjs/smtp-pool/index.d.ts +12 -4
  106. package/node_modules/nodemailer/dist/cjs/smtp-pool/index.js +39 -11
  107. package/node_modules/nodemailer/dist/cjs/smtp-pool/pool-resource.js +27 -31
  108. package/node_modules/nodemailer/dist/cjs/smtp-transport/index.d.ts +19 -6
  109. package/node_modules/nodemailer/dist/cjs/smtp-transport/index.js +25 -33
  110. package/node_modules/nodemailer/dist/cjs/stream-transport/index.d.ts +2 -0
  111. package/node_modules/nodemailer/dist/cjs/well-known/services.js +6 -0
  112. package/node_modules/nodemailer/dist/cjs/xoauth2/index.d.ts +13 -3
  113. package/node_modules/nodemailer/dist/cjs/xoauth2/index.js +25 -13
  114. package/node_modules/nodemailer/dist/esm/addressparser/index.d.ts +16 -2
  115. package/node_modules/nodemailer/dist/esm/addressparser/index.js +147 -27
  116. package/node_modules/nodemailer/dist/esm/base64/index.js +2 -1
  117. package/node_modules/nodemailer/dist/esm/dkim/index.d.ts +13 -1
  118. package/node_modules/nodemailer/dist/esm/dkim/index.js +25 -8
  119. package/node_modules/nodemailer/dist/esm/dkim/message-parser.d.ts +10 -1
  120. package/node_modules/nodemailer/dist/esm/dkim/message-parser.js +30 -5
  121. package/node_modules/nodemailer/dist/esm/dkim/relaxed-body.d.ts +10 -1
  122. package/node_modules/nodemailer/dist/esm/dkim/relaxed-body.js +2 -1
  123. package/node_modules/nodemailer/dist/esm/dkim/sign.d.ts +3 -0
  124. package/node_modules/nodemailer/dist/esm/dkim/sign.js +18 -1
  125. package/node_modules/nodemailer/dist/esm/errors.d.ts +3 -2
  126. package/node_modules/nodemailer/dist/esm/fetch/cookies.d.ts +12 -1
  127. package/node_modules/nodemailer/dist/esm/fetch/cookies.js +2 -1
  128. package/node_modules/nodemailer/dist/esm/fetch/index.d.ts +15 -1
  129. package/node_modules/nodemailer/dist/esm/fetch/index.js +39 -64
  130. package/node_modules/nodemailer/dist/esm/json-transport/index.d.ts +14 -4
  131. package/node_modules/nodemailer/dist/esm/json-transport/index.js +2 -0
  132. package/node_modules/nodemailer/dist/esm/mail-composer/index.d.ts +8 -3
  133. package/node_modules/nodemailer/dist/esm/mailer/index.d.ts +43 -25
  134. package/node_modules/nodemailer/dist/esm/mailer/mail-message.d.ts +11 -4
  135. package/node_modules/nodemailer/dist/esm/mime-funcs/index.d.ts +7 -3
  136. package/node_modules/nodemailer/dist/esm/mime-funcs/index.js +198 -84
  137. package/node_modules/nodemailer/dist/esm/mime-node/index.d.ts +5 -2
  138. package/node_modules/nodemailer/dist/esm/mime-node/index.js +2 -1
  139. package/node_modules/nodemailer/dist/esm/nodemailer.d.ts +38 -16
  140. package/node_modules/nodemailer/dist/esm/package-info.d.ts +1 -1
  141. package/node_modules/nodemailer/dist/esm/package-info.js +1 -1
  142. package/node_modules/nodemailer/dist/esm/punycode/index.js +1 -0
  143. package/node_modules/nodemailer/dist/esm/qp/index.d.ts +2 -0
  144. package/node_modules/nodemailer/dist/esm/qp/index.js +10 -1
  145. package/node_modules/nodemailer/dist/esm/sendmail-transport/index.d.ts +2 -0
  146. package/node_modules/nodemailer/dist/esm/ses-transport/index.d.ts +39 -2
  147. package/node_modules/nodemailer/dist/esm/shared/index.d.ts +43 -7
  148. package/node_modules/nodemailer/dist/esm/shared/index.js +5 -2
  149. package/node_modules/nodemailer/dist/esm/smtp-connection/data-stream.d.ts +2 -1
  150. package/node_modules/nodemailer/dist/esm/smtp-connection/data-stream.js +32 -24
  151. package/node_modules/nodemailer/dist/esm/smtp-connection/http-proxy-client.d.ts +13 -5
  152. package/node_modules/nodemailer/dist/esm/smtp-connection/http-proxy-client.js +23 -11
  153. package/node_modules/nodemailer/dist/esm/smtp-connection/index.d.ts +59 -24
  154. package/node_modules/nodemailer/dist/esm/smtp-connection/index.js +156 -75
  155. package/node_modules/nodemailer/dist/esm/smtp-pool/index.d.ts +12 -4
  156. package/node_modules/nodemailer/dist/esm/smtp-pool/index.js +39 -11
  157. package/node_modules/nodemailer/dist/esm/smtp-pool/pool-resource.js +27 -31
  158. package/node_modules/nodemailer/dist/esm/smtp-transport/index.d.ts +19 -6
  159. package/node_modules/nodemailer/dist/esm/smtp-transport/index.js +25 -33
  160. package/node_modules/nodemailer/dist/esm/stream-transport/index.d.ts +2 -0
  161. package/node_modules/nodemailer/dist/esm/well-known/services.js +6 -0
  162. package/node_modules/nodemailer/dist/esm/xoauth2/index.d.ts +13 -3
  163. package/node_modules/nodemailer/dist/esm/xoauth2/index.js +25 -13
  164. package/node_modules/nodemailer/dist/well-known/services.json +7 -0
  165. package/node_modules/nodemailer/package.json +16 -12
  166. package/package.json +18 -16
  167. package/renovate.json5 +59 -0
@@ -66,8 +66,8 @@ function isPartialLine(line) {
66
66
  * * **port** - is the port to connect to (defaults to 587 or 465)
67
67
  * * **host** - is the hostname or IP address to connect to (defaults to 'localhost')
68
68
  * * **secure** - use SSL
69
- * * **ignoreTLS** - ignore server support for STARTTLS
70
- * * **requireTLS** - forces the client to use STARTTLS
69
+ * * **ignoreTLS** - ignore server support for STARTTLS (has no effect when requireTLS is set)
70
+ * * **requireTLS** - forces the client to use STARTTLS, takes precedence over ignoreTLS and opportunisticTLS
71
71
  * * **name** - the name of the client server
72
72
  * * **localAddress** - outbound address to bind to (see: http://nodejs.org/api/net.html#net_net_connect_options_connectionlistener)
73
73
  * * **greetingTimeout** - Time to wait in ms until greeting message is received from the server (defaults to 30 seconds)
@@ -92,6 +92,11 @@ class SMTPConnection extends EventEmitter {
92
92
  this.id = crypto.randomBytes(8).toString('base64').replace(/\W/g, '');
93
93
  this.stage = 'init';
94
94
  this.options = options || {};
95
+ if (this.options.requireTLS && (this.options.ignoreTLS || this.options.opportunisticTLS)) {
96
+ // requireTLS wins, a contradictory configuration must not quietly fall back to plaintext.
97
+ // Copied so the caller's (possibly shared) options object is left as it was
98
+ this.options = Object.assign({}, this.options, { ignoreTLS: false, opportunisticTLS: false });
99
+ }
95
100
  this.secureConnection = !!this.options.secure;
96
101
  this.alreadySecured = !!this.options.secured;
97
102
  this.port = Number(this.options.port) || (this.secureConnection ? 465 : 587);
@@ -135,6 +140,8 @@ class SMTPConnection extends EventEmitter {
135
140
  this._destroyed = false;
136
141
  this._closing = false;
137
142
  this._currentDataStream = false;
143
+ this._pendingSend = false;
144
+ this._connectCallback = false;
138
145
  this._onSocketData = chunk => this._onData(chunk);
139
146
  this._onSocketError = error => this._onError(error, 'ESOCKET', false, 'CONN');
140
147
  this._onSocketClose = () => this._onClose();
@@ -149,7 +156,9 @@ class SMTPConnection extends EventEmitter {
149
156
  */
150
157
  connect(connectCallback) {
151
158
  if (typeof connectCallback === 'function') {
159
+ this._connectCallback = connectCallback;
152
160
  this.once('connect', () => {
161
+ this._connectCallback = false;
153
162
  this.logger.debug({
154
163
  tnx: 'smtp'
155
164
  }, 'SMTP handshake finished');
@@ -381,6 +390,16 @@ class SMTPConnection extends EventEmitter {
381
390
  }
382
391
  this._currentDataStream = false;
383
392
  }
393
+ // Detach from the message stream as well. The listener is swapped for a no-op rather than
394
+ // removed, a stream destroyed with an error later on would otherwise throw it as unhandled
395
+ if (this._pendingSend) {
396
+ const { stream, onStreamError } = this._pendingSend;
397
+ if (stream) {
398
+ stream.removeListener('error', onStreamError);
399
+ stream.on('error', TEARDOWN_NOOP);
400
+ }
401
+ this._pendingSend = false;
402
+ }
384
403
  if (socket && !socket.destroyed) {
385
404
  try {
386
405
  // Clear socket timeout to prevent timer leaks
@@ -414,14 +433,21 @@ class SMTPConnection extends EventEmitter {
414
433
  this._auth = authData || {};
415
434
  // Select SASL authentication method
416
435
  this._authMethod = (this._auth.method || '').toString().trim().toUpperCase() || false;
436
+ // XOAUTH2 needs a token generator or a custom handler, without either the method
437
+ // can not be run even when it is the only one the server advertised
438
+ const canUseXOAuth2 = !!this._auth.oauth2 || this.customAuth.has('XOAUTH2');
417
439
  if (!this._authMethod && this._auth.oauth2 && !this._auth.credentials) {
418
440
  this._authMethod = 'XOAUTH2';
419
441
  }
420
442
  else if (!this._authMethod || (this._authMethod === 'XOAUTH2' && !this._auth.oauth2)) {
421
- // use first supported
422
- this._authMethod = (this._supportedAuth[0] || 'PLAIN').toUpperCase().trim();
423
- }
424
- if (this._authMethod !== 'XOAUTH2' && (!this._auth.credentials || !this._auth.credentials.user || !this._auth.credentials.pass)) {
443
+ // use the first supported method that can be run
444
+ const supported = this._supportedAuth.find(method => method !== 'XOAUTH2' || canUseXOAuth2);
445
+ this._authMethod = (supported || 'PLAIN').toUpperCase().trim();
446
+ }
447
+ // a token login needs no credentials, every other method and every custom handler
448
+ // gets them filled in from the user and pass values
449
+ if ((this._authMethod !== 'XOAUTH2' || this.customAuth.has('XOAUTH2')) &&
450
+ (!this._auth.credentials || !this._auth.credentials.user || !this._auth.credentials.pass)) {
425
451
  if ((this._auth.user && this._auth.pass) || this.customAuth.has(this._authMethod)) {
426
452
  this._auth.credentials = {
427
453
  user: this._auth.user,
@@ -458,42 +484,45 @@ class SMTPConnection extends EventEmitter {
458
484
  returned = true;
459
485
  callback(this._formatError(err, 'EAUTH', lastResponse, 'AUTH ' + this._authMethod));
460
486
  };
487
+ // one implementation serves both sendCommand overloads, the promise is returned
488
+ // exactly when no callback was given
489
+ const sendCommand = (cmd, done) => {
490
+ let promise;
491
+ if (!done) {
492
+ promise = new Promise((resolve, reject) => {
493
+ done = shared.callbackPromise(resolve, reject);
494
+ });
495
+ }
496
+ this._responseActions.push(str => {
497
+ lastResponse = str;
498
+ let codes = str.match(/^(\d+)(?:\s(\d+\.\d+\.\d+))?\s/);
499
+ let data = {
500
+ command: cmd,
501
+ response: str
502
+ };
503
+ if (codes) {
504
+ data.status = Number(codes[1]) || 0;
505
+ if (codes[2]) {
506
+ data.code = codes[2];
507
+ }
508
+ data.text = str.substr(codes[0].length);
509
+ }
510
+ else {
511
+ data.text = str;
512
+ data.status = 0; // just in case we need to perform numeric comparisons
513
+ }
514
+ done(null, data);
515
+ });
516
+ setImmediate(() => this._sendCommand(cmd));
517
+ return promise;
518
+ };
461
519
  const handlerResponse = handler({
462
520
  auth: this._auth,
463
521
  method: this._authMethod,
464
522
  extensions: [].concat(this._supportedExtensions),
465
523
  authMethods: [].concat(this._supportedAuth),
466
524
  maxAllowedSize: this._maxAllowedSize || false,
467
- sendCommand: (cmd, done) => {
468
- let promise;
469
- if (!done) {
470
- promise = new Promise((resolve, reject) => {
471
- done = shared.callbackPromise(resolve, reject);
472
- });
473
- }
474
- this._responseActions.push(str => {
475
- lastResponse = str;
476
- let codes = str.match(/^(\d+)(?:\s(\d+\.\d+\.\d+))?\s/);
477
- let data = {
478
- command: cmd,
479
- response: str
480
- };
481
- if (codes) {
482
- data.status = Number(codes[1]) || 0;
483
- if (codes[2]) {
484
- data.code = codes[2];
485
- }
486
- data.text = str.substr(codes[0].length);
487
- }
488
- else {
489
- data.text = str;
490
- data.status = 0; // just in case we need to perform numeric comparisons
491
- }
492
- done(null, data);
493
- });
494
- setImmediate(() => this._sendCommand(cmd));
495
- return promise;
496
- },
525
+ sendCommand: sendCommand,
497
526
  resolve,
498
527
  reject
499
528
  });
@@ -547,35 +576,46 @@ class SMTPConnection extends EventEmitter {
547
576
  *
548
577
  * @param envelope Envelope object, {from: addr, to: [addr]}
549
578
  * @param message String, Buffer or a Stream
550
- * @param callback Callback to return once sending is completed
579
+ * @param done Callback to return once sending is completed
551
580
  */
552
581
  send(envelope, message, done) {
582
+ // ensure that the callback is only called once. The public callback type has a
583
+ // required result, the error paths hand over the error alone
584
+ let returned = false;
585
+ const callback = (err, info) => {
586
+ if (returned) {
587
+ return;
588
+ }
589
+ returned = true;
590
+ if (this._pendingSend && this._pendingSend.callback === callback) {
591
+ this._pendingSend = false;
592
+ }
593
+ done(err, info);
594
+ };
553
595
  if (!message) {
554
- return done(this._formatError('Empty message', 'EMESSAGE', false, 'API'));
596
+ return callback(this._formatError('Empty message', 'EMESSAGE', false, 'API'));
555
597
  }
556
598
  const isDestroyedMessage = this._isDestroyedMessage('send message');
557
599
  if (isDestroyedMessage) {
558
- return done(this._formatError(isDestroyedMessage, 'ECONNECTION', false, 'API'));
600
+ return callback(this._formatError(isDestroyedMessage, 'ECONNECTION', false, 'API'));
559
601
  }
560
602
  // reject larger messages than allowed
561
603
  if (this._maxAllowedSize && envelope.size > this._maxAllowedSize) {
562
604
  setImmediate(() => {
563
- done(this._formatError('Message size larger than allowed ' + this._maxAllowedSize, 'EMESSAGE', false, 'MAIL FROM'));
605
+ callback(this._formatError('Message size larger than allowed ' + this._maxAllowedSize, 'EMESSAGE', false, 'MAIL FROM'));
564
606
  });
565
607
  return;
566
608
  }
567
- // ensure that callback is only called once
568
- let returned = false;
569
- const callback = function (...args) {
570
- if (returned) {
571
- return;
572
- }
573
- returned = true;
574
- done(...args);
609
+ const pendingSend = {
610
+ callback,
611
+ stream: false,
612
+ onStreamError: err => callback(this._formatError(err, 'ESTREAM', false, 'API'))
575
613
  };
576
614
  if (typeof message.on === 'function') {
577
- message.on('error', err => callback(this._formatError(err, 'ESTREAM', false, 'API')));
615
+ pendingSend.stream = message;
616
+ pendingSend.stream.on('error', pendingSend.onStreamError);
578
617
  }
618
+ this._pendingSend = pendingSend;
579
619
  const startTime = Date.now();
580
620
  this._setEnvelope(envelope, (err, info) => {
581
621
  if (err) {
@@ -595,11 +635,13 @@ class SMTPConnection extends EventEmitter {
595
635
  if (err) {
596
636
  return callback(err);
597
637
  }
598
- info.envelopeTime = envelopeTime - startTime;
599
- info.messageTime = Date.now() - envelopeTime;
600
- info.messageSize = stream.outByteCount;
601
- info.response = str;
602
- return callback(null, info);
638
+ // the envelope info becomes the send result once the timings are on it
639
+ const result = info;
640
+ result.envelopeTime = envelopeTime - startTime;
641
+ result.messageTime = Date.now() - envelopeTime;
642
+ result.messageSize = stream.outByteCount;
643
+ result.response = str;
644
+ return callback(null, result);
603
645
  });
604
646
  if (typeof message.pipe === 'function') {
605
647
  message.pipe(stream);
@@ -747,6 +789,8 @@ class SMTPConnection extends EventEmitter {
747
789
  * @event
748
790
  * @param err Error object
749
791
  * @param type Error name
792
+ * @param data Server response that triggered the error, false if there is none
793
+ * @param command SMTP command that was in flight
750
794
  * @internal
751
795
  */
752
796
  _onError(err, type, data, command) {
@@ -766,8 +810,14 @@ class SMTPConnection extends EventEmitter {
766
810
  else {
767
811
  this.logger.error(data, err.message);
768
812
  }
813
+ // close() forgets the send in flight, it is completed with this same error afterwards so
814
+ // a late message stream error has nothing left to report
815
+ const pendingSend = this._pendingSend;
769
816
  this.emit('error', err);
770
817
  this.close();
818
+ if (pendingSend) {
819
+ pendingSend.callback(err);
820
+ }
771
821
  }
772
822
  /** @internal */
773
823
  _formatError(message, type, response, command) {
@@ -813,14 +863,25 @@ class SMTPConnection extends EventEmitter {
813
863
  this.logger.info({
814
864
  tnx: 'network'
815
865
  }, 'Connection closed');
866
+ // the unterminated remainder is only reported as a reply (and so gives the error a responseCode)
867
+ // when it starts like a complete failure reply, not for a fragment such as "55" or a 250
868
+ const failureResponse = typeof serverResponse === 'string' && /^[45]\d{2}[ -]/.test(serverResponse) ? serverResponse : false;
816
869
  if (this.upgrading && !this._destroyed) {
817
- return this._onError(new Error('Connection closed unexpectedly'), 'ETLS', serverResponse, 'CONN');
818
- }
819
- else if (![this._actionGreeting, this.close].includes(this._responseActions[0]) && !this._destroyed) {
820
- return this._onError(new Error('Connection closed unexpectedly'), 'ECONNECTION', serverResponse, 'CONN');
870
+ return this._onError(new Error('Connection closed unexpectedly'), 'ETLS', failureResponse, 'CONN');
871
+ }
872
+ if (!failureResponse && this._responseActions[0] === this._actionGreeting && this._connectCallback && !this._destroyed) {
873
+ // A silent close before the greeting is handed to the connect() callback rather than
874
+ // emitted as 'error', callers that never saw an error for it must not start throwing one
875
+ const connectCallback = this._connectCallback;
876
+ this._connectCallback = false;
877
+ const err = this._formatError(new Error('Connection closed unexpectedly'), 'ECONNECTION', false, 'CONN');
878
+ this.logger.warn({ tnx: 'network' }, err.message);
879
+ connectCallback(err);
880
+ this.close();
881
+ return;
821
882
  }
822
- else if (/^[45]\d{2}\b/.test(serverResponse)) {
823
- return this._onError(new Error('Connection closed unexpectedly'), 'ECONNECTION', serverResponse, 'CONN');
883
+ if (failureResponse || (this._responseActions[0] !== this.close && !this._destroyed)) {
884
+ return this._onError(new Error('Connection closed unexpectedly'), 'ECONNECTION', failureResponse, 'CONN');
824
885
  }
825
886
  this._destroy();
826
887
  }
@@ -998,6 +1059,7 @@ class SMTPConnection extends EventEmitter {
998
1059
  * {from:'...', to:['...']}
999
1060
  * or
1000
1061
  * {from:{address:'...',name:'...'}, to:[address:'...',name:'...']}
1062
+ * @param callback Callback to run once the envelope is processed
1001
1063
  * @internal
1002
1064
  */
1003
1065
  _setEnvelope(envelope, callback) {
@@ -1297,21 +1359,25 @@ class SMTPConnection extends EventEmitter {
1297
1359
  if (/[ -]AUTH\b/i.test(str)) {
1298
1360
  this.allowsAuth = true;
1299
1361
  }
1300
- // Detect if the server supports PLAIN auth
1301
- if (/[ -]AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)PLAIN/i.test(str)) {
1302
- this._supportedAuth.push('PLAIN');
1303
- }
1304
- // Detect if the server supports LOGIN auth
1305
- if (/[ -]AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)LOGIN/i.test(str)) {
1306
- this._supportedAuth.push('LOGIN');
1307
- }
1308
- // Detect if the server supports CRAM-MD5 auth
1309
- if (/[ -]AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)CRAM-MD5/i.test(str)) {
1310
- this._supportedAuth.push('CRAM-MD5');
1362
+ // Detect the advertised SASL mechanisms. The list is split into whole tokens rather
1363
+ // than searched for each name with a pattern: the patterns this replaced let two
1364
+ // whitespace runs overlap and backtracked quadratically over an AUTH line padded with
1365
+ // spaces, so a hostile server could stall the event loop from its EHLO reply
1366
+ // (GHSA-4ffr-jq9g-5ffx).
1367
+ const authMechanisms = new Set();
1368
+ for (const line of this._ehloLines) {
1369
+ const authMatch = /^AUTH[\s=](.*)/i.exec(line);
1370
+ if (authMatch) {
1371
+ for (const mechanism of authMatch[1].split(/[\s=]+/)) {
1372
+ authMechanisms.add(mechanism.toUpperCase());
1373
+ }
1374
+ }
1311
1375
  }
1312
- // Detect if the server supports XOAUTH2 auth
1313
- if (/[ -]AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)XOAUTH2/i.test(str)) {
1314
- this._supportedAuth.push('XOAUTH2');
1376
+ // listed in order of preference, the first one the credentials allow is used
1377
+ for (const mechanism of ['PLAIN', 'LOGIN', 'CRAM-MD5', 'XOAUTH2']) {
1378
+ if (authMechanisms.has(mechanism)) {
1379
+ this._supportedAuth.push(mechanism);
1380
+ }
1315
1381
  }
1316
1382
  // Detect if the server supports SIZE extensions (and the max allowed size)
1317
1383
  if ((match = str.match(/[ -]SIZE(?:[ \t]+(\d+))?/im))) {
@@ -1388,6 +1454,7 @@ class SMTPConnection extends EventEmitter {
1388
1454
  * hosts invalidly use a longer message than VXNlcm5hbWU6
1389
1455
  *
1390
1456
  * @param str Message from the server
1457
+ * @param callback Callback to run once the authentication sequence completes
1391
1458
  * @internal
1392
1459
  */
1393
1460
  _actionAUTH_LOGIN_USER(str, callback) {
@@ -1409,6 +1476,7 @@ class SMTPConnection extends EventEmitter {
1409
1476
  * base64 encoded again.
1410
1477
  *
1411
1478
  * @param str Message from the server
1479
+ * @param callback Callback to run once the authentication sequence completes
1412
1480
  * @internal
1413
1481
  */
1414
1482
  _actionAUTH_CRAM_MD5(str, callback) {
@@ -1433,6 +1501,7 @@ class SMTPConnection extends EventEmitter {
1433
1501
  * the user can be considered logged in. Start waiting for a message to send
1434
1502
  *
1435
1503
  * @param str Message from the server
1504
+ * @param callback Callback to run once the authentication sequence completes
1436
1505
  * @internal
1437
1506
  */
1438
1507
  _actionAUTH_CRAM_MD5_PASS(str, callback) {
@@ -1454,6 +1523,7 @@ class SMTPConnection extends EventEmitter {
1454
1523
  * response needs to be base64 encoded password.
1455
1524
  *
1456
1525
  * @param str Message from the server
1526
+ * @param callback Callback to run once the authentication sequence completes
1457
1527
  * @internal
1458
1528
  */
1459
1529
  _actionAUTH_LOGIN_PASS(str, callback) {
@@ -1473,6 +1543,8 @@ class SMTPConnection extends EventEmitter {
1473
1543
  * the user can be considered logged in. Start waiting for a message to send
1474
1544
  *
1475
1545
  * @param str Message from the server
1546
+ * @param isRetry True if this is a retry after a failed login, or the callback itself
1547
+ * @param [callback] Callback to run once the authentication sequence completes
1476
1548
  * @internal
1477
1549
  */
1478
1550
  _actionAUTHComplete(str, isRetry, callback) {
@@ -1515,6 +1587,7 @@ class SMTPConnection extends EventEmitter {
1515
1587
  * Handle response for a MAIL FROM: command
1516
1588
  *
1517
1589
  * @param str Message from the server
1590
+ * @param callback Callback to run once the envelope is processed
1518
1591
  * @internal
1519
1592
  */
1520
1593
  _actionMAIL(str, callback) {
@@ -1543,6 +1616,7 @@ class SMTPConnection extends EventEmitter {
1543
1616
  * Handle response for a RCPT TO: command
1544
1617
  *
1545
1618
  * @param str Message from the server
1619
+ * @param callback Callback to run once the envelope is processed
1546
1620
  * @internal
1547
1621
  */
1548
1622
  _actionRCPT(str, callback) {
@@ -1571,7 +1645,11 @@ class SMTPConnection extends EventEmitter {
1571
1645
  this._sendCommand('DATA');
1572
1646
  }
1573
1647
  else {
1574
- err = this._formatError("Can't send mail - all recipients were rejected", 'EENVELOPE', str, 'RCPT TO');
1648
+ // report a temporary rejection when there is one, taking the last reply would mark the
1649
+ // whole message as permanently failed although some recipients were only deferred
1650
+ const deferred = envelope.rejectedErrors.find(rejectedErr => rejectedErr.responseCode && rejectedErr.responseCode < 500);
1651
+ const reply = deferred?.response ?? str;
1652
+ err = this._formatError("Can't send mail - all recipients were rejected", 'EENVELOPE', reply, 'RCPT TO');
1575
1653
  err.rejected = envelope.rejected;
1576
1654
  err.rejectedErrors = envelope.rejectedErrors;
1577
1655
  return callback(err);
@@ -1590,6 +1668,7 @@ class SMTPConnection extends EventEmitter {
1590
1668
  * Handle response for a DATA command
1591
1669
  *
1592
1670
  * @param str Message from the server
1671
+ * @param callback Callback to run once the envelope is processed
1593
1672
  * @internal
1594
1673
  */
1595
1674
  _actionDATA(str, callback) {
@@ -1616,6 +1695,7 @@ class SMTPConnection extends EventEmitter {
1616
1695
  * We expect a single response that defines if the sending succeeded or failed
1617
1696
  *
1618
1697
  * @param str Message from the server
1698
+ * @param callback Callback to run with the final send result
1619
1699
  * @internal
1620
1700
  */
1621
1701
  _actionSMTPStream(str, callback) {
@@ -1632,6 +1712,7 @@ class SMTPConnection extends EventEmitter {
1632
1712
  * @param recipient The recipient this response applies to
1633
1713
  * @param final Is this the final recipient?
1634
1714
  * @param str Message from the server
1715
+ * @param callback Callback to run with the final send result
1635
1716
  * @internal
1636
1717
  */
1637
1718
  _actionLMTPStream(recipient, final, str, callback) {
@@ -17,7 +17,7 @@ export interface SMTPPoolOptions extends SMTPTransportOptions {
17
17
  rateLimit?: number | undefined;
18
18
  /** Time window for rateLimit in milliseconds, defaults to 1000 */
19
19
  rateDelta?: number | undefined;
20
- /** How many times a message is requeued when its connection closes while sending, unlimited when not set or negative */
20
+ /** How many times a message is requeued when its connection closes while sending, defaults to 5, a negative value means unlimited */
21
21
  maxRequeues?: number | undefined;
22
22
  }
23
23
  /**
@@ -26,6 +26,7 @@ export interface SMTPPoolOptions extends SMTPTransportOptions {
26
26
  export type SMTPPoolResolvedOptions = SMTPPoolOptions & {
27
27
  maxConnections: number;
28
28
  maxMessages: number;
29
+ maxRequeues: number;
29
30
  };
30
31
  /**
31
32
  * Result of a message sent through the pool, same as for the SMTP transport
@@ -80,7 +81,7 @@ declare class SMTPPool extends EventEmitter {
80
81
  /**
81
82
  * The Mail instance using this transport, assigned by Mail
82
83
  */
83
- mailer?: Mail<SMTPPoolSentMessageInfo> | undefined;
84
+ mailer?: Mail<SMTPPoolSentMessageInfo, SMTPPoolOptions> | undefined;
84
85
  constructor(options?: SMTPPoolOptions | string);
85
86
  /**
86
87
  * Placeholder function for creating proxy sockets. This method immediatelly returns
@@ -96,7 +97,7 @@ declare class SMTPPool extends EventEmitter {
96
97
  * @param mail Mail object
97
98
  * @param callback Callback function
98
99
  */
99
- send(mail: MailMessage, callback: SMTPPoolSendCallback): boolean;
100
+ send(mail: MailMessage<SMTPPoolSentMessageInfo>, callback: SMTPPoolSendCallback): boolean;
100
101
  /**
101
102
  * Closes all connections in the pool. If there is a message being sent, the connection
102
103
  * is closed later
@@ -109,9 +110,14 @@ declare class SMTPPool extends EventEmitter {
109
110
  /**
110
111
  * Verifies SMTP configuration
111
112
  *
112
- * @param callback Callback function
113
+ * @returns Promise that resolves to true if the configuration is usable
113
114
  */
114
115
  verify(): Promise<true>;
116
+ /**
117
+ * Verifies SMTP configuration
118
+ *
119
+ * @param callback Callback function
120
+ */
115
121
  verify(callback: VerifyCallback): void;
116
122
  }
117
123
  /**
@@ -122,4 +128,6 @@ declare namespace SMTPPool {
122
128
  type MailOptions = SendMailOptions;
123
129
  type SentMessageInfo = SMTPPoolSentMessageInfo;
124
130
  }
131
+ /** The same aliases as module level exports, for `import * as SMTPPool` and `import SMTPPool = require()` */
132
+ export type { SMTPPoolOptions as Options, SendMailOptions as MailOptions, SMTPPoolSentMessageInfo as SentMessageInfo };
125
133
  export default SMTPPool;
@@ -5,6 +5,10 @@ import wellKnown from '../well-known/index.js';
5
5
  import * as shared from '../shared/index.js';
6
6
  import * as errors from '../errors.js';
7
7
  import * as packageData from '../package-info.js';
8
+ /** First delay before a requeued message is retried, doubled on every further requeue */
9
+ const REQUEUE_BASE_DELAY = 50;
10
+ /** Upper bound for the requeue delay */
11
+ const REQUEUE_MAX_DELAY = 2000;
8
12
  /**
9
13
  * Creates a SMTP pool transport object for Nodemailer
10
14
  *
@@ -36,6 +40,8 @@ class SMTPPool extends EventEmitter {
36
40
  );
37
41
  this.options.maxConnections = this.options.maxConnections || 5;
38
42
  this.options.maxMessages = this.options.maxMessages || 100;
43
+ // a default bound, a server that closes every connection before the greeting would otherwise be retried forever
44
+ this.options.maxRequeues = typeof this.options.maxRequeues === 'number' ? this.options.maxRequeues : 5;
39
45
  this.logger = shared.getLogger(this.options, {
40
46
  component: this.options.component || 'smtp-pool'
41
47
  });
@@ -79,6 +85,10 @@ class SMTPPool extends EventEmitter {
79
85
  */
80
86
  send(mail, callback) {
81
87
  if (this._closed) {
88
+ // Mail.sendMail ignores the return value, so without a callback its promise would never settle
89
+ const err = new Error('Connection pool was closed');
90
+ err.code = errors.ECONNECTION;
91
+ setImmediate(() => callback(err));
82
92
  return false;
83
93
  }
84
94
  this._queue.push({
@@ -292,15 +302,22 @@ class SMTPPool extends EventEmitter {
292
302
  // Note that we must wait a bit.. because the callback of the 'error' handler might be called
293
303
  // in the next event loop
294
304
  setTimeout(() => {
305
+ let delay = 0;
295
306
  if (connection.queueEntry) {
296
307
  if (this._shouldRequeuOnConnectionClose(connection.queueEntry)) {
297
- this._requeueEntryOnConnectionClose(connection);
308
+ delay = this._requeueEntryOnConnectionClose(connection);
298
309
  }
299
310
  else {
300
311
  this._failDeliveryOnConnectionClose(connection);
301
312
  }
302
313
  }
303
- this._continueProcessing();
314
+ if (delay) {
315
+ // back off, a server that keeps dropping connections is not hammered
316
+ setTimeout(() => this._continueProcessing(), delay);
317
+ }
318
+ else {
319
+ this._continueProcessing();
320
+ }
304
321
  }, 50);
305
322
  }
306
323
  else {
@@ -315,7 +332,7 @@ class SMTPPool extends EventEmitter {
315
332
  }
316
333
  /** @internal */
317
334
  _shouldRequeuOnConnectionClose(queueEntry) {
318
- if (this.options.maxRequeues === undefined || this.options.maxRequeues < 0) {
335
+ if (this.options.maxRequeues < 0) {
319
336
  return true;
320
337
  }
321
338
  return queueEntry.requeueAttempts < this.options.maxRequeues;
@@ -324,7 +341,9 @@ class SMTPPool extends EventEmitter {
324
341
  _failDeliveryOnConnectionClose(connection) {
325
342
  if (connection.queueEntry && connection.queueEntry.callback) {
326
343
  try {
327
- connection.queueEntry.callback(new Error('Reached maximum number of retries after connection was closed'));
344
+ const err = new Error('Reached maximum number of retries after connection was closed');
345
+ err.code = errors.ECONNECTION;
346
+ connection.queueEntry.callback(err);
328
347
  }
329
348
  catch (E) {
330
349
  this.logger.error({
@@ -339,6 +358,7 @@ class SMTPPool extends EventEmitter {
339
358
  }
340
359
  /** @internal */
341
360
  _requeueEntryOnConnectionClose(connection) {
361
+ const delay = Math.min(REQUEUE_BASE_DELAY * 2 ** connection.queueEntry.requeueAttempts, REQUEUE_MAX_DELAY);
342
362
  connection.queueEntry.requeueAttempts += 1;
343
363
  this.logger.debug({
344
364
  tnx: 'pool',
@@ -348,6 +368,7 @@ class SMTPPool extends EventEmitter {
348
368
  }, 'Re-queued message <%s> for #%s. Attempt: #%s', connection.queueEntry.messageId, connection.id, connection.queueEntry.requeueAttempts);
349
369
  this._queue.unshift(connection.queueEntry);
350
370
  connection.queueEntry = false;
371
+ return delay;
351
372
  }
352
373
  /**
353
374
  * Continue to process message if the pool hasn't closed
@@ -424,10 +445,12 @@ class SMTPPool extends EventEmitter {
424
445
  callback = shared.callbackPromise(resolve, reject);
425
446
  });
426
447
  }
448
+ // the error paths hand over the error alone
449
+ const done = callback;
427
450
  const auth = new PoolResource(this).auth;
428
451
  this.getSocket(this.options, (err, socketOptions) => {
429
452
  if (err) {
430
- return callback(err);
453
+ return done(err);
431
454
  }
432
455
  let options = this.options;
433
456
  if (socketOptions && socketOptions.connection) {
@@ -449,14 +472,14 @@ class SMTPPool extends EventEmitter {
449
472
  }
450
473
  returned = true;
451
474
  connection.close();
452
- return callback(err);
475
+ return done(err);
453
476
  });
454
477
  connection.once('end', () => {
455
478
  if (returned) {
456
479
  return;
457
480
  }
458
481
  returned = true;
459
- return callback(new Error('Connection closed'));
482
+ return done(new Error('Connection closed'));
460
483
  });
461
484
  const finalize = () => {
462
485
  if (returned) {
@@ -464,12 +487,17 @@ class SMTPPool extends EventEmitter {
464
487
  }
465
488
  returned = true;
466
489
  connection.quit();
467
- return callback(null, true);
490
+ return done(null, true);
468
491
  };
469
- connection.connect(() => {
492
+ connection.connect(err => {
470
493
  if (returned) {
471
494
  return;
472
495
  }
496
+ if (err) {
497
+ returned = true;
498
+ connection.close();
499
+ return done(err);
500
+ }
473
501
  if (auth && (connection.allowsAuth || options.forceAuth)) {
474
502
  connection.login(auth, err => {
475
503
  if (returned) {
@@ -478,7 +506,7 @@ class SMTPPool extends EventEmitter {
478
506
  if (err) {
479
507
  returned = true;
480
508
  connection.close();
481
- return callback(err);
509
+ return done(err);
482
510
  }
483
511
  finalize();
484
512
  });
@@ -488,7 +516,7 @@ class SMTPPool extends EventEmitter {
488
516
  err.code = errors.ENOAUTH;
489
517
  returned = true;
490
518
  connection.close();
491
- return callback(err);
519
+ return done(err);
492
520
  }
493
521
  else {
494
522
  finalize();