@nocobase/plugin-workflow-mailer 2.2.16 → 2.2.18

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 (53) hide show
  1. package/dist/externalVersion.js +6 -6
  2. package/dist/node_modules/joi/dist/joi-browser.min.js +1 -1
  3. package/dist/node_modules/joi/lib/index.js +1 -1
  4. package/dist/node_modules/joi/lib/messages.js +35 -6
  5. package/dist/node_modules/joi/lib/types/keys.js +18 -1
  6. package/dist/node_modules/joi/lib/types/link.js +18 -2
  7. package/dist/node_modules/joi/lib/types/string.js +2 -2
  8. package/dist/node_modules/joi/package.json +1 -1
  9. package/dist/node_modules/nodemailer/.ncurc.js +2 -0
  10. package/dist/node_modules/nodemailer/.prettierignore +8 -0
  11. package/dist/node_modules/nodemailer/.prettierrc +12 -0
  12. package/dist/node_modules/nodemailer/.prettierrc.js +2 -0
  13. package/dist/node_modules/nodemailer/.release-please-config.json +9 -0
  14. package/dist/node_modules/nodemailer/eslint.config.js +88 -0
  15. package/dist/node_modules/nodemailer/lib/addressparser/index.js +314 -71
  16. package/dist/node_modules/nodemailer/lib/base64/index.js +9 -13
  17. package/dist/node_modules/nodemailer/lib/dkim/index.js +17 -18
  18. package/dist/node_modules/nodemailer/lib/dkim/message-parser.js +12 -13
  19. package/dist/node_modules/nodemailer/lib/dkim/relaxed-body.js +3 -3
  20. package/dist/node_modules/nodemailer/lib/dkim/sign.js +22 -18
  21. package/dist/node_modules/nodemailer/lib/errors.js +59 -0
  22. package/dist/node_modules/nodemailer/lib/fetch/cookies.js +14 -19
  23. package/dist/node_modules/nodemailer/lib/fetch/index.js +164 -27
  24. package/dist/node_modules/nodemailer/lib/json-transport/index.js +4 -4
  25. package/dist/node_modules/nodemailer/lib/mail-composer/index.js +229 -155
  26. package/dist/node_modules/nodemailer/lib/mailer/index.js +106 -54
  27. package/dist/node_modules/nodemailer/lib/mailer/mail-message.js +106 -68
  28. package/dist/node_modules/nodemailer/lib/mime-funcs/index.js +105 -59
  29. package/dist/node_modules/nodemailer/lib/mime-funcs/mime-types.js +21 -14
  30. package/dist/node_modules/nodemailer/lib/mime-node/index.js +389 -137
  31. package/dist/node_modules/nodemailer/lib/mime-node/last-newline.js +1 -1
  32. package/dist/node_modules/nodemailer/lib/mime-node/le-unix.js +4 -7
  33. package/dist/node_modules/nodemailer/lib/mime-node/le-windows.js +1 -4
  34. package/dist/node_modules/nodemailer/lib/nodemailer.js +1 -1
  35. package/dist/node_modules/nodemailer/lib/punycode/index.js +1 -1
  36. package/dist/node_modules/nodemailer/lib/qp/index.js +33 -22
  37. package/dist/node_modules/nodemailer/lib/sendmail-transport/index.js +54 -44
  38. package/dist/node_modules/nodemailer/lib/ses-transport/index.js +110 -193
  39. package/dist/node_modules/nodemailer/lib/shared/index.js +299 -240
  40. package/dist/node_modules/nodemailer/lib/shared/objects.js +42 -0
  41. package/dist/node_modules/nodemailer/lib/shared/url.js +151 -0
  42. package/dist/node_modules/nodemailer/lib/smtp-connection/data-stream.js +3 -6
  43. package/dist/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +50 -19
  44. package/dist/node_modules/nodemailer/lib/smtp-connection/index.js +415 -223
  45. package/dist/node_modules/nodemailer/lib/smtp-pool/index.js +27 -34
  46. package/dist/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +19 -16
  47. package/dist/node_modules/nodemailer/lib/smtp-transport/index.js +66 -53
  48. package/dist/node_modules/nodemailer/lib/stream-transport/index.js +16 -7
  49. package/dist/node_modules/nodemailer/lib/well-known/index.js +7 -7
  50. package/dist/node_modules/nodemailer/lib/well-known/services.json +328 -38
  51. package/dist/node_modules/nodemailer/lib/xoauth2/index.js +105 -33
  52. package/dist/node_modules/nodemailer/package.json +1 -1
  53. package/package.json +4 -4
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
3
  const packageInfo = require('../../package.json');
4
- const EventEmitter = require('events').EventEmitter;
4
+ const { EventEmitter } = require('events');
5
5
  const net = require('net');
6
6
  const tls = require('tls');
7
7
  const os = require('os');
8
8
  const crypto = require('crypto');
9
9
  const DataStream = require('./data-stream');
10
- const PassThrough = require('stream').PassThrough;
10
+ const { PassThrough } = require('stream');
11
11
  const shared = require('../shared');
12
12
 
13
13
  // default timeout values in ms
@@ -15,6 +15,29 @@ const CONNECTION_TIMEOUT = 2 * 60 * 1000; // how much to wait for the connection
15
15
  const SOCKET_TIMEOUT = 10 * 60 * 1000; // how much to wait for socket inactivity before disconnecting the client
16
16
  const GREETING_TIMEOUT = 30 * 1000; // how much to wait after connection is established but SMTP greeting is not receieved
17
17
  const DNS_TIMEOUT = 30 * 1000; // how much to wait for resolveHostname
18
+ const TEARDOWN_NOOP = () => {}; // reusable no-op handler for absorbing errors during socket teardown
19
+
20
+ /**
21
+ * Re-interpret a server response stored in fake 8-bit byte-container form
22
+ * (the result of chunk.toString('binary') in _onData) as UTF-8.
23
+ *
24
+ * Server reply text has no formally defined charset (RFC 5321 §4.2.1), but
25
+ * modern MTAs commonly use UTF-8. The byte-container plumbing in _onData is
26
+ * required to reassemble multi-byte sequences split across socket chunks;
27
+ * this helper performs the actual decode at the line boundary, falling back
28
+ * to the byte-container form when the bytes are not valid UTF-8 so that
29
+ * legacy 8-bit replies are still recoverable byte-for-byte.
30
+ */
31
+ function decodeServerResponse(str) {
32
+ if (!str) {
33
+ return str;
34
+ }
35
+ const utf8 = Buffer.from(str, 'binary').toString('utf8');
36
+ // The input is a byte container (each char is in U+0000..U+00FF) so it can never
37
+ // already contain U+FFFD; any \uFFFD in the result was inserted by Node's UTF-8
38
+ // decoder for invalid bytes, which means we should return the original bytes intact.
39
+ return utf8.includes('\uFFFD') ? str : utf8;
40
+ }
18
41
 
