@nocobase/plugin-notification-email 2.3.0-beta.12 → 2.3.0-beta.13

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 (48) hide show
  1. package/dist/client/index.js +1 -1
  2. package/dist/client-v2/locale.d.ts +1 -1
  3. package/dist/externalVersion.js +7 -7
  4. package/dist/node_modules/nodemailer/.ncurc.js +2 -0
  5. package/dist/node_modules/nodemailer/.prettierignore +8 -0
  6. package/dist/node_modules/nodemailer/.prettierrc +12 -0
  7. package/dist/node_modules/nodemailer/.prettierrc.js +2 -0
  8. package/dist/node_modules/nodemailer/.release-please-config.json +9 -0
  9. package/dist/node_modules/nodemailer/eslint.config.js +88 -0
  10. package/dist/node_modules/nodemailer/lib/addressparser/index.js +314 -71
  11. package/dist/node_modules/nodemailer/lib/base64/index.js +9 -13
  12. package/dist/node_modules/nodemailer/lib/dkim/index.js +17 -18
  13. package/dist/node_modules/nodemailer/lib/dkim/message-parser.js +12 -13
  14. package/dist/node_modules/nodemailer/lib/dkim/relaxed-body.js +3 -3
  15. package/dist/node_modules/nodemailer/lib/dkim/sign.js +23 -19
  16. package/dist/node_modules/nodemailer/lib/errors.js +59 -0
  17. package/dist/node_modules/nodemailer/lib/fetch/cookies.js +14 -19
  18. package/dist/node_modules/nodemailer/lib/fetch/index.js +164 -27
  19. package/dist/node_modules/nodemailer/lib/json-transport/index.js +4 -4
  20. package/dist/node_modules/nodemailer/lib/mail-composer/index.js +235 -161
  21. package/dist/node_modules/nodemailer/lib/mailer/index.js +106 -52
  22. package/dist/node_modules/nodemailer/lib/mailer/mail-message.js +106 -68
  23. package/dist/node_modules/nodemailer/lib/mime-funcs/index.js +105 -59
  24. package/dist/node_modules/nodemailer/lib/mime-funcs/mime-types.js +21 -14
  25. package/dist/node_modules/nodemailer/lib/mime-node/index.js +399 -138
  26. package/dist/node_modules/nodemailer/lib/mime-node/last-newline.js +1 -1
  27. package/dist/node_modules/nodemailer/lib/mime-node/le-unix.js +4 -7
  28. package/dist/node_modules/nodemailer/lib/mime-node/le-windows.js +1 -4
  29. package/dist/node_modules/nodemailer/lib/nodemailer.js +1 -1
  30. package/dist/node_modules/nodemailer/lib/punycode/index.js +460 -0
  31. package/dist/node_modules/nodemailer/lib/qp/index.js +33 -22
  32. package/dist/node_modules/nodemailer/lib/sendmail-transport/index.js +54 -44
  33. package/dist/node_modules/nodemailer/lib/ses-transport/index.js +110 -193
  34. package/dist/node_modules/nodemailer/lib/shared/index.js +316 -207
  35. package/dist/node_modules/nodemailer/lib/shared/objects.js +42 -0
  36. package/dist/node_modules/nodemailer/lib/shared/url.js +151 -0
  37. package/dist/node_modules/nodemailer/lib/smtp-connection/data-stream.js +3 -6
  38. package/dist/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +50 -19
  39. package/dist/node_modules/nodemailer/lib/smtp-connection/index.js +423 -218
  40. package/dist/node_modules/nodemailer/lib/smtp-pool/index.js +27 -34
  41. package/dist/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +19 -16
  42. package/dist/node_modules/nodemailer/lib/smtp-transport/index.js +66 -53
  43. package/dist/node_modules/nodemailer/lib/stream-transport/index.js +16 -7
  44. package/dist/node_modules/nodemailer/lib/well-known/index.js +7 -7
  45. package/dist/node_modules/nodemailer/lib/well-known/services.json +333 -38
  46. package/dist/node_modules/nodemailer/lib/xoauth2/index.js +105 -33
  47. package/dist/node_modules/nodemailer/package.json +1 -1
  48. package/package.json +3 -3
