nodemailer 10.0.1 → 10.0.2
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 +8 -0
- package/dist/cjs/mailer/index.js +17 -5
- package/dist/cjs/mime-node/index.js +28 -7
- package/dist/cjs/package-info.d.ts +1 -1
- package/dist/cjs/package-info.js +1 -1
- package/dist/cjs/shared/index.d.ts +0 -1
- package/dist/cjs/shared/index.js +16 -7
- package/dist/esm/mailer/index.js +17 -5
- package/dist/esm/mime-node/index.js +28 -7
- package/dist/esm/package-info.d.ts +1 -1
- package/dist/esm/package-info.js +1 -1
- package/dist/esm/shared/index.d.ts +0 -1
- package/dist/esm/shared/index.js +16 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## [10.0.2](https://github.com/nodemailer/nodemailer/compare/v10.0.1...v10.0.2) (2026-09-09)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **mime-node:** flatten nested recipient arrays without recursion ([ebe0849](https://github.com/nodemailer/nodemailer/commit/ebe084940aef88278afc6016b78c6d1c3821bb66))
|
|
9
|
+
* **shared:** keep the TLS server name out of the DNS cache ([a6512db](https://github.com/nodemailer/nodemailer/commit/a6512dbcb3c6e7f2f70d3acccc5752defe3c61fe))
|
|
10
|
+
|
|
3
11
|
## [10.0.1](https://github.com/nodemailer/nodemailer/compare/v10.0.0...v10.0.1) (2026-09-07)
|
|
4
12
|
|
|
5
13
|
|
package/dist/cjs/mailer/index.js
CHANGED
|
@@ -172,12 +172,24 @@ class Mail extends node_events_1.EventEmitter {
|
|
|
172
172
|
}, 'PluginCompile Error: %s', err.message);
|
|
173
173
|
return done(err);
|
|
174
174
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
let recipientCount;
|
|
176
|
+
try {
|
|
177
|
+
mail.message = new index_js_1.default(mail.data).compile();
|
|
178
|
+
mail.setMailerHeader();
|
|
179
|
+
mail.setPriorityHeaders();
|
|
180
|
+
mail.setListHeaders();
|
|
181
|
+
recipientCount = mail.message.getEnvelope().to.length;
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
// message data can throw while it is compiled, the error belongs to the callback
|
|
185
|
+
this.logger.error({
|
|
186
|
+
err,
|
|
187
|
+
tnx: 'transport',
|
|
188
|
+
action: 'send'
|
|
189
|
+
}, 'Compile Error: %s', err.message);
|
|
190
|
+
return done(err);
|
|
191
|
+
}
|
|
179
192
|
const maxRecipients = mail.data.maxRecipients === undefined ? DEFAULT_MAX_RECIPIENTS : mail.data.maxRecipients;
|
|
180
|
-
const recipientCount = mail.message.getEnvelope().to.length;
|
|
181
193
|
if (maxRecipients && recipientCount > maxRecipients) {
|
|
182
194
|
const err = new Error(`Message has ${recipientCount} recipients, which is over the ${maxRecipients} allowed by maxRecipients`);
|
|
183
195
|
err.code = errors.EMAXRECIPIENTS;
|
|
@@ -1042,17 +1042,38 @@ class MimeNode {
|
|
|
1042
1042
|
* @internal
|
|
1043
1043
|
*/
|
|
1044
1044
|
_parseAddresses(addresses) {
|
|
1045
|
-
// Collected into one list as we go. concat.apply spreads the entries into arguments
|
|
1046
|
-
// and throws a RangeError once a recipient array is long enough to pass the
|
|
1047
|
-
// argument limit, which a large Bcc list reaches on its own.
|
|
1048
1045
|
const flattened = [];
|
|
1049
|
-
|
|
1046
|
+
// Nested arrays are flattened iteratively. concat.apply throws once a recipient list
|
|
1047
|
+
// is long enough to pass the argument limit, and an array handed to addressparser is
|
|
1048
|
+
// stringified, which recurses once per nesting level and exhausts the call stack on
|
|
1049
|
+
// a deep enough value. Every array is walked once, which also ends a self-referential
|
|
1050
|
+
// input
|
|
1051
|
+
const seen = new WeakSet();
|
|
1052
|
+
const stack = [];
|
|
1053
|
+
const enter = (list) => {
|
|
1054
|
+
if (!seen.has(list)) {
|
|
1055
|
+
seen.add(list);
|
|
1056
|
+
stack.push({ list, pos: 0 });
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
enter(Array.isArray(addresses) ? addresses : [addresses]);
|
|
1060
|
+
while (stack.length) {
|
|
1061
|
+
const frame = stack[stack.length - 1];
|
|
1062
|
+
if (frame.pos >= frame.list.length) {
|
|
1063
|
+
stack.pop();
|
|
1064
|
+
continue;
|
|
1065
|
+
}
|
|
1066
|
+
const address = frame.list[frame.pos++];
|
|
1067
|
+
if (Array.isArray(address)) {
|
|
1068
|
+
enter(address);
|
|
1069
|
+
continue;
|
|
1070
|
+
}
|
|
1050
1071
|
if (address && address.address) {
|
|
1051
1072
|
const normalized = this._normalizeAddress(address.address);
|
|
1052
1073
|
if (normalized === address.address && typeof address.name === 'string') {
|
|
1053
1074
|
// there is nothing to rewrite, so there is nothing to keep off the original
|
|
1054
1075
|
flattened.push(address);
|
|
1055
|
-
|
|
1076
|
+
continue;
|
|
1056
1077
|
}
|
|
1057
1078
|
// rewriting would land on the object the caller passed in and might
|
|
1058
1079
|
// still hold a reference to, so rewrite a copy of it instead. An own
|
|
@@ -1062,13 +1083,13 @@ class MimeNode {
|
|
|
1062
1083
|
copy.address = normalized;
|
|
1063
1084
|
copy.name = address.name || '';
|
|
1064
1085
|
flattened.push(copy);
|
|
1065
|
-
|
|
1086
|
+
continue;
|
|
1066
1087
|
}
|
|
1067
1088
|
const parsed = this._normalizeParsedAddresses((0, index_js_1.default)(address));
|
|
1068
1089
|
for (let i = 0; i < parsed.length; i++) {
|
|
1069
1090
|
flattened.push(parsed[i]);
|
|
1070
1091
|
}
|
|
1071
|
-
}
|
|
1092
|
+
}
|
|
1072
1093
|
return flattened;
|
|
1073
1094
|
}
|
|
1074
1095
|
/**
|
package/dist/cjs/package-info.js
CHANGED
package/dist/cjs/shared/index.js
CHANGED
|
@@ -109,7 +109,6 @@ const formatDNSValue = (value, extra) => {
|
|
|
109
109
|
// Select a random address from available addresses, or null if none
|
|
110
110
|
const host = addresses.length > 0 ? addresses[Math.floor(Math.random() * addresses.length)] : null;
|
|
111
111
|
return Object.assign({
|
|
112
|
-
servername: value.servername,
|
|
113
112
|
host,
|
|
114
113
|
// Include all addresses for connection fallback support
|
|
115
114
|
_addresses: addresses
|
|
@@ -123,14 +122,19 @@ const resolveHostname = (options, callback) => {
|
|
|
123
122
|
if (!options.host || node_net_1.default.isIP(options.host)) {
|
|
124
123
|
// nothing to do here
|
|
125
124
|
const value = {
|
|
126
|
-
addresses: [options.host]
|
|
127
|
-
servername: options.servername || false
|
|
125
|
+
addresses: [options.host]
|
|
128
126
|
};
|
|
129
127
|
return callback(null, formatDNSValue(value, {
|
|
128
|
+
servername: options.servername || false,
|
|
130
129
|
cached: false
|
|
131
130
|
}));
|
|
132
131
|
}
|
|
133
132
|
const host = options.host;
|
|
133
|
+
// The TLS server name belongs to the connection asking, not to the host it resolves. The
|
|
134
|
+
// cache is shared by every transport of the process and keyed by host alone, so a server
|
|
135
|
+
// name stored in it would be the one of whichever transport resolved the host first, and a
|
|
136
|
+
// later transport with its own tls.servername would present and verify that name instead
|
|
137
|
+
const servername = options.servername || host;
|
|
134
138
|
let cached;
|
|
135
139
|
if (exports.dnsCache.has(options.host)) {
|
|
136
140
|
cached = exports.dnsCache.get(options.host);
|
|
@@ -153,6 +157,7 @@ const resolveHostname = (options, callback) => {
|
|
|
153
157
|
}
|
|
154
158
|
if (!cached.expires || cached.expires >= now) {
|
|
155
159
|
return callback(null, formatDNSValue(cached.value, {
|
|
160
|
+
servername,
|
|
156
161
|
cached: true
|
|
157
162
|
}));
|
|
158
163
|
}
|
|
@@ -180,14 +185,14 @@ const resolveHostname = (options, callback) => {
|
|
|
180
185
|
const allAddresses = ipv4Addresses.concat(ipv6Addresses);
|
|
181
186
|
if (allAddresses.length) {
|
|
182
187
|
const value = {
|
|
183
|
-
addresses: allAddresses
|
|
184
|
-
servername: options.servername || host
|
|
188
|
+
addresses: allAddresses
|
|
185
189
|
};
|
|
186
190
|
exports.dnsCache.set(host, {
|
|
187
191
|
value,
|
|
188
192
|
expires: Date.now() + (options.dnsTtl || DNS_TTL)
|
|
189
193
|
});
|
|
190
194
|
return callback(null, formatDNSValue(value, {
|
|
195
|
+
servername,
|
|
191
196
|
cached: false
|
|
192
197
|
}));
|
|
193
198
|
}
|
|
@@ -200,6 +205,7 @@ const resolveHostname = (options, callback) => {
|
|
|
200
205
|
expires: Date.now() + (options.dnsTtl || DNS_TTL)
|
|
201
206
|
});
|
|
202
207
|
return callback(null, formatDNSValue(cached.value, {
|
|
208
|
+
servername,
|
|
203
209
|
cached: true,
|
|
204
210
|
error: ipv4Error
|
|
205
211
|
}));
|
|
@@ -214,6 +220,7 @@ const resolveHostname = (options, callback) => {
|
|
|
214
220
|
expires: Date.now() + (options.dnsTtl || DNS_TTL)
|
|
215
221
|
});
|
|
216
222
|
return callback(null, formatDNSValue(cached.value, {
|
|
223
|
+
servername,
|
|
217
224
|
cached: true,
|
|
218
225
|
error: err
|
|
219
226
|
}));
|
|
@@ -231,18 +238,19 @@ const resolveHostname = (options, callback) => {
|
|
|
231
238
|
if (!supportedAddresses.length && cached) {
|
|
232
239
|
// nothing was found, fallback to cached value
|
|
233
240
|
return callback(null, formatDNSValue(cached.value, {
|
|
241
|
+
servername,
|
|
234
242
|
cached: true
|
|
235
243
|
}));
|
|
236
244
|
}
|
|
237
245
|
const value = {
|
|
238
|
-
addresses: supportedAddresses.length ? supportedAddresses : [host]
|
|
239
|
-
servername: options.servername || host
|
|
246
|
+
addresses: supportedAddresses.length ? supportedAddresses : [host]
|
|
240
247
|
};
|
|
241
248
|
exports.dnsCache.set(host, {
|
|
242
249
|
value,
|
|
243
250
|
expires: Date.now() + (options.dnsTtl || DNS_TTL)
|
|
244
251
|
});
|
|
245
252
|
return callback(null, formatDNSValue(value, {
|
|
253
|
+
servername,
|
|
246
254
|
cached: false
|
|
247
255
|
}));
|
|
248
256
|
});
|
|
@@ -254,6 +262,7 @@ const resolveHostname = (options, callback) => {
|
|
|
254
262
|
expires: Date.now() + (options.dnsTtl || DNS_TTL)
|
|
255
263
|
});
|
|
256
264
|
return callback(null, formatDNSValue(cached.value, {
|
|
265
|
+
servername,
|
|
257
266
|
cached: true,
|
|
258
267
|
error: lookupErr
|
|
259
268
|
}));
|
package/dist/esm/mailer/index.js
CHANGED
|
@@ -134,12 +134,24 @@ class Mail extends EventEmitter {
|
|
|
134
134
|
}, 'PluginCompile Error: %s', err.message);
|
|
135
135
|
return done(err);
|
|
136
136
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
let recipientCount;
|
|
138
|
+
try {
|
|
139
|
+
mail.message = new MailComposer(mail.data).compile();
|
|
140
|
+
mail.setMailerHeader();
|
|
141
|
+
mail.setPriorityHeaders();
|
|
142
|
+
mail.setListHeaders();
|
|
143
|
+
recipientCount = mail.message.getEnvelope().to.length;
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
// message data can throw while it is compiled, the error belongs to the callback
|
|
147
|
+
this.logger.error({
|
|
148
|
+
err,
|
|
149
|
+
tnx: 'transport',
|
|
150
|
+
action: 'send'
|
|
151
|
+
}, 'Compile Error: %s', err.message);
|
|
152
|
+
return done(err);
|
|
153
|
+
}
|
|
141
154
|
const maxRecipients = mail.data.maxRecipients === undefined ? DEFAULT_MAX_RECIPIENTS : mail.data.maxRecipients;
|
|
142
|
-
const recipientCount = mail.message.getEnvelope().to.length;
|
|
143
155
|
if (maxRecipients && recipientCount > maxRecipients) {
|
|
144
156
|
const err = new Error(`Message has ${recipientCount} recipients, which is over the ${maxRecipients} allowed by maxRecipients`);
|
|
145
157
|
err.code = errors.EMAXRECIPIENTS;
|
|
@@ -1004,17 +1004,38 @@ class MimeNode {
|
|
|
1004
1004
|
* @internal
|
|
1005
1005
|
*/
|
|
1006
1006
|
_parseAddresses(addresses) {
|
|
1007
|
-
// Collected into one list as we go. concat.apply spreads the entries into arguments
|
|
1008
|
-
// and throws a RangeError once a recipient array is long enough to pass the
|
|
1009
|
-
// argument limit, which a large Bcc list reaches on its own.
|
|
1010
1007
|
const flattened = [];
|
|
1011
|
-
|
|
1008
|
+
// Nested arrays are flattened iteratively. concat.apply throws once a recipient list
|
|
1009
|
+
// is long enough to pass the argument limit, and an array handed to addressparser is
|
|
1010
|
+
// stringified, which recurses once per nesting level and exhausts the call stack on
|
|
1011
|
+
// a deep enough value. Every array is walked once, which also ends a self-referential
|
|
1012
|
+
// input
|
|
1013
|
+
const seen = new WeakSet();
|
|
1014
|
+
const stack = [];
|
|
1015
|
+
const enter = (list) => {
|
|
1016
|
+
if (!seen.has(list)) {
|
|
1017
|
+
seen.add(list);
|
|
1018
|
+
stack.push({ list, pos: 0 });
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
1021
|
+
enter(Array.isArray(addresses) ? addresses : [addresses]);
|
|
1022
|
+
while (stack.length) {
|
|
1023
|
+
const frame = stack[stack.length - 1];
|
|
1024
|
+
if (frame.pos >= frame.list.length) {
|
|
1025
|
+
stack.pop();
|
|
1026
|
+
continue;
|
|
1027
|
+
}
|
|
1028
|
+
const address = frame.list[frame.pos++];
|
|
1029
|
+
if (Array.isArray(address)) {
|
|
1030
|
+
enter(address);
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1012
1033
|
if (address && address.address) {
|
|
1013
1034
|
const normalized = this._normalizeAddress(address.address);
|
|
1014
1035
|
if (normalized === address.address && typeof address.name === 'string') {
|
|
1015
1036
|
// there is nothing to rewrite, so there is nothing to keep off the original
|
|
1016
1037
|
flattened.push(address);
|
|
1017
|
-
|
|
1038
|
+
continue;
|
|
1018
1039
|
}
|
|
1019
1040
|
// rewriting would land on the object the caller passed in and might
|
|
1020
1041
|
// still hold a reference to, so rewrite a copy of it instead. An own
|
|
@@ -1024,13 +1045,13 @@ class MimeNode {
|
|
|
1024
1045
|
copy.address = normalized;
|
|
1025
1046
|
copy.name = address.name || '';
|
|
1026
1047
|
flattened.push(copy);
|
|
1027
|
-
|
|
1048
|
+
continue;
|
|
1028
1049
|
}
|
|
1029
1050
|
const parsed = this._normalizeParsedAddresses(addressparser(address));
|
|
1030
1051
|
for (let i = 0; i < parsed.length; i++) {
|
|
1031
1052
|
flattened.push(parsed[i]);
|
|
1032
1053
|
}
|
|
1033
|
-
}
|
|
1054
|
+
}
|
|
1034
1055
|
return flattened;
|
|
1035
1056
|
}
|
|
1036
1057
|
/**
|
package/dist/esm/package-info.js
CHANGED
package/dist/esm/shared/index.js
CHANGED
|
@@ -68,7 +68,6 @@ const formatDNSValue = (value, extra) => {
|
|
|
68
68
|
// Select a random address from available addresses, or null if none
|
|
69
69
|
const host = addresses.length > 0 ? addresses[Math.floor(Math.random() * addresses.length)] : null;
|
|
70
70
|
return Object.assign({
|
|
71
|
-
servername: value.servername,
|
|
72
71
|
host,
|
|
73
72
|
// Include all addresses for connection fallback support
|
|
74
73
|
_addresses: addresses
|
|
@@ -82,14 +81,19 @@ export const resolveHostname = (options, callback) => {
|
|
|
82
81
|
if (!options.host || net.isIP(options.host)) {
|
|
83
82
|
// nothing to do here
|
|
84
83
|
const value = {
|
|
85
|
-
addresses: [options.host]
|
|
86
|
-
servername: options.servername || false
|
|
84
|
+
addresses: [options.host]
|
|
87
85
|
};
|
|
88
86
|
return callback(null, formatDNSValue(value, {
|
|
87
|
+
servername: options.servername || false,
|
|
89
88
|
cached: false
|
|
90
89
|
}));
|
|
91
90
|
}
|
|
92
91
|
const host = options.host;
|
|
92
|
+
// The TLS server name belongs to the connection asking, not to the host it resolves. The
|
|
93
|
+
// cache is shared by every transport of the process and keyed by host alone, so a server
|
|
94
|
+
// name stored in it would be the one of whichever transport resolved the host first, and a
|
|
95
|
+
// later transport with its own tls.servername would present and verify that name instead
|
|
96
|
+
const servername = options.servername || host;
|
|
93
97
|
let cached;
|
|
94
98
|
if (dnsCache.has(options.host)) {
|
|
95
99
|
cached = dnsCache.get(options.host);
|
|
@@ -112,6 +116,7 @@ export const resolveHostname = (options, callback) => {
|
|
|
112
116
|
}
|
|
113
117
|
if (!cached.expires || cached.expires >= now) {
|
|
114
118
|
return callback(null, formatDNSValue(cached.value, {
|
|
119
|
+
servername,
|
|
115
120
|
cached: true
|
|
116
121
|
}));
|
|
117
122
|
}
|
|
@@ -139,14 +144,14 @@ export const resolveHostname = (options, callback) => {
|
|
|
139
144
|
const allAddresses = ipv4Addresses.concat(ipv6Addresses);
|
|
140
145
|
if (allAddresses.length) {
|
|
141
146
|
const value = {
|
|
142
|
-
addresses: allAddresses
|
|
143
|
-
servername: options.servername || host
|
|
147
|
+
addresses: allAddresses
|
|
144
148
|
};
|
|
145
149
|
dnsCache.set(host, {
|
|
146
150
|
value,
|
|
147
151
|
expires: Date.now() + (options.dnsTtl || DNS_TTL)
|
|
148
152
|
});
|
|
149
153
|
return callback(null, formatDNSValue(value, {
|
|
154
|
+
servername,
|
|
150
155
|
cached: false
|
|
151
156
|
}));
|
|
152
157
|
}
|
|
@@ -159,6 +164,7 @@ export const resolveHostname = (options, callback) => {
|
|
|
159
164
|
expires: Date.now() + (options.dnsTtl || DNS_TTL)
|
|
160
165
|
});
|
|
161
166
|
return callback(null, formatDNSValue(cached.value, {
|
|
167
|
+
servername,
|
|
162
168
|
cached: true,
|
|
163
169
|
error: ipv4Error
|
|
164
170
|
}));
|
|
@@ -173,6 +179,7 @@ export const resolveHostname = (options, callback) => {
|
|
|
173
179
|
expires: Date.now() + (options.dnsTtl || DNS_TTL)
|
|
174
180
|
});
|
|
175
181
|
return callback(null, formatDNSValue(cached.value, {
|
|
182
|
+
servername,
|
|
176
183
|
cached: true,
|
|
177
184
|
error: err
|
|
178
185
|
}));
|
|
@@ -190,18 +197,19 @@ export const resolveHostname = (options, callback) => {
|
|
|
190
197
|
if (!supportedAddresses.length && cached) {
|
|
191
198
|
// nothing was found, fallback to cached value
|
|
192
199
|
return callback(null, formatDNSValue(cached.value, {
|
|
200
|
+
servername,
|
|
193
201
|
cached: true
|
|
194
202
|
}));
|
|
195
203
|
}
|
|
196
204
|
const value = {
|
|
197
|
-
addresses: supportedAddresses.length ? supportedAddresses : [host]
|
|
198
|
-
servername: options.servername || host
|
|
205
|
+
addresses: supportedAddresses.length ? supportedAddresses : [host]
|
|
199
206
|
};
|
|
200
207
|
dnsCache.set(host, {
|
|
201
208
|
value,
|
|
202
209
|
expires: Date.now() + (options.dnsTtl || DNS_TTL)
|
|
203
210
|
});
|
|
204
211
|
return callback(null, formatDNSValue(value, {
|
|
212
|
+
servername,
|
|
205
213
|
cached: false
|
|
206
214
|
}));
|
|
207
215
|
});
|
|
@@ -213,6 +221,7 @@ export const resolveHostname = (options, callback) => {
|
|
|
213
221
|
expires: Date.now() + (options.dnsTtl || DNS_TTL)
|
|
214
222
|
});
|
|
215
223
|
return callback(null, formatDNSValue(cached.value, {
|
|
224
|
+
servername,
|
|
216
225
|
cached: true,
|
|
217
226
|
error: lookupErr
|
|
218
227
|
}));
|