19
42
  /**
20
43
  * Generates a SMTP connection object
@@ -28,9 +51,9 @@ const DNS_TIMEOUT = 30 * 1000; // how much to wait for resolveHostname
28
51
  * * **requireTLS** - forces the client to use STARTTLS
29
52
  * * **name** - the name of the client server
30
53
  * * **localAddress** - outbound address to bind to (see: http://nodejs.org/api/net.html#net_net_connect_options_connectionlistener)
31
- * * **greetingTimeout** - Time to wait in ms until greeting message is received from the server (defaults to 10000)
32
- * * **connectionTimeout** - how many milliseconds to wait for the connection to establish
33
- * * **socketTimeout** - Time of inactivity until the connection is closed (defaults to 1 hour)
54
+ * * **greetingTimeout** - Time to wait in ms until greeting message is received from the server (defaults to 30 seconds)
55
+ * * **connectionTimeout** - how many milliseconds to wait for the connection to establish (defaults to 2 minutes)
56
+ * * **socketTimeout** - Time of inactivity until the connection is closed (defaults to 10 minutes)
34
57
  * * **dnsTimeout** - Time to wait in ms for the DNS requests to be resolved (defaults to 30 seconds)
35
58
  * * **lmtp** - if true, uses LMTP instead of SMTP protocol
36
59
  * * **logger** - bunyan compatible logger interface
@@ -67,7 +90,7 @@ class SMTPConnection extends EventEmitter {
67
90
  this.secureConnection = true;
68
91
  }
69
92
 
70
- this.name = this.options.name || this._getHostname();
93
+ this.name = (this.options.name || this._getHostname()).toString().replace(/[\r\n]+/g, '');
71
94
 
72
95
  this.logger = shared.getLogger(this.options, {
73
96
  component: this.options.component || 'smtp-connection',
@@ -75,13 +98,12 @@ class SMTPConnection extends EventEmitter {
75
98
  });
76
99
 
77
100
  this.customAuth = new Map();
78
- Object.keys(this.options.customAuth || {}).forEach(key => {
79
- let mapKey = (key || '').toString().trim().toUpperCase();
80
- if (!mapKey) {
81
- return;
101
+ for (const key of Object.keys(this.options.customAuth || {})) {
102
+ const mapKey = (key || '').toString().trim().toUpperCase();
103
+ if (mapKey) {
104
+ this.customAuth.set(mapKey, this.options.customAuth[key]);
82
105
  }
83
- this.customAuth.set(mapKey, this.options.customAuth[key]);
84
- });
106
+ }
85
107
 
86
108
  /**
87
109
  * Expose version nr, just for the reference
@@ -124,7 +146,7 @@ class SMTPConnection extends EventEmitter {
124
146
 
125
147
  /**
126
148
  * The socket connecting to the server
127
- * @publick
149
+ * @public
128
150
  */
129
151
  this._socket = false;
130
152
 