@@ -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
@@ -58,6 +81,8 @@ class SMTPConnection extends EventEmitter {
58
81
  this.port = Number(this.options.port) || (this.secureConnection ? 465 : 587);
59
82
  this.host = this.options.host || 'localhost';
60
83
 
84
+ this.servername = this.options.servername ? this.options.servername : !net.isIP(this.host) ? this.host : false;
85
+
61
86
  this.allowInternalNetworkInterfaces = this.options.allowInternalNetworkInterfaces || false;
62
87
 
63
88
  if (typeof this.options.secure === 'undefined' && this.port === 465) {
@@ -65,7 +90,7 @@ class SMTPConnection extends EventEmitter {
65
90
  this.secureConnection = true;
66
91
  }
67
92
 
68
- this.name = this.options.name || this._getHostname();
93
+ this.name = (this.options.name || this._getHostname()).toString().replace(/[\r\n]+/g, '');
69
94
 
70
95
  this.logger = shared.getLogger(this.options, {
71
96
  component: this.options.component || 'smtp-connection',
@@ -73,13 +98,12 @@ class SMTPConnection extends EventEmitter {
73
98
  });
74
99
 
75
100
  this.customAuth = new Map();
76
- Object.keys(this.options.customAuth || {}).forEach(key => {
77
- let mapKey = (key || '').toString().trim().toUpperCase();
78
- if (!mapKey) {
79
- 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]);
80
105
  }
81
- this.customAuth.set(mapKey, this.options.customAuth[key]);
82
- });
106
+ }
83
107
 
84
108
  /**
85
109
  * Expose version nr, just for the reference
@@ -122,7 +146,7 @@ class SMTPConnection extends EventEmitter {
122
146
 
123
147
  /**
124
148
  * The socket connecting to the server
125
- * @publick
149
+ * @public
126
150
  */
127
151
  this._socket = false;
128
152
 
