nodemailer 10.0.9 → 10.0.10

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## [10.0.10](https://github.com/nodemailer/nodemailer/compare/v10.0.9...v10.0.10) (2026-09-14)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * derive the attachment filename from the basename of a Windows path ([c7cc7ce](https://github.com/nodemailer/nodemailer/commit/c7cc7ce41a3602441747476a2a2c4a8ff466a83e))
9
+ * **dkim:** unfold folded header lines in linear time ([28a5909](https://github.com/nodemailer/nodemailer/commit/28a5909cec27646cb001dc7e97a8f5d26c973078))
10
+ * **smtp-connection:** reassemble multiline replies in linear time ([f2d82fa](https://github.com/nodemailer/nodemailer/commit/f2d82fa84d47015a7bbb4253da61eeb778c658b1))
11
+
3
12
  ## [10.0.9](https://github.com/nodemailer/nodemailer/compare/v10.0.8...v10.0.9) (2026-09-12)
4
13
 
5
14
 
@@ -126,11 +126,18 @@ class MessageParser extends node_stream_1.Transform {
126
126
  // signature covers exactly the bytes the receiving side canonicalizes
127
127
  // Only SP and HTAB fold a line, and only they are trimmed from the field name, the
128
128
  // same whitespace the relaxed canonicalization in sign.ts works with
129
- const lines = (this.rawHeaders || Buffer.alloc(0)).toString('binary').split(/\r?\n/);
130
- for (let i = lines.length - 1; i > 0; i--) {
131
- if (/^[ \t]/.test(lines[i])) {
132
- lines[i - 1] += '\n' + lines[i];
133
- lines.splice(i, 1);
129
+ const rawLines = (this.rawHeaders || Buffer.alloc(0)).toString('binary').split(/\r?\n/);
130
+ // Unfold in a single forward pass and only ever test a freshly split line for the
131
+ // continuation prefix. Testing an already merged line instead would rescan a string
132
+ // that grows with every continuation line, so a header folded into many continuation
133
+ // lines (a large recipient list, for example) would take quadratic time to unfold
134
+ const lines = [];
135
+ for (const rawLine of rawLines) {
136
+ if (lines.length && /^[ \t]/.test(rawLine)) {
137
+ lines[lines.length - 1] += '\n' + rawLine;
138
+ }
139
+ else {
140
+ lines.push(rawLine);
134
141
  }
135
142
  }
136
143
  return lines
@@ -151,8 +151,10 @@ class MailComposer {
151
151
  data.filename = attachment.filename;
152
152
  }
153
153
  else if (!isMessageNode && attachment.filename !== false) {
154
+ // a backslash separates as well, so a Windows path does not put the sender's directories in the headers
154
155
  data.filename =
155
- (attachment.path || attachment.href || '').split('/').pop().split('?').shift() || 'attachment-' + (i + 1);
156
+ (attachment.path || attachment.href || '').split(/[/\\]/).pop().split('?').shift() ||
157
+ 'attachment-' + (i + 1);
156
158
  if (data.filename.indexOf('.') < 0) {
157
159
  data.filename += '.' + mimeFuncs.detectExtension(data.contentType);
158
160
  }
@@ -107,8 +107,9 @@ class MailMessage {
107
107
  if (this.data.attachments && this.data.attachments.length) {
108
108
  this.data.attachments.forEach((attachment, i) => {
109
109
  if (!attachment.filename) {
110
+ // a backslash separates as well, so a Windows path does not put the sender's directories in the headers
110
111
  attachment.filename =
111
- (attachment.path || attachment.href || '').split('/').pop().split('?').shift() ||
112
+ (attachment.path || attachment.href || '').split(/[/\\]/).pop().split('?').shift() ||
112
113
  'attachment-' + (i + 1);
113
114
  if (attachment.filename.indexOf('.') < 0) {
114
115
  attachment.filename += '.' + mimeFuncs.detectExtension(attachment.contentType);
@@ -1,3 +1,3 @@
1
1
  export declare const name = "nodemailer";
2
- export declare const version = "10.0.9";
2
+ export declare const version = "10.0.10";
3
3
  export declare const homepage = "https://nodemailer.com/";
@@ -3,5 +3,5 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.homepage = exports.version = exports.name = void 0;
5
5
  exports.name = 'nodemailer';
6
- exports.version = '10.0.9';
6
+ exports.version = '10.0.10';
7
7
  exports.homepage = 'https://nodemailer.com/';
@@ -41,6 +41,8 @@ export interface SMTPConnectionOptions {
41
41
  greetingTimeout?: number | undefined;
42
42
  /** Time of inactivity in ms until the connection is closed, defaults to 10 minutes */
43
43
  socketTimeout?: number | undefined;
44
+ /** Largest single server response to accept in bytes, defaults to 1 MB */
45
+ maxResponseSize?: number | undefined;
44
46
  /** Time to wait in ms for the DNS requests to be resolved, defaults to 30 seconds */
45
47
  dnsTimeout?: number | undefined;
46
48
  /** Use LMTP instead of SMTP */
@@ -268,6 +270,7 @@ export type SMTPConnectionResponseAction = (str: string) => void;
268
270
  * * **greetingTimeout** - Time to wait in ms until greeting message is received from the server (defaults to 30 seconds)
269
271
  * * **connectionTimeout** - how many milliseconds to wait for the connection to establish (defaults to 2 minutes)
270
272
  * * **socketTimeout** - Time of inactivity until the connection is closed (defaults to 10 minutes)
273
+ * * **maxResponseSize** - Largest single server response to accept in bytes (defaults to 1 MB)
271
274
  * * **dnsTimeout** - Time to wait in ms for the DNS requests to be resolved (defaults to 30 seconds)
272
275
  * * **lmtp** - if true, uses LMTP instead of SMTP protocol
273
276
  * * **logger** - bunyan compatible logger interface
@@ -51,6 +51,9 @@ const SOCKET_TIMEOUT = 10 * 60 * 1000; // how much to wait for socket inactivity
51
51
  const GREETING_TIMEOUT = 30 * 1000; // how much to wait after connection is established but SMTP greeting is not receieved
52
52
  const DNS_TIMEOUT = 30 * 1000; // how much to wait for resolveHostname
53
53
  const TEARDOWN_NOOP = () => { }; // reusable no-op handler for absorbing errors during socket teardown
54
+ // how many bytes a single server response may occupy while it is still being received.
55
+ // Generous compared to any real reply, it only stops a peer that never completes one
56
+ const MAX_RESPONSE_SIZE = 1024 * 1024;
54
57
  /**
55
58
  * Re-interpret a server response stored in fake 8-bit byte-container form
56
59
  * (the result of chunk.toString('binary') in _onData) as UTF-8.
@@ -79,11 +82,19 @@ function decodeServerResponse(str) {
79
82
  * Called with the byte-container form the queue holds (see _onData): the check only looks
80
83
  * at leading ASCII digits and '-' of the last line, and a UTF-8 continuation byte is never
81
84
  * 0x0A, so line boundaries and the tested prefix are the same before and after decoding.
82
- * The last line is read with lastIndexOf rather than split() because a queue entry grows
83
- * with every chunk appended to it and only its final line matters.
85
+ * The last line is read with lastIndexOf rather than split() because a queue entry may hold
86
+ * a whole multiline reply and only its final line matters.
84
87
  */
85
88
  function isPartialResponse(str) {
86
- return /^\d+-/.test(str.slice(str.lastIndexOf('\n') + 1));
89
+ return isPartialLine(str.slice(str.lastIndexOf('\n') + 1));
90
+ }
91
+ /**
92
+ * True when a single reply line is a continuation ("250-..."). Used where the line is
93
+ * already known to be one line, which skips the scan isPartialResponse needs to find the
94
+ * last line of a whole reply.
95
+ */
96
+ function isPartialLine(line) {
97
+ return /^\d+-/.test(line);
87
98
  }
88
99
  /**
89
100
  * Generates a SMTP connection object
@@ -100,6 +111,7 @@ function isPartialResponse(str) {
100
111
  * * **greetingTimeout** - Time to wait in ms until greeting message is received from the server (defaults to 30 seconds)
101
112
  * * **connectionTimeout** - how many milliseconds to wait for the connection to establish (defaults to 2 minutes)
102
113
  * * **socketTimeout** - Time of inactivity until the connection is closed (defaults to 10 minutes)
114
+ * * **maxResponseSize** - Largest single server response to accept in bytes (defaults to 1 MB)
103
115
  * * **dnsTimeout** - Time to wait in ms for the DNS requests to be resolved (defaults to 30 seconds)
104
116
  * * **lmtp** - if true, uses LMTP instead of SMTP protocol
105
117
  * * **logger** - bunyan compatible logger interface
@@ -146,6 +158,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
146
158
  this.secure = !!this.secureConnection;
147
159
  this._remainder = '';
148
160
  this._responseQueue = [];
161
+ this._responsePartial = false;
149
162
  this.lastServerResponse = false;
150
163
  this._socket = false;
151
164
  this._supportedAuth = [];
@@ -714,28 +727,58 @@ class SMTPConnection extends node_events_1.EventEmitter {
714
727
  if (this._destroyed || !chunk || !chunk.length) {
715
728
  return;
716
729
  }
717
- let data = chunk.toString('binary');
718
- let lines = (this._remainder + data).split(/\r?\n/);
719
- let lastline;
730
+ const maxResponseSize = this.options.maxResponseSize || MAX_RESPONSE_SIZE;
731
+ const data = chunk.toString('binary');
732
+ // A chunk without a line break only extends the line currently being received, so
733
+ // keep it in the remainder and leave that string unflattened. Splitting the whole
734
+ // remainder again on every chunk would rescan everything buffered for that line so
735
+ // far, which is quadratic in the length of a line the peer never terminates
736
+ if (!data.includes('\n')) {
737
+ this._remainder += data;
738
+ if (this._remainder.length > maxResponseSize) {
739
+ return this._onResponseTooLarge();
740
+ }
741
+ return;
742
+ }
743
+ const lines = (this._remainder + data).split(/\r?\n/);
720
744
  this._remainder = lines.pop();
721
745
  for (let i = 0, len = lines.length; i < len; i++) {
722
- if (this._responseQueue.length) {
723
- lastline = this._responseQueue[this._responseQueue.length - 1];
724
- if (isPartialResponse(lastline)) {
725
- this._responseQueue[this._responseQueue.length - 1] += '\n' + lines[i];
726
- continue;
727
- }
746
+ if (this._responsePartial) {
747
+ this._responseQueue[this._responseQueue.length - 1] += '\n' + lines[i];
728
748
  }
729
- this._responseQueue.push(lines[i]);
730
- }
731
- if (this._responseQueue.length) {
732
- lastline = this._responseQueue[this._responseQueue.length - 1];
733
- if (isPartialResponse(lastline)) {
734
- return;
749
+ else {
750
+ this._responseQueue.push(lines[i]);
735
751
  }
752
+ // The line just added is the last line of that queue entry, so it alone decides
753
+ // whether the reply is still partial. Looking for the last line of the accumulated
754
+ // entry instead would rescan a string that grows with every continuation line,
755
+ // which is quadratic in the size of the reply
756
+ this._responsePartial = isPartialLine(lines[i]);
757
+ // Checked as each line lands, so a peer that never completes a reply cannot keep
758
+ // buffering, and the limit does not depend on how it split its bytes into chunks
759
+ if (this._responsePartial && this._responseQueue[this._responseQueue.length - 1].length > maxResponseSize) {
760
+ return this._onResponseTooLarge();
761
+ }
762
+ }
763
+ if (this._remainder.length > maxResponseSize) {
764
+ return this._onResponseTooLarge();
765
+ }
766
+ if (this._responsePartial) {
767
+ return;
736
768
  }
737
769
  this._processResponse();
738
770
  }
771
+ /**
772
+ * Drops a connection whose peer keeps extending a reply it never completes, releasing
773
+ * whatever was buffered for that reply
774
+ * @internal
775
+ */
776
+ _onResponseTooLarge() {
777
+ this._remainder = '';
778
+ this._responseQueue = [];
779
+ this._responsePartial = false;
780
+ this._onError(new Error('Server response exceeds maximum allowed size'), 'EPROTOCOL', false, 'CONN');
781
+ }
739
782
  /**
740
783
  * 'error' listener for the socket
741
784
  *
@@ -876,6 +919,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
876
919
  // part of the secured EHLO capabilities). STARTTLS response injection.
877
920
  this._remainder = '';
878
921
  this._responseQueue = [];
922
+ this._responsePartial = false;
879
923
  // do not remove all listeners or it breaks node v0.10 as there's
880
924
  // apparently a 'finish' event set that would be cleared as well
881
925
  // we can safely keep 'error', 'end', 'close' etc. events
@@ -124,11 +124,18 @@ export default class MessageParser extends Transform {
124
124
  // signature covers exactly the bytes the receiving side canonicalizes
125
125
  // Only SP and HTAB fold a line, and only they are trimmed from the field name, the
126
126
  // same whitespace the relaxed canonicalization in sign.ts works with
127
- const lines = (this.rawHeaders || Buffer.alloc(0)).toString('binary').split(/\r?\n/);
128
- for (let i = lines.length - 1; i > 0; i--) {
129
- if (/^[ \t]/.test(lines[i])) {
130
- lines[i - 1] += '\n' + lines[i];
131
- lines.splice(i, 1);
127
+ const rawLines = (this.rawHeaders || Buffer.alloc(0)).toString('binary').split(/\r?\n/);
128
+ // Unfold in a single forward pass and only ever test a freshly split line for the
129
+ // continuation prefix. Testing an already merged line instead would rescan a string
130
+ // that grows with every continuation line, so a header folded into many continuation
131
+ // lines (a large recipient list, for example) would take quadratic time to unfold
132
+ const lines = [];
133
+ for (const rawLine of rawLines) {
134
+ if (lines.length && /^[ \t]/.test(rawLine)) {
135
+ lines[lines.length - 1] += '\n' + rawLine;
136
+ }
137
+ else {
138
+ lines.push(rawLine);
132
139
  }
133
140
  }
134
141
  return lines
@@ -113,8 +113,10 @@ class MailComposer {
113
113
  data.filename = attachment.filename;
114
114
  }
115
115
  else if (!isMessageNode && attachment.filename !== false) {
116
+ // a backslash separates as well, so a Windows path does not put the sender's directories in the headers
116
117
  data.filename =
117
- (attachment.path || attachment.href || '').split('/').pop().split('?').shift() || 'attachment-' + (i + 1);
118
+ (attachment.path || attachment.href || '').split(/[/\\]/).pop().split('?').shift() ||
119
+ 'attachment-' + (i + 1);
118
120
  if (data.filename.indexOf('.') < 0) {
119
121
  data.filename += '.' + mimeFuncs.detectExtension(data.contentType);
120
122
  }
@@ -69,8 +69,9 @@ export default class MailMessage {
69
69
  if (this.data.attachments && this.data.attachments.length) {
70
70
  this.data.attachments.forEach((attachment, i) => {
71
71
  if (!attachment.filename) {
72
+ // a backslash separates as well, so a Windows path does not put the sender's directories in the headers
72
73
  attachment.filename =
73
- (attachment.path || attachment.href || '').split('/').pop().split('?').shift() ||
74
+ (attachment.path || attachment.href || '').split(/[/\\]/).pop().split('?').shift() ||
74
75
  'attachment-' + (i + 1);
75
76
  if (attachment.filename.indexOf('.') < 0) {
76
77
  attachment.filename += '.' + mimeFuncs.detectExtension(attachment.contentType);
@@ -1,3 +1,3 @@
1
1
  export declare const name = "nodemailer";
2
- export declare const version = "10.0.9";
2
+ export declare const version = "10.0.10";
3
3
  export declare const homepage = "https://nodemailer.com/";
@@ -1,4 +1,4 @@
1
1
  // Generated by scripts/build.js from package.json. Do not edit by hand.
2
2
  export const name = 'nodemailer';
3
- export const version = '10.0.9';
3
+ export const version = '10.0.10';
4
4
  export const homepage = 'https://nodemailer.com/';
@@ -41,6 +41,8 @@ export interface SMTPConnectionOptions {
41
41
  greetingTimeout?: number | undefined;
42
42
  /** Time of inactivity in ms until the connection is closed, defaults to 10 minutes */
43
43
  socketTimeout?: number | undefined;
44
+ /** Largest single server response to accept in bytes, defaults to 1 MB */
45
+ maxResponseSize?: number | undefined;
44
46
  /** Time to wait in ms for the DNS requests to be resolved, defaults to 30 seconds */
45
47
  dnsTimeout?: number | undefined;
46
48
  /** Use LMTP instead of SMTP */
@@ -268,6 +270,7 @@ export type SMTPConnectionResponseAction = (str: string) => void;
268
270
  * * **greetingTimeout** - Time to wait in ms until greeting message is received from the server (defaults to 30 seconds)
269
271
  * * **connectionTimeout** - how many milliseconds to wait for the connection to establish (defaults to 2 minutes)
270
272
  * * **socketTimeout** - Time of inactivity until the connection is closed (defaults to 10 minutes)
273
+ * * **maxResponseSize** - Largest single server response to accept in bytes (defaults to 1 MB)
271
274
  * * **dnsTimeout** - Time to wait in ms for the DNS requests to be resolved (defaults to 30 seconds)
272
275
  * * **lmtp** - if true, uses LMTP instead of SMTP protocol
273
276
  * * **logger** - bunyan compatible logger interface
@@ -13,6 +13,9 @@ const SOCKET_TIMEOUT = 10 * 60 * 1000; // how much to wait for socket inactivity
13
13
  const GREETING_TIMEOUT = 30 * 1000; // how much to wait after connection is established but SMTP greeting is not receieved
14
14
  const DNS_TIMEOUT = 30 * 1000; // how much to wait for resolveHostname
15
15
  const TEARDOWN_NOOP = () => { }; // reusable no-op handler for absorbing errors during socket teardown
16
+ // how many bytes a single server response may occupy while it is still being received.
17
+ // Generous compared to any real reply, it only stops a peer that never completes one
18
+ const MAX_RESPONSE_SIZE = 1024 * 1024;
16
19
  /**
17
20
  * Re-interpret a server response stored in fake 8-bit byte-container form
18
21
  * (the result of chunk.toString('binary') in _onData) as UTF-8.
@@ -41,11 +44,19 @@ function decodeServerResponse(str) {
41
44
  * Called with the byte-container form the queue holds (see _onData): the check only looks
42
45
  * at leading ASCII digits and '-' of the last line, and a UTF-8 continuation byte is never
43
46
  * 0x0A, so line boundaries and the tested prefix are the same before and after decoding.
44
- * The last line is read with lastIndexOf rather than split() because a queue entry grows
45
- * with every chunk appended to it and only its final line matters.
47
+ * The last line is read with lastIndexOf rather than split() because a queue entry may hold
48
+ * a whole multiline reply and only its final line matters.
46
49
  */
47
50
  function isPartialResponse(str) {
48
- return /^\d+-/.test(str.slice(str.lastIndexOf('\n') + 1));
51
+ return isPartialLine(str.slice(str.lastIndexOf('\n') + 1));
52
+ }
53
+ /**
54
+ * True when a single reply line is a continuation ("250-..."). Used where the line is
55
+ * already known to be one line, which skips the scan isPartialResponse needs to find the
56
+ * last line of a whole reply.
57
+ */
58
+ function isPartialLine(line) {
59
+ return /^\d+-/.test(line);
49
60
  }
50
61
  /**
51
62
  * Generates a SMTP connection object
@@ -62,6 +73,7 @@ function isPartialResponse(str) {
62
73
  * * **greetingTimeout** - Time to wait in ms until greeting message is received from the server (defaults to 30 seconds)
63
74
  * * **connectionTimeout** - how many milliseconds to wait for the connection to establish (defaults to 2 minutes)
64
75
  * * **socketTimeout** - Time of inactivity until the connection is closed (defaults to 10 minutes)
76
+ * * **maxResponseSize** - Largest single server response to accept in bytes (defaults to 1 MB)
65
77
  * * **dnsTimeout** - Time to wait in ms for the DNS requests to be resolved (defaults to 30 seconds)
66
78
  * * **lmtp** - if true, uses LMTP instead of SMTP protocol
67
79
  * * **logger** - bunyan compatible logger interface
@@ -108,6 +120,7 @@ class SMTPConnection extends EventEmitter {
108
120
  this.secure = !!this.secureConnection;
109
121
  this._remainder = '';
110
122
  this._responseQueue = [];
123
+ this._responsePartial = false;
111
124
  this.lastServerResponse = false;
112
125
  this._socket = false;
113
126
  this._supportedAuth = [];
@@ -676,28 +689,58 @@ class SMTPConnection extends EventEmitter {
676
689
  if (this._destroyed || !chunk || !chunk.length) {
677
690
  return;
678
691
  }
679
- let data = chunk.toString('binary');
680
- let lines = (this._remainder + data).split(/\r?\n/);
681
- let lastline;
692
+ const maxResponseSize = this.options.maxResponseSize || MAX_RESPONSE_SIZE;
693
+ const data = chunk.toString('binary');
694
+ // A chunk without a line break only extends the line currently being received, so
695
+ // keep it in the remainder and leave that string unflattened. Splitting the whole
696
+ // remainder again on every chunk would rescan everything buffered for that line so
697
+ // far, which is quadratic in the length of a line the peer never terminates
698
+ if (!data.includes('\n')) {
699
+ this._remainder += data;
700
+ if (this._remainder.length > maxResponseSize) {
701
+ return this._onResponseTooLarge();
702
+ }
703
+ return;
704
+ }
705
+ const lines = (this._remainder + data).split(/\r?\n/);
682
706
  this._remainder = lines.pop();
683
707
  for (let i = 0, len = lines.length; i < len; i++) {
684
- if (this._responseQueue.length) {
685
- lastline = this._responseQueue[this._responseQueue.length - 1];
686
- if (isPartialResponse(lastline)) {
687
- this._responseQueue[this._responseQueue.length - 1] += '\n' + lines[i];
688
- continue;
689
- }
708
+ if (this._responsePartial) {
709
+ this._responseQueue[this._responseQueue.length - 1] += '\n' + lines[i];
690
710
  }
691
- this._responseQueue.push(lines[i]);
692
- }
693
- if (this._responseQueue.length) {
694
- lastline = this._responseQueue[this._responseQueue.length - 1];
695
- if (isPartialResponse(lastline)) {
696
- return;
711
+ else {
712
+ this._responseQueue.push(lines[i]);
697
713
  }
714
+ // The line just added is the last line of that queue entry, so it alone decides
715
+ // whether the reply is still partial. Looking for the last line of the accumulated
716
+ // entry instead would rescan a string that grows with every continuation line,
717
+ // which is quadratic in the size of the reply
718
+ this._responsePartial = isPartialLine(lines[i]);
719
+ // Checked as each line lands, so a peer that never completes a reply cannot keep
720
+ // buffering, and the limit does not depend on how it split its bytes into chunks
721
+ if (this._responsePartial && this._responseQueue[this._responseQueue.length - 1].length > maxResponseSize) {
722
+ return this._onResponseTooLarge();
723
+ }
724
+ }
725
+ if (this._remainder.length > maxResponseSize) {
726
+ return this._onResponseTooLarge();
727
+ }
728
+ if (this._responsePartial) {
729
+ return;
698
730
  }
699
731
  this._processResponse();
700
732
  }
733
+ /**
734
+ * Drops a connection whose peer keeps extending a reply it never completes, releasing
735
+ * whatever was buffered for that reply
736
+ * @internal
737
+ */
738
+ _onResponseTooLarge() {
739
+ this._remainder = '';
740
+ this._responseQueue = [];
741
+ this._responsePartial = false;
742
+ this._onError(new Error('Server response exceeds maximum allowed size'), 'EPROTOCOL', false, 'CONN');
743
+ }
701
744
  /**
702
745
  * 'error' listener for the socket
703
746
  *
@@ -838,6 +881,7 @@ class SMTPConnection extends EventEmitter {
838
881
  // part of the secured EHLO capabilities). STARTTLS response injection.
839
882
  this._remainder = '';
840
883
  this._responseQueue = [];
884
+ this._responsePartial = false;
841
885
  // do not remove all listeners or it breaks node v0.10 as there's
842
886
  // apparently a 'finish' event set that would be cleared as well
843
887
  // we can safely keep 'error', 'end', 'close' etc. events
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodemailer",
3
- "version": "10.0.9",
3
+ "version": "10.0.10",
4
4
  "description": "Easy as cake e-mail sending from your Node.js applications",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/nodemailer.js",