@@ -189,6 +211,13 @@ class SMTPConnection extends EventEmitter {
189
211
  */
190
212
  this._closing = false;
191
213
 
214
+ /**
215
+ * Message DATA stream currently piped to the socket, if any. Tracked so
216
+ * close() can unpipe it before tearing the socket down.
217
+ * @private
218
+ */
219
+ this._currentDataStream = false;
220
+
192
221
  /**
193
222
  * Callbacks for socket's listeners
194
223
  */
@@ -197,6 +226,17 @@ class SMTPConnection extends EventEmitter {
197
226
  this._onSocketClose = () => this._onClose();
198
227
  this._onSocketEnd = () => this._onEnd();
199
228
  this._onSocketTimeout = () => this._onTimeout();
229
+
230
+ /**
231
+ * Connection-phase error handler (supports fallback to alternative addresses)
232
+ */
233
+ this._onConnectionSocketError = err => this._onConnectionError(err, 'ESOCKET');
234
+
235
+ /**
236
+ * Connection attempt counter for fallback race condition protection
237
+ * @private
238
+ */
239
+ this._connectionAttemptId = 0;
200
240
  }
201
241
 
202
242
  /**
@@ -232,17 +272,11 @@ class SMTPConnection extends EventEmitter {
232
272
  opts.localAddress = this.options.localAddress;
233
273
  }
234
274
 
235
- let setupConnectionHandlers = () => {
236
- this._connectionTimeout = setTimeout(() => {
237
- this._onError('Connection timeout', 'ETIMEDOUT', false, 'CONN');
238
- }, this.options.connectionTimeout || CONNECTION_TIMEOUT);
239
-
240
- this._socket.on('error', this._onSocketError);
241
- };
242
-
243
275
  if (this.options.connection) {
244
276
  // connection is already opened
245
277
  this._socket = this.options.connection;
278
+ this._setupConnectionHandlers();
279
+
246
280
  if (this.secureConnection && !this.alreadySecured) {
247
281
  setImmediate(() =>
248
282
  this._upgradeConnection(err => {
@@ -260,115 +294,178 @@ class SMTPConnection extends EventEmitter {
260
294
  } else if (this.options.socket) {
261
295
  // socket object is set up but not yet connected
262
296
  this._socket = this.options.socket;
263
- return shared.resolveHostname(opts, (err, resolved) => {
264
- if (err) {
265
- return setImmediate(() => this._onError(err, 'EDNS', false, 'CONN'));
266
- }
267
- this.logger.debug(
268
- {
269
- tnx: 'dns',
270
- source: opts.host,
271
- resolved: resolved.host,
272
- cached: !!resolved.cached
273
- },
274
- 'Resolved %s as %s [cache %s]',
275
- opts.host,
276
- resolved.host,
277
- resolved.cached ? 'hit' : 'miss'
278
- );
279
- Object.keys(resolved).forEach(key => {
280
- if (key.charAt(0) !== '_' && resolved[key]) {
281
- opts[key] = resolved[key];
282
- }
283
- });
297
+ return this._resolveAndConnect(opts, _resolved => {
284
298
  try {
285
299
  this._socket.connect(this.port, this.host, () => {
286
300
  this._socket.setKeepAlive(true);
301
+
302
+ // a `secure` connection over a caller-provided socket must still
303
+ // perform the TLS handshake, otherwise AUTH and the message body
304
+ // would be sent in cleartext despite the caller requesting TLS
305
+ if (this.secureConnection && !this.alreadySecured) {
306
+ return this._upgradeConnection(err => {
307
+ if (err) {
308
+ this._onError(new Error('Error initiating TLS - ' + (err.message || err)), 'ETLS', false, 'CONN');
309
+ return;
310
+ }
311
+ this._onConnect();
312
+ });
313
+ }
314
+
287
315
  this._onConnect();
288
316
  });
289
- setupConnectionHandlers();
317
+ this._setupConnectionHandlers();
290
318
  } catch (E) {
291
319
  return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
292
320
  }
293
321
  });
294
- } else if (this.secureConnection) {
295
- // connect using tls
296
- if (this.options.tls) {
297
- Object.keys(this.options.tls).forEach(key => {
298
- opts[key] = this.options.tls[key];
299
- });
300
- }
322
+ } else {
323
+ if (this.secureConnection) {
324
+ Object.assign(opts, this.options.tls || {});
301
325
 
302
- // ensure servername for SNI
303
- if (this.servername && !opts.servername) {
304
- opts.servername = this.servername;
326
+ // ensure servername for SNI
327
+ if (this.servername && !opts.servername) {
328
+ opts.servername = this.servername;
329
+ }
305
330
  }
306
331
 
307
- return shared.resolveHostname(opts, (err, resolved) => {
308
- if (err) {
309
- return setImmediate(() => this._onError(err, 'EDNS', false, 'CONN'));
310
- }
311
- this.logger.debug(
312
- {
313
- tnx: 'dns',
314
- source: opts.host,
315
- resolved: resolved.host,
316
- cached: !!resolved.cached
317
- },
318
- 'Resolved %s as %s [cache %s]',
319
- opts.host,
320
- resolved.host,
321
- resolved.cached ? 'hit' : 'miss'
322
- );
323
- Object.keys(resolved).forEach(key => {
324
- if (key.charAt(0) !== '_' && resolved[key]) {
325
- opts[key] = resolved[key];
326
- }
327
- });
328
- try {
329
- this._socket = tls.connect(opts, () => {
330
- this._socket.setKeepAlive(true);
331
- this._onConnect();
332
- });
333
- setupConnectionHandlers();
334
- } catch (E) {
335
- return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
336
- }
332
+ return this._resolveAndConnect(opts, resolved => {
333
+ // Store fallback addresses for retry on connection failure
334
+ this._fallbackAddresses = (resolved._addresses || []).filter(addr => addr !== opts.host);
335
+ this._connectOpts = Object.assign({}, opts);
336
+
337
+ this._connectToHost(opts, this.secureConnection);
337
338
  });
338
- } else {
339
- // connect using plaintext
340
- return shared.resolveHostname(opts, (err, resolved) => {
341
- if (err) {
342
- return setImmediate(() => this._onError(err, 'EDNS', false, 'CONN'));
339
+ }
340
+ }
341
+
342
+ /**
343
+ * Resolves the hostname and applies resolved values to opts,
344
+ * then calls the provided callback with the resolved data
345
+ *
346
+ * @param {Object} opts Connection options (modified in place)
347
+ * @param {Function} callback Called with resolved data on success
348
+ */
349
+ _resolveAndConnect(opts, callback) {
350
+ return shared.resolveHostname(opts, (err, resolved) => {
351
+ if (err) {
352
+ return setImmediate(() => this._onError(err, 'EDNS', false, 'CONN'));
353
+ }
354
+ this.logger.debug(
355
+ {
356
+ tnx: 'dns',
357
+ source: opts.host,
358
+ resolved: resolved.host,
359
+ cached: !!resolved.cached
360
+ },
361
+ 'Resolved %s as %s [cache %s]',
362
+ opts.host,
363
+ resolved.host,
364
+ resolved.cached ? 'hit' : 'miss'
365
+ );
366
+ for (const key of Object.keys(resolved)) {
367
+ if (key.charAt(0) !== '_' && resolved[key]) {
368
+ opts[key] = resolved[key];
343
369
  }
344
- this.logger.debug(
345
- {
346
- tnx: 'dns',
347
- source: opts.host,
348
- resolved: resolved.host,
349
- cached: !!resolved.cached
350
- },
351
- 'Resolved %s as %s [cache %s]',
352
- opts.host,
353
- resolved.host,
354
- resolved.cached ? 'hit' : 'miss'
355
- );
356
- Object.keys(resolved).forEach(key => {
357
- if (key.charAt(0) !== '_' && resolved[key]) {
358
- opts[key] = resolved[key];
359
- }
360
- });
361
- try {
362
- this._socket = net.connect(opts, () => {
363
- this._socket.setKeepAlive(true);
364
- this._onConnect();
365
- });
366
- setupConnectionHandlers();
367
- } catch (E) {
368
- return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
370
+ }
371
+ callback(resolved);
372
+ });
373
+ }
374
+
375
+ /**
376
+ * Attempts to connect to the specified host address
377
+ *
378
+ * @param {Object} opts Connection options
379
+ * @param {Boolean} secure Whether to use TLS
380
+ */
381
+ _connectToHost(opts, secure) {
382
+ // If the client was closed while DNS resolution was in flight, do not open
383
+ // a socket here: close() ran with this._socket still unset and so had
384
+ // nothing to tear down, and _onConnect's remedial close() is a no-op once
385
+ // _closing is set — the freshly connected socket would leak.
386
+ if (this._destroyed || this._closing) {
387
+ return;
388
+ }
389
+
390
+ this._connectionAttemptId++;
391
+ const currentAttemptId = this._connectionAttemptId;
392
+
393
+ const connectFn = secure ? tls.connect : net.connect;
394
+ try {
395
+ this._socket = connectFn(opts, () => {
396
+ // Ignore callback if this is a stale connection attempt
397
+ if (this._connectionAttemptId !== currentAttemptId) {
398
+ return;
369
399
  }
400
+ this._socket.setKeepAlive(true);
401
+ this._onConnect();
370
402
  });
403
+ this._setupConnectionHandlers();
404
+ } catch (E) {
405
+ return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Sets up connection timeout and error handlers
411
+ */
412
+ _setupConnectionHandlers() {
413
+ this._connectionTimeout = setTimeout(() => {
414
+ this._onConnectionError('Connection timeout', 'ETIMEDOUT');
415
+ }, this.options.connectionTimeout || CONNECTION_TIMEOUT);
416
+
417
+ this._socket.on('error', this._onConnectionSocketError);
418
+ }
419
+
420
+ /**
421
+ * Handles connection errors with fallback to alternative addresses
422
+ *
423
+ * @param {Error|String} err Error object or message
424
+ * @param {String} code Error code
425
+ */
426
+ _onConnectionError(err, code) {
427
+ clearTimeout(this._connectionTimeout);
428
+
429
+ // Check if we have fallback addresses to try
430
+ const canFallback = this._fallbackAddresses && this._fallbackAddresses.length && this.stage === 'init' && !this._destroyed;
431
+
432
+ if (!canFallback) {
433
+ // No more fallback addresses, report the error
434
+ this._onError(err, code, false, 'CONN');
435
+ return;
436
+ }
437
+
438
+ const nextHost = this._fallbackAddresses.shift();
439
+
440
+ this.logger.info(
441
+ {
442
+ tnx: 'network',
443
+ failedHost: this._connectOpts.host,
444
+ nextHost,
445
+ error: err.message || err
446
+ },
447
+ 'Connection to %s failed, trying %s',
448
+ this._connectOpts.host,
449
+ nextHost
450
+ );
451
+
452
+ // Clean up current socket
453
+ if (this._socket) {
454
+ try {
455
+ this._socket.removeListener('error', this._onConnectionSocketError);
456
+ // Absorb any late teardown error (e.g. a TLS fallback socket emitting
457
+ // after destroy), mirroring the guard used in close()
458
+ this._socket.on('error', TEARDOWN_NOOP);
459
+ this._socket.destroy();
460
+ } catch (_E) {
461
+ // ignore
462
+ }
463
+ this._socket = null;
371
464
  }
465
+
466
+ // Update host and retry
467
+ this._connectOpts.host = nextHost;
468
+ this._connectToHost(this._connectOpts, this.secureConnection);
372
469
  }
373
470
 
374
471
  /**
@@ -393,12 +490,7 @@ class SMTPConnection extends EventEmitter {
393
490
  }
394
491
  this._closing = true;
395
492
 
396
- let closeMethod = 'end';
397
-
398
- if (this.stage === 'init') {
399
- // Close the socket immediately when connection timed out
400
- closeMethod = 'destroy';
401
- }
493
+ const closeMethod = this.stage === 'init' ? 'destroy' : 'end';
402
494
 
403
495
  this.logger.debug(
404
496
  {
@@ -408,12 +500,35 @@ class SMTPConnection extends EventEmitter {
408
500
  closeMethod
409
501
  );
410
502
 
411
- let socket = (this._socket && this._socket.socket) || this._socket;
503
+ const socket = (this._socket && this._socket.socket) || this._socket;
504
+
505
+ // Detach any in-flight DATA stream from the socket so the source stream
506
+ // can be garbage-collected once the socket is gone.
507
+ if (this._currentDataStream) {
508
+ try {
509
+ this._currentDataStream.unpipe(this._socket);
510
+ } catch (_E) {
511
+ // ignore
512
+ }
513
+ this._currentDataStream = false;
514
+ }
412
515
 
413
516
  if (socket && !socket.destroyed) {
414
517
  try {
415
- this._socket[closeMethod]();
416
- } catch (E) {
518
+ // Clear socket timeout to prevent timer leaks
519
+ socket.setTimeout(0);
520
+ // Remove all listeners to allow proper garbage collection
521
+ socket.removeListener('data', this._onSocketData);
522
+ socket.removeListener('timeout', this._onSocketTimeout);
523
+ socket.removeListener('close', this._onSocketClose);
524
+ socket.removeListener('end', this._onSocketEnd);
525
+ socket.removeListener('error', this._onSocketError);
526
+ socket.removeListener('error', this._onConnectionSocketError);
527
+ // Absorb errors that may fire during socket teardown (e.g. server
528
+ // sending cleartext after TLS shutdown triggers ERR_SSL_BAD_RECORD_TYPE)
529
+ socket.on('error', TEARDOWN_NOOP);
530
+ socket[closeMethod]();
531
+ } catch (_E) {
417
532
  // just ignore
418
533
  }
419
534
  }
@@ -454,11 +569,11 @@ class SMTPConnection extends EventEmitter {
454
569
  }
455
570
 
456
571
  if (this.customAuth.has(this._authMethod)) {
457
- let handler = this.customAuth.get(this._authMethod);
572
+ const handler = this.customAuth.get(this._authMethod);
458
573
  let lastResponse;
459
574
  let returned = false;
460
575
 
461
- let resolve = () => {
576
+ const resolve = () => {
462
577
  if (returned) {
463
578
  return;
464
579
  }
@@ -477,7 +592,7 @@ class SMTPConnection extends EventEmitter {
477
592
  callback(null, true);
478
593
  };
479
594
 
480
- let reject = err => {
595
+ const reject = err => {
481
596
  if (returned) {
482
597
  return;
483
598
  }
@@ -485,7 +600,7 @@ class SMTPConnection extends EventEmitter {
485
600
  callback(this._formatError(err, 'EAUTH', lastResponse, 'AUTH ' + this._authMethod));
486
601
  };
487
602
 
488
- let handlerResponse = handler({
603
+ const handlerResponse = handler({
489
604
  auth: this._auth,
490
605
  method: this._authMethod,
491
606
 
@@ -612,7 +727,7 @@ class SMTPConnection extends EventEmitter {
612
727
 
613
728
  // ensure that callback is only called once
614
729
  let returned = false;
615
- let callback = function () {
730
+ const callback = function () {
616
731
  if (returned) {
617
732
  return;
618
733
  }
@@ -625,13 +740,22 @@ class SMTPConnection extends EventEmitter {
625
740
  message.on('error', err => callback(this._formatError(err, 'ESTREAM', false, 'API')));
626
741
  }
627
742
 
628
- let startTime = Date.now();
743
+ const startTime = Date.now();
629
744
  this._setEnvelope(envelope, (err, info) => {
630
745
  if (err) {
746
+ // create passthrough stream to consume to prevent OOM
747
+ const stream = new PassThrough();
748
+ if (typeof message.pipe === 'function') {
749
+ message.pipe(stream);
750
+ } else {
751
+ stream.write(message);
752
+ stream.end();
753
+ }
754
+
631
755
  return callback(err);
632
756
  }
633
- let envelopeTime = Date.now();
634
- let stream = this._createSendStream((err, str) => {
757
+ const envelopeTime = Date.now();
758
+ const stream = this._createSendStream((err, str) => {
635
759
  if (err) {
636
760
  return callback(err);
637
761
  }
@@ -658,6 +782,11 @@ class SMTPConnection extends EventEmitter {
658
782
  * @param {Function} callback Callback to return once connection is reset
659
783
  */
660
784
  reset(callback) {
785
+ const isDestroyedMessage = this._isDestroyedMessage('reset');
786
+ if (isDestroyedMessage) {
787
+ return callback(this._formatError(isDestroyedMessage, 'ECONNECTION', false, 'API'));
788
+ }
789
+
661
790
  this._sendCommand('RSET');
662
791
  this._responseActions.push(str => {
663
792
  if (str.charAt(0) !== '2') {
@@ -704,7 +833,13 @@ class SMTPConnection extends EventEmitter {
704
833
  this._socket.removeListener('timeout', this._onSocketTimeout);
705
834
  this._socket.removeListener('close', this._onSocketClose);
706
835
  this._socket.removeListener('end', this._onSocketEnd);
836
+ // Switch from connection-phase error handler to normal error handler
837
+ this._socket.removeListener('error', this._onConnectionSocketError);
838
+ // _upgradeConnection (options.connection + secure) may already have attached
839
+ // the normal handler; remove it first so we never end up with a duplicate
840
+ this._socket.removeListener('error', this._onSocketError);
707
841
 
842
+ this._socket.on('error', this._onSocketError);
708
843
  this._socket.on('data', this._onSocketData);
709
844
  this._socket.once('close', this._onSocketClose);
710
845
  this._socket.once('end', this._onSocketEnd);
@@ -736,7 +871,7 @@ class SMTPConnection extends EventEmitter {
736
871
  return;
737
872
  }
738
873
 
739
- let data = (chunk || '').toString('binary');
874
+ let data = chunk.toString('binary');
740
875
  let lines = (this._remainder + data).split(/\r?\n/);
741
876
  let lastline;
742
877
 
@@ -783,7 +918,12 @@ class SMTPConnection extends EventEmitter {
783
918
 
784
919
  err = this._formatError(err, type, data, command);
785
920
 
786
- this.logger.error(data, err.message);
921
+ const transientCodes = ['ETIMEDOUT', 'ESOCKET', 'ECONNECTION'];
922
+ if (transientCodes.includes(err.code)) {
923
+ this.logger.warn(data, err.message);
924
+ } else {
925
+ this.logger.error(data, err.message);
926
+ }
787
927
 
788
928
  this.emit('error', err);
789
929
  this.close();
@@ -807,7 +947,7 @@ class SMTPConnection extends EventEmitter {
807
947
  err.message += ': ' + response;
808
948
  }
809
949
 
810
- let responseCode = (typeof response === 'string' && Number((response.match(/^\d+/) || [])[0])) || false;
950
+ const responseCode = (typeof response === 'string' && Number((response.match(/^\d+/) || [])[0])) || false;
811
951
  if (responseCode) {
812
952
  err.responseCode = responseCode;
813
953
  }
@@ -828,15 +968,15 @@ class SMTPConnection extends EventEmitter {
828
968
  let serverResponse = false;
829
969
 
830
970
  if (this._remainder && this._remainder.trim()) {
971
+ this.lastServerResponse = serverResponse = decodeServerResponse(this._remainder.trim());
831
972
  if (this.options.debug || this.options.transactionLog) {
832
973
  this.logger.debug(
833
974
  {
834
975
  tnx: 'server'
835
976
  },
836
- this._remainder.replace(/\r?\n$/, '')
977
+ serverResponse
837
978
  );
838
979
  }
839
- this.lastServerResponse = serverResponse = this._remainder.trim();
840
980
  }
841
981
 
842
982
  this.logger.info(
@@ -864,7 +1004,9 @@ class SMTPConnection extends EventEmitter {
864
1004
  */
865
1005
  _onEnd() {
866
1006
  if (this._socket && !this._socket.destroyed) {
867
- this._socket.destroy();
1007
+ // Peer sent FIN — finish our half of the close gracefully rather
1008
+ // than destroying. 'close' fires after the OS finalizes teardown.
1009
+ this._socket.end();
868
1010
  }
869
1011
  }
870
1012
 
@@ -885,6 +1027,8 @@ class SMTPConnection extends EventEmitter {
885
1027
  return;
886
1028
  }
887
1029
  this._destroyed = true;
1030
+ // keep the documented public flag in sync with the private state
1031
+ this.destroyed = true;
888
1032
  this.emit('end');
889
1033
  }
890
1034
 
@@ -895,6 +1039,15 @@ class SMTPConnection extends EventEmitter {
895
1039
  * has been secured
896
1040
  */
897
1041
  _upgradeConnection(callback) {
1042
+ // RFC 3207 section 6: the client MUST discard any knowledge obtained from
1043
+ // the server that was not received over the TLS-protected session. Drop any
1044
+ // buffered input received before the handshake so a man-in-the-middle cannot
1045
+ // inject plaintext bytes after the "220" reply (e.g. a CRLF-free fragment that
1046
+ // would otherwise be prepended to the first post-TLS response and parsed as
1047
+ // part of the secured EHLO capabilities). STARTTLS response injection.
1048
+ this._remainder = '';
1049
+ this._responseQueue = [];
1050
+
898
1051
  // do not remove all listeners or it breaks node v0.10 as there's
899
1052
  // apparently a 'finish' event set that would be cleared as well
900
1053
 
@@ -902,21 +1055,32 @@ class SMTPConnection extends EventEmitter {
902
1055
  this._socket.removeListener('data', this._onSocketData); // incoming data is going to be gibberish from this point onwards
903
1056
  this._socket.removeListener('timeout', this._onSocketTimeout); // timeout will be re-set for the new socket object
904
1057
 
905
- let socketPlain = this._socket;
906
- let opts = {
907
- socket: this._socket,
908
- host: this.host
909
- };
910
-
911
- Object.keys(this.options.tls || {}).forEach(key => {
912
- opts[key] = this.options.tls[key];
913
- });
1058
+ const socketPlain = this._socket;
1059
+ const opts = Object.assign(
1060
+ {
1061
+ socket: this._socket,
1062
+ host: this.host
1063
+ },
1064
+ this.options.tls || {}
1065
+ );
914
1066
 
915
1067
  // ensure servername for SNI
916
1068
  if (this.servername && !opts.servername) {
917
1069
  opts.servername = this.servername;
918
1070
  }
919
1071
 
1072
+ // Remove all listeners from the plain socket to allow proper garbage
1073
+ // collection. Used on both the TLS-success path and the synchronous
1074
+ // tls.connect() throw path; either way the plain socket is done.
1075
+ const removePlainSocketListeners = () => {
1076
+ socketPlain.removeListener('close', this._onSocketClose);
1077
+ socketPlain.removeListener('end', this._onSocketEnd);
1078
+ socketPlain.removeListener('error', this._onSocketError);
1079
+ // the connection-phase handler is attached when upgrading a pre-opened
1080
+ // options.connection socket; strip it so nothing lingers on the plain socket
1081
+ socketPlain.removeListener('error', this._onConnectionSocketError);
1082
+ };
1083
+
920
1084
  this.upgrading = true;
921
1085
  // tls.connect is not an asynchronous function however it may still throw errors and requires to be wrapped with try/catch
922
1086
  try {
@@ -925,12 +1089,12 @@ class SMTPConnection extends EventEmitter {
925
1089
  this.upgrading = false;
926
1090
  this._socket.on('data', this._onSocketData);
927
1091
 
928
- socketPlain.removeListener('close', this._onSocketClose);
929
- socketPlain.removeListener('end', this._onSocketEnd);
1092
+ removePlainSocketListeners();
930
1093
 
931
1094
  return callback(null, true);
932
1095
  });
933
1096
  } catch (err) {
1097
+ removePlainSocketListeners();
934
1098
  return callback(err);
935
1099
  }
936
1100
 
@@ -947,18 +1111,27 @@ class SMTPConnection extends EventEmitter {
947
1111
 
948
1112
  /**
949
1113
  * Processes queued responses from the server
950
- *
951
- * @param {Boolean} force If true, ignores _processing flag
952
1114
  */
953
1115
  _processResponse() {
954
1116
  if (!this._responseQueue.length) {
955
1117
  return false;
956
1118
  }
957
1119
 
958
- let str = (this.lastServerResponse = (this._responseQueue.shift() || '').toString());
1120
+ const raw = (this._responseQueue.shift() || '').toString();
1121
+
1122
+ // Skip unexpected empty lines without consuming a response action or
1123
+ // overwriting lastServerResponse; reprocess whatever else is queued.
1124
+ if (!raw.trim()) {
1125
+ setImmediate(() => this._processResponse());
1126
+ return;
1127
+ }
1128
+
1129
+ let str = (this.lastServerResponse = decodeServerResponse(raw));
959
1130
 
960
1131
  if (/^\d+-/.test(str.split('\n').pop())) {
961
- // keep waiting for the final part of multiline response
1132
+ // last line is still a continuation: put the partial response back on the
1133
+ // queue and wait for the rest rather than dropping it
1134
+ this._responseQueue.unshift(raw);
962
1135
  return;
963
1136
  }
964
1137
 
@@ -971,12 +1144,7 @@ class SMTPConnection extends EventEmitter {
971
1144
  );
972
1145
  }
973
1146
 
974
- if (!str.trim()) {
975
- // skip unexpected empty lines
976
- setImmediate(() => this._processResponse());
977
- }
978
-
979
- let action = this._responseActions.shift();
1147
+ const action = this._responseActions.shift();
980
1148
 
981
1149
  if (typeof action === 'function') {
982
1150
  action.call(this, str);
@@ -1024,7 +1192,7 @@ class SMTPConnection extends EventEmitter {
1024
1192
  * {from:{address:'...',name:'...'}, to:[address:'...',name:'...']}
1025
1193
  */
1026
1194
  _setEnvelope(envelope, callback) {
1027
- let args = [];
1195
+ const args = [];
1028
1196
  let useSmtpUtf8 = false;
1029
1197
 
1030
1198
  this._envelope = envelope || {};
@@ -1059,7 +1227,7 @@ class SMTPConnection extends EventEmitter {
1059
1227
  }
1060
1228
 
1061
1229
  // clone the recipients array for latter manipulation
1062
- this._envelope.rcptQueue = JSON.parse(JSON.stringify(this._envelope.to || []));
1230
+ this._envelope.rcptQueue = [].concat(this._envelope.to || []);
1063
1231
  this._envelope.rejected = [];
1064
1232
  this._envelope.rejectedErrors = [];
1065
1233
  this._envelope.accepted = [];
@@ -1072,6 +1240,23 @@ class SMTPConnection extends EventEmitter {
1072
1240
  }
1073
1241
  }
1074
1242
 
1243
+ // RFC 8689: validate REQUIRETLS eligibility before queuing the MAIL FROM
1244
+ // response action, so a rejection here cannot leave an orphaned action in
1245
+ // _responseActions (which would consume the next reply and desync a reused
1246
+ // connection).
1247
+ if (this._envelope.requireTLSExtensionEnabled) {
1248
+ if (!this.secure) {
1249
+ return callback(
1250
+ this._formatError('REQUIRETLS can only be used over TLS connections (RFC 8689)', 'EREQUIRETLS', false, 'MAIL FROM')
1251
+ );
1252
+ }
1253
+ if (!this._supportedExtensions.includes('REQUIRETLS')) {
1254
+ return callback(
1255
+ this._formatError('Server does not support REQUIRETLS extension (RFC 8689)', 'EREQUIRETLS', false, 'MAIL FROM')
1256
+ );
1257
+ }
1258
+ }
1259
+
1075
1260
  this._responseActions.push(str => {
1076
1261
  this._actionMAIL(str, callback);
1077
1262
  });
@@ -1091,7 +1276,10 @@ class SMTPConnection extends EventEmitter {
1091
1276
  }
1092
1277
 
1093
1278
  if (this._envelope.size && this._supportedExtensions.includes('SIZE')) {
1094
- args.push('SIZE=' + this._envelope.size);
1279
+ const sizeValue = Number(this._envelope.size) || 0;
1280
+ if (sizeValue > 0) {
1281
+ args.push('SIZE=' + sizeValue);
1282
+ }
1095
1283
  }
1096
1284
 
1097
1285
  // If the server supports DSN and the envelope includes an DSN prop
@@ -1105,6 +1293,13 @@ class SMTPConnection extends EventEmitter {
1105
1293
  }
1106
1294
  }
1107
1295
 
1296
+ // RFC 8689: append the REQUIRETLS keyword to MAIL FROM. Eligibility
1297
+ // (TLS connection + server support) was already validated above, before
1298
+ // the response action was queued.
1299
+ if (this._envelope.requireTLSExtensionEnabled) {
1300
+ args.push('REQUIRETLS');
1301
+ }
1302
+
1108
1303
  this._sendCommand('MAIL FROM:<' + this._envelope.from + '>' + (args.length ? ' ' + args.join(' ') : ''));
1109
1304
  }
1110
1305
 
@@ -1127,7 +1322,7 @@ class SMTPConnection extends EventEmitter {
1127
1322
  throw new Error('ret: ' + JSON.stringify(ret));
1128
1323
  }
1129
1324
 
1130
- let envid = (params.envid || params.id || '').toString() || null;
1325
+ const envid = (params.envid || params.id || '').toString() || null;
1131
1326
 
1132
1327
  let notify = params.notify || null;
1133
1328
  if (notify) {
@@ -1135,9 +1330,9 @@ class SMTPConnection extends EventEmitter {
1135
1330
  notify = notify.split(',');
1136
1331
  }
1137
1332
  notify = notify.map(n => n.trim().toUpperCase());
1138
- let validNotify = ['NEVER', 'SUCCESS', 'FAILURE', 'DELAY'];
1139
- let invaliNotify = notify.filter(n => !validNotify.includes(n));
1140
- if (invaliNotify.length || (notify.length > 1 && notify.includes('NEVER'))) {
1333
+ const validNotify = ['NEVER', 'SUCCESS', 'FAILURE', 'DELAY'];
1334
+ const invalidNotify = notify.filter(n => !validNotify.includes(n));
1335
+ if (invalidNotify.length || (notify.length > 1 && notify.includes('NEVER'))) {
1141
1336
  throw new Error('notify: ' + JSON.stringify(notify.join(',')));
1142
1337
  }
1143
1338
  notify = notify.join(',');
@@ -1157,7 +1352,7 @@ class SMTPConnection extends EventEmitter {
1157
1352
  }
1158
1353
 
1159
1354
  _getDsnRcptToArgs() {
1160
- let args = [];
1355
+ const args = [];
1161
1356
  // If the server supports DSN and the envelope includes an DSN prop
1162
1357
  // then append DSN params to the RCPT TO command
1163
1358
  if (this._envelope.dsn && this._supportedExtensions.includes('DSN')) {
@@ -1172,12 +1367,11 @@ class SMTPConnection extends EventEmitter {
1172
1367
  }
1173
1368
 
1174
1369
  _createSendStream(callback) {
1175
- let dataStream = new DataStream();
1176
- let logStream;
1370
+ const dataStream = new DataStream();
1177
1371
 
1178
1372
  if (this.options.lmtp) {
1179
1373
  this._envelope.accepted.forEach((recipient, i) => {
1180
- let final = i === this._envelope.accepted.length - 1;
1374
+ const final = i === this._envelope.accepted.length - 1;
1181
1375
  this._responseActions.push(str => {
1182
1376
  this._actionLMTPStream(recipient, final, str, callback);
1183
1377
  });
@@ -1188,12 +1382,13 @@ class SMTPConnection extends EventEmitter {
1188
1382
  });
1189
1383
  }
1190
1384
 
1385
+ this._currentDataStream = dataStream;
1191
1386
  dataStream.pipe(this._socket, {
1192
1387
  end: false
1193
1388
  });
1194
1389
 
1195
1390
  if (this.options.debug) {
1196
- logStream = new PassThrough();
1391
+ const logStream = new PassThrough();
1197
1392
  logStream.on('readable', () => {
1198
1393
  let chunk;
1199
1394
  while ((chunk = logStream.read())) {
@@ -1209,6 +1404,9 @@ class SMTPConnection extends EventEmitter {
1209
1404
  }
1210
1405
 
1211
1406
  dataStream.once('end', () => {
1407
+ if (this._currentDataStream === dataStream) {
1408
+ this._currentDataStream = false;
1409
+ }
1212
1410
  this.logger.info(
1213
1411
  {
1214
1412
  tnx: 'message',
@@ -1283,7 +1481,12 @@ class SMTPConnection extends EventEmitter {
1283
1481
 
1284
1482
  if (str.charAt(0) !== '2') {
1285
1483
  if (this.options.requireTLS) {
1286
- this._onError(new Error('EHLO failed but HELO does not support required STARTTLS. response=' + str), 'ECONNECTION', str, 'EHLO');
1484
+ this._onError(
1485
+ new Error('EHLO failed but HELO does not support required STARTTLS. response=' + str),
1486
+ 'ECONNECTION',
1487
+ str,
1488
+ 'EHLO'
1489
+ );
1287
1490
  return;
1288
1491
  }
1289
1492
 
@@ -1321,6 +1524,11 @@ class SMTPConnection extends EventEmitter {
1321
1524
  this._supportedExtensions.push('8BITMIME');
1322
1525
  }
1323
1526
 
1527
+ // Detect if the server supports REQUIRETLS (RFC 8689)
1528
+ if (/[ -]REQUIRETLS\b/im.test(str)) {
1529
+ this._supportedExtensions.push('REQUIRETLS');
1530
+ }
1531
+
1324
1532
  // Detect if the server supports PIPELINING
1325
1533
  if (/[ -]PIPELINING\b/im.test(str)) {
1326
1534
  this._supportedExtensions.push('PIPELINING');
@@ -1461,22 +1669,21 @@ class SMTPConnection extends EventEmitter {
1461
1669
  * @param {String} str Message from the server
1462
1670
  */
1463
1671
  _actionAUTH_CRAM_MD5(str, callback) {
1464
- let challengeMatch = str.match(/^334\s+(.+)$/);
1465
- let challengeString = '';
1672
+ const challengeMatch = str.match(/^334\s+(.+)$/);
1466
1673
 
1467
1674
  if (!challengeMatch) {
1468
- return callback(this._formatError('Invalid login sequence while waiting for server challenge string', 'EAUTH', str, 'AUTH CRAM-MD5'));
1469
- } else {
1470
- challengeString = challengeMatch[1];
1675
+ return callback(
1676
+ this._formatError('Invalid login sequence while waiting for server challenge string', 'EAUTH', str, 'AUTH CRAM-MD5')
1677
+ );
1471
1678
  }
1472
1679
 
1473
1680
  // Decode from base64
1474
- let base64decoded = Buffer.from(challengeString, 'base64').toString('ascii'),
1475
- hmacMD5 = crypto.createHmac('md5', this._auth.credentials.pass);
1681
+ const base64decoded = Buffer.from(challengeMatch[1], 'base64').toString('ascii');
1682
+ const hmacMD5 = crypto.createHmac('md5', this._auth.credentials.pass);
1476
1683
 
1477
1684
  hmacMD5.update(base64decoded);
1478
1685
 
1479
- let prepended = this._auth.credentials.user + ' ' + hmacMD5.digest('hex');
1686
+ const prepended = this._auth.credentials.user + ' ' + hmacMD5.digest('hex');
1480
1687
 
1481
1688
  this._responseActions.push(str => {
1482
1689
  this._actionAUTH_CRAM_MD5_PASS(str, callback);
@@ -1597,39 +1804,29 @@ class SMTPConnection extends EventEmitter {
1597
1804
  * @param {String} str Message from the server
1598
1805
  */
1599
1806
  _actionMAIL(str, callback) {
1600
- let message, curRecipient;
1601
1807
  if (Number(str.charAt(0)) !== 2) {
1602
- if (this._usingSmtpUtf8 && /^550 /.test(str) && /[\x80-\uFFFF]/.test(this._envelope.from)) {
1603
- message = 'Internationalized mailbox name not allowed';
1604
- } else {
1605
- message = 'Mail command failed';
1606
- }
1808
+ const message =
1809
+ this._usingSmtpUtf8 && /^550 /.test(str) && /[\x80-\uFFFF]/.test(this._envelope.from)
1810
+ ? 'Internationalized mailbox name not allowed'
1811
+ : 'Mail command failed';
1607
1812
  return callback(this._formatError(message, 'EENVELOPE', str, 'MAIL FROM'));
1608
1813
  }
1609
1814
 
1610
1815
  if (!this._envelope.rcptQueue.length) {
1611
- return callback(this._formatError('Can\x27t send mail - no recipients defined', 'EENVELOPE', false, 'API'));
1612
- } else {
1613
- this._recipientQueue = [];
1614
-
1615
- if (this._supportedExtensions.includes('PIPELINING')) {
1616
- while (this._envelope.rcptQueue.length) {
1617
- curRecipient = this._envelope.rcptQueue.shift();
1618
- this._recipientQueue.push(curRecipient);
1619
- this._responseActions.push(str => {
1620
- this._actionRCPT(str, callback);
1621
- });
1622
- this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
1623
- }
1624
- } else {
1625
- curRecipient = this._envelope.rcptQueue.shift();
1626
- this._recipientQueue.push(curRecipient);
1627
- this._responseActions.push(str => {
1628
- this._actionRCPT(str, callback);
1629
- });
1630
- this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
1631
- }
1816
+ return callback(this._formatError("Can't send mail - no recipients defined", 'EENVELOPE', false, 'API'));
1632
1817
  }
1818
+
1819
+ this._recipientQueue = [];
1820
+ const usePipelining = this._supportedExtensions.includes('PIPELINING');
1821
+
1822
+ do {
1823
+ const curRecipient = this._envelope.rcptQueue.shift();
1824
+ this._recipientQueue.push(curRecipient);
1825
+ this._responseActions.push(str => {
1826
+ this._actionRCPT(str, callback);
1827
+ });
1828
+ this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
1829
+ } while (usePipelining && this._envelope.rcptQueue.length);
1633
1830
  }
1634
1831
 
1635
1832
  /**
@@ -1638,16 +1835,14 @@ class SMTPConnection extends EventEmitter {
1638
1835
  * @param {String} str Message from the server
1639
1836
  */
1640
1837
  _actionRCPT(str, callback) {
1641
- let message,
1642
- err,
1643
- curRecipient = this._recipientQueue.shift();
1838
+ let err;
1839
+ const curRecipient = this._recipientQueue.shift();
1644
1840
  if (Number(str.charAt(0)) !== 2) {
1645
1841
  // this is a soft error
1646
- if (this._usingSmtpUtf8 && /^553 /.test(str) && /[\x80-\uFFFF]/.test(curRecipient)) {
1647
- message = 'Internationalized mailbox name not allowed';
1648
- } else {
1649
- message = 'Recipient command failed';
1650
- }
1842
+ const message =
1843
+ this._usingSmtpUtf8 && /^553 /.test(str) && /[\x80-\uFFFF]/.test(curRecipient)
1844
+ ? 'Internationalized mailbox name not allowed'
1845
+ : 'Recipient command failed';
1651
1846
  this._envelope.rejected.push(curRecipient);
1652
1847
  // store error for the failed recipient
1653
1848
  err = this._formatError(message, 'EENVELOPE', str, 'RCPT TO');
@@ -1664,18 +1859,18 @@ class SMTPConnection extends EventEmitter {
1664
1859
  });
1665
1860
  this._sendCommand('DATA');
1666
1861
  } else {
1667
- err = this._formatError('Can\x27t send mail - all recipients were rejected', 'EENVELOPE', str, 'RCPT TO');
1862
+ err = this._formatError("Can't send mail - all recipients were rejected", 'EENVELOPE', str, 'RCPT TO');
1668
1863
  err.rejected = this._envelope.rejected;
1669
1864
  err.rejectedErrors = this._envelope.rejectedErrors;
1670
1865
  return callback(err);
1671
1866
  }
1672
1867
  } else if (this._envelope.rcptQueue.length) {
1673
- curRecipient = this._envelope.rcptQueue.shift();
1674
- this._recipientQueue.push(curRecipient);
1868
+ const nextRecipient = this._envelope.rcptQueue.shift();
1869
+ this._recipientQueue.push(nextRecipient);
1675
1870
  this._responseActions.push(str => {
1676
1871
  this._actionRCPT(str, callback);
1677
1872
  });
1678
- this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
1873
+ this._sendCommand('RCPT TO:<' + nextRecipient + '>' + this._getDsnRcptToArgs());
1679
1874
  }
1680
1875
  }
1681
1876
 
@@ -1691,7 +1886,7 @@ class SMTPConnection extends EventEmitter {
1691
1886
  return callback(this._formatError('Data command failed', 'EENVELOPE', str, 'DATA'));
1692
1887
  }
1693
1888
 
1694
- let response = {
1889
+ const response = {
1695
1890
  accepted: this._envelope.accepted,
1696
1891
  rejected: this._envelope.rejected
1697
1892
  };
@@ -1715,12 +1910,9 @@ class SMTPConnection extends EventEmitter {
1715
1910
  */
1716
1911
  _actionSMTPStream(str, callback) {
1717
1912
  if (Number(str.charAt(0)) !== 2) {
1718
- // Message failed
1719
1913
  return callback(this._formatError('Message failed', 'EMESSAGE', str, 'DATA'));
1720
- } else {
1721
- // Message sent succesfully
1722
- return callback(null, str);
1723
1914
  }
1915
+ return callback(null, str);
1724
1916
  }
1725
1917
 
1726
1918
  /**
@@ -1803,7 +1995,7 @@ class SMTPConnection extends EventEmitter {
1803
1995
  let defaultHostname;
1804
1996
  try {
1805
1997
  defaultHostname = os.hostname() || '';
1806
- } catch (err) {
1998
+ } catch (_err) {
1807
1999
  // fails on windows 7
1808
2000
  defaultHostname = 'localhost';
1809
2001
  }