@@ -187,6 +211,13 @@ class SMTPConnection extends EventEmitter {
187
211
  */
188
212
  this._closing = false;
189
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
+
190
221
  /**
191
222
  * Callbacks for socket's listeners
192
223
  */
@@ -195,6 +226,17 @@ class SMTPConnection extends EventEmitter {
195
226
  this._onSocketClose = () => this._onClose();
196
227
  this._onSocketEnd = () => this._onEnd();
197
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;
198
240
  }
199
241
 
200
242
  /**
@@ -230,17 +272,11 @@ class SMTPConnection extends EventEmitter {
230
272
  opts.localAddress = this.options.localAddress;
231
273
  }
232
274
 
233
- let setupConnectionHandlers = () => {
234
- this._connectionTimeout = setTimeout(() => {
235
- this._onError('Connection timeout', 'ETIMEDOUT', false, 'CONN');
236
- }, this.options.connectionTimeout || CONNECTION_TIMEOUT);
237
-
238
- this._socket.on('error', this._onSocketError);
239
- };
240
-
241
275
  if (this.options.connection) {
242
276
  // connection is already opened
243
277
  this._socket = this.options.connection;
278
+ this._setupConnectionHandlers();
279
+
244
280
  if (this.secureConnection && !this.alreadySecured) {
245
281
  setImmediate(() =>
246
282
  this._upgradeConnection(err => {
@@ -258,109 +294,178 @@ class SMTPConnection extends EventEmitter {
258
294
  } else if (this.options.socket) {
259
295
  // socket object is set up but not yet connected
260
296
  this._socket = this.options.socket;
261
- return shared.resolveHostname(opts, (err, resolved) => {
262
- if (err) {
263
- return setImmediate(() => this._onError(err, 'EDNS', false, 'CONN'));
264
- }
265
- this.logger.debug(
266
- {
267
- tnx: 'dns',
268
- source: opts.host,
269
- resolved: resolved.host,
270
- cached: !!resolved.cached
271
- },
272
- 'Resolved %s as %s [cache %s]',
273
- opts.host,
274
- resolved.host,
275
- resolved.cached ? 'hit' : 'miss'
276
- );
277
- Object.keys(resolved).forEach(key => {
278
- if (key.charAt(0) !== '_' && resolved[key]) {
279
- opts[key] = resolved[key];
280
- }
281
- });
297
+ return this._resolveAndConnect(opts, _resolved => {
282
298
  try {
283
299
  this._socket.connect(this.port, this.host, () => {
284
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
+
285
315
  this._onConnect();
286
316
  });
287
- setupConnectionHandlers();
317
+ this._setupConnectionHandlers();
288
318
  } catch (E) {
289
319
  return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
290
320
  }
291
321
  });
292
- } else if (this.secureConnection) {
293
- // connect using tls
294
- if (this.options.tls) {
295
- Object.keys(this.options.tls).forEach(key => {
296
- opts[key] = this.options.tls[key];
297
- });
298
- }
299
- return shared.resolveHostname(opts, (err, resolved) => {
300
- if (err) {
301
- return setImmediate(() => this._onError(err, 'EDNS', false, 'CONN'));
302
- }
303
- this.logger.debug(
304
- {
305
- tnx: 'dns',
306
- source: opts.host,
307
- resolved: resolved.host,
308
- cached: !!resolved.cached
309
- },
310
- 'Resolved %s as %s [cache %s]',
311
- opts.host,
312
- resolved.host,
313
- resolved.cached ? 'hit' : 'miss'
314
- );
315
- Object.keys(resolved).forEach(key => {
316
- if (key.charAt(0) !== '_' && resolved[key]) {
317
- opts[key] = resolved[key];
318
- }
319
- });
320
- try {
321
- this._socket = tls.connect(opts, () => {
322
- this._socket.setKeepAlive(true);
323
- this._onConnect();
324
- });
325
- setupConnectionHandlers();
326
- } catch (E) {
327
- return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
322
+ } else {
323
+ if (this.secureConnection) {
324
+ Object.assign(opts, this.options.tls || {});
325
+
326
+ // ensure servername for SNI
327
+ if (this.servername && !opts.servername) {
328
+ opts.servername = this.servername;
328
329
  }
330
+ }
331
+
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);
329
338
  });
330
- } else {
331
- // connect using plaintext
332
- return shared.resolveHostname(opts, (err, resolved) => {
333
- if (err) {
334
- 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];
335
369
  }
336
- this.logger.debug(
337
- {
338
- tnx: 'dns',
339
- source: opts.host,
340
- resolved: resolved.host,
341
- cached: !!resolved.cached
342
- },
343
- 'Resolved %s as %s [cache %s]',
344
- opts.host,
345
- resolved.host,
346
- resolved.cached ? 'hit' : 'miss'
347
- );
348
- Object.keys(resolved).forEach(key => {
349
- if (key.charAt(0) !== '_' && resolved[key]) {
350
- opts[key] = resolved[key];
351
- }
352
- });
353
- try {
354
- this._socket = net.connect(opts, () => {
355
- this._socket.setKeepAlive(true);
356
- this._onConnect();
357
- });
358
- setupConnectionHandlers();
359
- } catch (E) {
360
- 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;
361
399
  }
400
+ this._socket.setKeepAlive(true);
401
+ this._onConnect();
362
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;
363
464
  }
465
+
466
+ // Update host and retry
467
+ this._connectOpts.host = nextHost;
468
+ this._connectToHost(this._connectOpts, this.secureConnection);
364
469
  }
365
470
 
366
471
  /**
@@ -385,12 +490,7 @@ class SMTPConnection extends EventEmitter {
385
490
  }
386
491
  this._closing = true;
387
492
 
388
- let closeMethod = 'end';
389
-
390
- if (this.stage === 'init') {
391
- // Close the socket immediately when connection timed out
392
- closeMethod = 'destroy';
393
- }
493
+ const closeMethod = this.stage === 'init' ? 'destroy' : 'end';
394
494
 
395
495
  this.logger.debug(
396
496
  {
@@ -400,12 +500,35 @@ class SMTPConnection extends EventEmitter {
400
500
  closeMethod
401
501
  );
402
502
 
403
- 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
+ }
404
515
 
405
516
  if (socket && !socket.destroyed) {
406
517
  try {
407
- this._socket[closeMethod]();
408
- } 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) {
409
532
  // just ignore
410
533
  }
411
534
  }
@@ -446,11 +569,11 @@ class SMTPConnection extends EventEmitter {
446
569
  }
447
570
 
448
571
  if (this.customAuth.has(this._authMethod)) {
449
- let handler = this.customAuth.get(this._authMethod);
572
+ const handler = this.customAuth.get(this._authMethod);
450
573
  let lastResponse;
451
574
  let returned = false;
452
575
 
453
- let resolve = () => {
576
+ const resolve = () => {
454
577
  if (returned) {
455
578
  return;
456
579
  }
@@ -469,7 +592,7 @@ class SMTPConnection extends EventEmitter {
469
592
  callback(null, true);
470
593
  };
471
594
 
472
- let reject = err => {
595
+ const reject = err => {
473
596
  if (returned) {
474
597
  return;
475
598
  }
@@ -477,7 +600,7 @@ class SMTPConnection extends EventEmitter {
477
600
  callback(this._formatError(err, 'EAUTH', lastResponse, 'AUTH ' + this._authMethod));
478
601
  };
479
602
 
480
- let handlerResponse = handler({
603
+ const handlerResponse = handler({
481
604
  auth: this._auth,
482
605
  method: this._authMethod,
483
606
 
@@ -604,7 +727,7 @@ class SMTPConnection extends EventEmitter {
604
727
 
605
728
  // ensure that callback is only called once
606
729
  let returned = false;
607
- let callback = function () {
730
+ const callback = function () {
608
731
  if (returned) {
609
732
  return;
610
733
  }
@@ -617,13 +740,22 @@ class SMTPConnection extends EventEmitter {
617
740
  message.on('error', err => callback(this._formatError(err, 'ESTREAM', false, 'API')));
618
741
  }
619
742
 
620
- let startTime = Date.now();
743
+ const startTime = Date.now();
621
744
  this._setEnvelope(envelope, (err, info) => {
622
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
+
623
755
  return callback(err);
624
756
  }
625
- let envelopeTime = Date.now();
626
- let stream = this._createSendStream((err, str) => {
757
+ const envelopeTime = Date.now();
758
+ const stream = this._createSendStream((err, str) => {
627
759
  if (err) {
628
760
  return callback(err);
629
761
  }
@@ -650,6 +782,11 @@ class SMTPConnection extends EventEmitter {
650
782
  * @param {Function} callback Callback to return once connection is reset
651
783
  */
652
784
  reset(callback) {
785
+ const isDestroyedMessage = this._isDestroyedMessage('reset');
786
+ if (isDestroyedMessage) {
787
+ return callback(this._formatError(isDestroyedMessage, 'ECONNECTION', false, 'API'));
788
+ }
789
+
653
790
  this._sendCommand('RSET');
654
791
  this._responseActions.push(str => {
655
792
  if (str.charAt(0) !== '2') {
@@ -696,7 +833,13 @@ class SMTPConnection extends EventEmitter {
696
833
  this._socket.removeListener('timeout', this._onSocketTimeout);
697
834
  this._socket.removeListener('close', this._onSocketClose);
698
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);
699
841
 
842
+ this._socket.on('error', this._onSocketError);
700
843
  this._socket.on('data', this._onSocketData);
701
844
  this._socket.once('close', this._onSocketClose);
702
845
  this._socket.once('end', this._onSocketEnd);
@@ -728,7 +871,7 @@ class SMTPConnection extends EventEmitter {
728
871
  return;
729
872
  }
730
873
 
731
- let data = (chunk || '').toString('binary');
874
+ let data = chunk.toString('binary');
732
875
  let lines = (this._remainder + data).split(/\r?\n/);
733
876
  let lastline;
734
877
 
@@ -775,7 +918,12 @@ class SMTPConnection extends EventEmitter {
775
918
 
776
919
  err = this._formatError(err, type, data, command);
777
920
 
778
- 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
+ }
779
927
 
780
928
  this.emit('error', err);
781
929
  this.close();
@@ -799,7 +947,7 @@ class SMTPConnection extends EventEmitter {
799
947
  err.message += ': ' + response;
800
948
  }
801
949
 
802
- let responseCode = (typeof response === 'string' && Number((response.match(/^\d+/) || [])[0])) || false;
950
+ const responseCode = (typeof response === 'string' && Number((response.match(/^\d+/) || [])[0])) || false;
803
951
  if (responseCode) {
804
952
  err.responseCode = responseCode;
805
953
  }
@@ -820,15 +968,15 @@ class SMTPConnection extends EventEmitter {
820
968
  let serverResponse = false;
821
969
 
822
970
  if (this._remainder && this._remainder.trim()) {
971
+ this.lastServerResponse = serverResponse = decodeServerResponse(this._remainder.trim());
823
972
  if (this.options.debug || this.options.transactionLog) {
824
973
  this.logger.debug(
825
974
  {
826
975
  tnx: 'server'
827
976
  },
828
- this._remainder.replace(/\r?\n$/, '')
977
+ serverResponse
829
978
  );
830
979
  }
831
- this.lastServerResponse = serverResponse = this._remainder.trim();
832
980
  }
833
981
 
834
982
  this.logger.info(
@@ -856,7 +1004,9 @@ class SMTPConnection extends EventEmitter {
856
1004
  */
857
1005
  _onEnd() {
858
1006
  if (this._socket && !this._socket.destroyed) {
859
- 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();
860
1010
  }
861
1011
  }
862
1012
 
@@ -877,6 +1027,8 @@ class SMTPConnection extends EventEmitter {
877
1027
  return;
878
1028
  }
879
1029
  this._destroyed = true;
1030
+ // keep the documented public flag in sync with the private state
1031
+ this.destroyed = true;
880
1032
  this.emit('end');
881
1033
  }
882
1034
 
@@ -887,6 +1039,15 @@ class SMTPConnection extends EventEmitter {
887
1039
  * has been secured
888
1040
  */
889
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
+
890
1051
  // do not remove all listeners or it breaks node v0.10 as there's
891
1052
  // apparently a 'finish' event set that would be cleared as well
892
1053
 
@@ -894,15 +1055,31 @@ class SMTPConnection extends EventEmitter {
894
1055
  this._socket.removeListener('data', this._onSocketData); // incoming data is going to be gibberish from this point onwards
895
1056
  this._socket.removeListener('timeout', this._onSocketTimeout); // timeout will be re-set for the new socket object
896
1057
 
897
- let socketPlain = this._socket;
898
- let opts = {
899
- socket: this._socket,
900
- host: this.host
901
- };
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
+ );
902
1066
 
903
- Object.keys(this.options.tls || {}).forEach(key => {
904
- opts[key] = this.options.tls[key];
905
- });
1067
+ // ensure servername for SNI
1068
+ if (this.servername && !opts.servername) {
1069
+ opts.servername = this.servername;
1070
+ }
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
+ };
906
1083
 
907
1084
  this.upgrading = true;
908
1085
  // tls.connect is not an asynchronous function however it may still throw errors and requires to be wrapped with try/catch
@@ -912,12 +1089,12 @@ class SMTPConnection extends EventEmitter {
912
1089
  this.upgrading = false;
913
1090
  this._socket.on('data', this._onSocketData);
914
1091
 
915
- socketPlain.removeListener('close', this._onSocketClose);
916
- socketPlain.removeListener('end', this._onSocketEnd);
1092
+ removePlainSocketListeners();
917
1093
 
918
1094
  return callback(null, true);
919
1095
  });
920
1096
  } catch (err) {
1097
+ removePlainSocketListeners();
921
1098
  return callback(err);
922
1099
  }
923
1100
 
@@ -934,18 +1111,27 @@ class SMTPConnection extends EventEmitter {
934
1111
 
935
1112
  /**
936
1113
  * Processes queued responses from the server
937
- *
938
- * @param {Boolean} force If true, ignores _processing flag
939
1114
  */
940
1115
  _processResponse() {
941
1116
  if (!this._responseQueue.length) {
942
1117
  return false;
943
1118
  }
944
1119
 
945
- 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));
946
1130
 
947
1131
  if (/^\d+-/.test(str.split('\n').pop())) {
948
- // 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);
949
1135
  return;
950
1136
  }
951
1137
 
@@ -958,12 +1144,7 @@ class SMTPConnection extends EventEmitter {
958
1144
  );
959
1145
  }
960
1146
 
961
- if (!str.trim()) {
962
- // skip unexpected empty lines
963
- setImmediate(() => this._processResponse());
964
- }
965
-
966
- let action = this._responseActions.shift();
1147
+ const action = this._responseActions.shift();
967
1148
 
968
1149
  if (typeof action === 'function') {
969
1150
  action.call(this, str);
@@ -1011,7 +1192,7 @@ class SMTPConnection extends EventEmitter {
1011
1192
  * {from:{address:'...',name:'...'}, to:[address:'...',name:'...']}
1012
1193
  */
1013
1194
  _setEnvelope(envelope, callback) {
1014
- let args = [];
1195
+ const args = [];
1015
1196
  let useSmtpUtf8 = false;
1016
1197
 
1017
1198
  this._envelope = envelope || {};
@@ -1046,7 +1227,7 @@ class SMTPConnection extends EventEmitter {
1046
1227
  }
1047
1228
 
1048
1229
  // clone the recipients array for latter manipulation
1049
- this._envelope.rcptQueue = JSON.parse(JSON.stringify(this._envelope.to || []));
1230
+ this._envelope.rcptQueue = [].concat(this._envelope.to || []);
1050
1231
  this._envelope.rejected = [];
1051
1232
  this._envelope.rejectedErrors = [];
1052
1233
  this._envelope.accepted = [];
@@ -1059,6 +1240,23 @@ class SMTPConnection extends EventEmitter {
1059
1240
  }
1060
1241
  }
1061
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
+
1062
1260
  this._responseActions.push(str => {
1063
1261
  this._actionMAIL(str, callback);
1064
1262
  });
@@ -1078,7 +1276,10 @@ class SMTPConnection extends EventEmitter {
1078
1276
  }
1079
1277
 
1080
1278
  if (this._envelope.size && this._supportedExtensions.includes('SIZE')) {
1081
- args.push('SIZE=' + this._envelope.size);
1279
+ const sizeValue = Number(this._envelope.size) || 0;
1280
+ if (sizeValue > 0) {
1281
+ args.push('SIZE=' + sizeValue);
1282
+ }
1082
1283
  }
1083
1284
 
1084
1285
  // If the server supports DSN and the envelope includes an DSN prop
@@ -1092,6 +1293,13 @@ class SMTPConnection extends EventEmitter {
1092
1293
  }
1093
1294
  }
1094
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
+
1095
1303
  this._sendCommand('MAIL FROM:<' + this._envelope.from + '>' + (args.length ? ' ' + args.join(' ') : ''));
1096
1304
  }
1097
1305
 
@@ -1114,7 +1322,7 @@ class SMTPConnection extends EventEmitter {
1114
1322
  throw new Error('ret: ' + JSON.stringify(ret));
1115
1323
  }
1116
1324
 
1117
- let envid = (params.envid || params.id || '').toString() || null;
1325
+ const envid = (params.envid || params.id || '').toString() || null;
1118
1326
 
1119
1327
  let notify = params.notify || null;
1120
1328
  if (notify) {
@@ -1122,9 +1330,9 @@ class SMTPConnection extends EventEmitter {
1122
1330
  notify = notify.split(',');
1123
1331
  }
1124
1332
  notify = notify.map(n => n.trim().toUpperCase());
1125
- let validNotify = ['NEVER', 'SUCCESS', 'FAILURE', 'DELAY'];
1126
- let invaliNotify = notify.filter(n => !validNotify.includes(n));
1127
- 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'))) {
1128
1336
  throw new Error('notify: ' + JSON.stringify(notify.join(',')));
1129
1337
  }
1130
1338
  notify = notify.join(',');
@@ -1144,7 +1352,7 @@ class SMTPConnection extends EventEmitter {
1144
1352
  }
1145
1353
 
1146
1354
  _getDsnRcptToArgs() {
1147
- let args = [];
1355
+ const args = [];
1148
1356
  // If the server supports DSN and the envelope includes an DSN prop
1149
1357
  // then append DSN params to the RCPT TO command
1150
1358
  if (this._envelope.dsn && this._supportedExtensions.includes('DSN')) {
@@ -1159,12 +1367,11 @@ class SMTPConnection extends EventEmitter {
1159
1367
  }
1160
1368
 
1161
1369
  _createSendStream(callback) {
1162
- let dataStream = new DataStream();
1163
- let logStream;
1370
+ const dataStream = new DataStream();
1164
1371
 
1165
1372
  if (this.options.lmtp) {
1166
1373
  this._envelope.accepted.forEach((recipient, i) => {
1167
- let final = i === this._envelope.accepted.length - 1;
1374
+ const final = i === this._envelope.accepted.length - 1;
1168
1375
  this._responseActions.push(str => {
1169
1376
  this._actionLMTPStream(recipient, final, str, callback);
1170
1377
  });
@@ -1175,12 +1382,13 @@ class SMTPConnection extends EventEmitter {
1175
1382
  });
1176
1383
  }
1177
1384
 
1385
+ this._currentDataStream = dataStream;
1178
1386
  dataStream.pipe(this._socket, {
1179
1387
  end: false
1180
1388
  });
1181
1389
 
1182
1390
  if (this.options.debug) {
1183
- logStream = new PassThrough();
1391
+ const logStream = new PassThrough();
1184
1392
  logStream.on('readable', () => {
1185
1393
  let chunk;
1186
1394
  while ((chunk = logStream.read())) {
@@ -1196,6 +1404,9 @@ class SMTPConnection extends EventEmitter {
1196
1404
  }
1197
1405
 
1198
1406
  dataStream.once('end', () => {
1407
+ if (this._currentDataStream === dataStream) {
1408
+ this._currentDataStream = false;
1409
+ }
1199
1410
  this.logger.info(
1200
1411
  {
1201
1412
  tnx: 'message',
@@ -1270,7 +1481,12 @@ class SMTPConnection extends EventEmitter {
1270
1481
 
1271
1482
  if (str.charAt(0) !== '2') {
1272
1483
  if (this.options.requireTLS) {
1273
- 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
+ );
1274
1490
  return;
1275
1491
  }
1276
1492
 
@@ -1308,6 +1524,11 @@ class SMTPConnection extends EventEmitter {
1308
1524
  this._supportedExtensions.push('8BITMIME');
1309
1525
  }
1310
1526
 
1527
+ // Detect if the server supports REQUIRETLS (RFC 8689)
1528
+ if (/[ -]REQUIRETLS\b/im.test(str)) {
1529
+ this._supportedExtensions.push('REQUIRETLS');
1530
+ }
1531
+
1311
1532
  // Detect if the server supports PIPELINING
1312
1533
  if (/[ -]PIPELINING\b/im.test(str)) {
1313
1534
  this._supportedExtensions.push('PIPELINING');
@@ -1448,22 +1669,21 @@ class SMTPConnection extends EventEmitter {
1448
1669
  * @param {String} str Message from the server
1449
1670
  */
1450
1671
  _actionAUTH_CRAM_MD5(str, callback) {
1451
- let challengeMatch = str.match(/^334\s+(.+)$/);
1452
- let challengeString = '';
1672
+ const challengeMatch = str.match(/^334\s+(.+)$/);
1453
1673
 
1454
1674
  if (!challengeMatch) {
1455
- return callback(this._formatError('Invalid login sequence while waiting for server challenge string', 'EAUTH', str, 'AUTH CRAM-MD5'));
1456
- } else {
1457
- challengeString = challengeMatch[1];
1675
+ return callback(
1676
+ this._formatError('Invalid login sequence while waiting for server challenge string', 'EAUTH', str, 'AUTH CRAM-MD5')
1677
+ );
1458
1678
  }
1459
1679
 
1460
1680
  // Decode from base64
1461
- let base64decoded = Buffer.from(challengeString, 'base64').toString('ascii'),
1462
- 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);
1463
1683
 
1464
1684
  hmacMD5.update(base64decoded);
1465
1685
 
1466
- let prepended = this._auth.credentials.user + ' ' + hmacMD5.digest('hex');
1686
+ const prepended = this._auth.credentials.user + ' ' + hmacMD5.digest('hex');
1467
1687
 
1468
1688
  this._responseActions.push(str => {
1469
1689
  this._actionAUTH_CRAM_MD5_PASS(str, callback);
@@ -1584,39 +1804,29 @@ class SMTPConnection extends EventEmitter {
1584
1804
  * @param {String} str Message from the server
1585
1805
  */
1586
1806
  _actionMAIL(str, callback) {
1587
- let message, curRecipient;
1588
1807
  if (Number(str.charAt(0)) !== 2) {
1589
- if (this._usingSmtpUtf8 && /^550 /.test(str) && /[\x80-\uFFFF]/.test(this._envelope.from)) {
1590
- message = 'Internationalized mailbox name not allowed';
1591
- } else {
1592
- message = 'Mail command failed';
1593
- }
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';
1594
1812
  return callback(this._formatError(message, 'EENVELOPE', str, 'MAIL FROM'));
1595
1813
  }
1596
1814
 
1597
1815
  if (!this._envelope.rcptQueue.length) {
1598
- return callback(this._formatError('Can\x27t send mail - no recipients defined', 'EENVELOPE', false, 'API'));
1599
- } else {
1600
- this._recipientQueue = [];
1601
-
1602
- if (this._supportedExtensions.includes('PIPELINING')) {
1603
- while (this._envelope.rcptQueue.length) {
1604
- curRecipient = this._envelope.rcptQueue.shift();
1605
- this._recipientQueue.push(curRecipient);
1606
- this._responseActions.push(str => {
1607
- this._actionRCPT(str, callback);
1608
- });
1609
- this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
1610
- }
1611
- } else {
1612
- curRecipient = this._envelope.rcptQueue.shift();
1613
- this._recipientQueue.push(curRecipient);
1614
- this._responseActions.push(str => {
1615
- this._actionRCPT(str, callback);
1616
- });
1617
- this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
1618
- }
1816
+ return callback(this._formatError("Can't send mail - no recipients defined", 'EENVELOPE', false, 'API'));
1619
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);
1620
1830
  }
1621
1831
 
1622
1832
  /**
@@ -1625,16 +1835,14 @@ class SMTPConnection extends EventEmitter {
1625
1835
  * @param {String} str Message from the server
1626
1836
  */
1627
1837
  _actionRCPT(str, callback) {
1628
- let message,
1629
- err,
1630
- curRecipient = this._recipientQueue.shift();
1838
+ let err;
1839
+ const curRecipient = this._recipientQueue.shift();
1631
1840
  if (Number(str.charAt(0)) !== 2) {
1632
1841
  // this is a soft error
1633
- if (this._usingSmtpUtf8 && /^553 /.test(str) && /[\x80-\uFFFF]/.test(curRecipient)) {
1634
- message = 'Internationalized mailbox name not allowed';
1635
- } else {
1636
- message = 'Recipient command failed';
1637
- }
1842
+ const message =
1843
+ this._usingSmtpUtf8 && /^553 /.test(str) && /[\x80-\uFFFF]/.test(curRecipient)
1844
+ ? 'Internationalized mailbox name not allowed'
1845
+ : 'Recipient command failed';
1638
1846
  this._envelope.rejected.push(curRecipient);
1639
1847
  // store error for the failed recipient
1640
1848
  err = this._formatError(message, 'EENVELOPE', str, 'RCPT TO');
@@ -1651,18 +1859,18 @@ class SMTPConnection extends EventEmitter {
1651
1859
  });
1652
1860
  this._sendCommand('DATA');
1653
1861
  } else {
1654
- 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');
1655
1863
  err.rejected = this._envelope.rejected;
1656
1864
  err.rejectedErrors = this._envelope.rejectedErrors;
1657
1865
  return callback(err);
1658
1866
  }
1659
1867
  } else if (this._envelope.rcptQueue.length) {
1660
- curRecipient = this._envelope.rcptQueue.shift();
1661
- this._recipientQueue.push(curRecipient);
1868
+ const nextRecipient = this._envelope.rcptQueue.shift();
1869
+ this._recipientQueue.push(nextRecipient);
1662
1870
  this._responseActions.push(str => {
1663
1871
  this._actionRCPT(str, callback);
1664
1872
  });
1665
- this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
1873
+ this._sendCommand('RCPT TO:<' + nextRecipient + '>' + this._getDsnRcptToArgs());
1666
1874
  }
1667
1875
  }
1668
1876
 
@@ -1678,7 +1886,7 @@ class SMTPConnection extends EventEmitter {
1678
1886
  return callback(this._formatError('Data command failed', 'EENVELOPE', str, 'DATA'));
1679
1887
  }
1680
1888
 
1681
- let response = {
1889
+ const response = {
1682
1890
  accepted: this._envelope.accepted,
1683
1891
  rejected: this._envelope.rejected
1684
1892
  };
@@ -1702,12 +1910,9 @@ class SMTPConnection extends EventEmitter {
1702
1910
  */
1703
1911
  _actionSMTPStream(str, callback) {
1704
1912
  if (Number(str.charAt(0)) !== 2) {
1705
- // Message failed
1706
1913
  return callback(this._formatError('Message failed', 'EMESSAGE', str, 'DATA'));
1707
- } else {
1708
- // Message sent succesfully
1709
- return callback(null, str);
1710
1914
  }
1915
+ return callback(null, str);
1711
1916
  }
1712
1917
 
1713
1918
  /**
@@ -1790,7 +1995,7 @@ class SMTPConnection extends EventEmitter {
1790
1995
  let defaultHostname;
1791
1996
  try {
1792
1997
  defaultHostname = os.hostname() || '';
1793
- } catch (err) {
1998
+ } catch (_err) {
1794
1999
  // fails on windows 7
1795
2000
  defaultHostname = 'localhost';
1796
2001
  }