nodemailer 8.0.9 → 8.0.11

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,25 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## [8.0.11](https://github.com/nodemailer/nodemailer/compare/v8.0.10...v8.0.11) (2026-06-10)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * apply the transport-level newline option in stream and sendmail transports ([cb4f904](https://github.com/nodemailer/nodemailer/commit/cb4f904a53d2c2feeaf327203c92378d46304398))
9
+ * include icalEvent path/href content in the application/ics attachment ([b801c48](https://github.com/nodemailer/nodemailer/commit/b801c48fab8e9b71bc7e0ea1fb32ce6b34675b15))
10
+ * parse Ethereal response props without polynomial regex backtracking ([067aebe](https://github.com/nodemailer/nodemailer/commit/067aebec83b8cbe7682905e89b30ab19d260b503))
11
+ * resolve oauth2_provision_cb at send time for non-pooled SMTP transports ([203c8ec](https://github.com/nodemailer/nodemailer/commit/203c8ecf97594ac2e69919b0f3ba966c0f86750e))
12
+ * return the promise from every resolveContent branch ([07ffe8c](https://github.com/nodemailer/nodemailer/commit/07ffe8cfd97f0486b8c7b541f398922ddab47882))
13
+ * strip the url scheme from List-ID header values ([77e5885](https://github.com/nodemailer/nodemailer/commit/77e5885cfa0c6723ea7749c1ee74b1c11aeb78bd))
14
+ * tag AWS SES transport errors with the ESES code ([efa647a](https://github.com/nodemailer/nodemailer/commit/efa647a125dd698413a7cf6813b8e36881a06f91))
15
+
16
+ ## [8.0.10](https://github.com/nodemailer/nodemailer/compare/v8.0.9...v8.0.10) (2026-05-29)
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * fall back to lower-severity handler when custom logger lacks a level method ([6d849df](https://github.com/nodemailer/nodemailer/commit/6d849df59a56184b48844ed10b5fb7b8e9f74634))
22
+
3
23
  ## [8.0.9](https://github.com/nodemailer/nodemailer/compare/v8.0.8...v8.0.9) (2026-05-26)
4
24
 
5
25
 
package/CLAUDE.md CHANGED
@@ -50,6 +50,7 @@ Conventional Commit prefixes used in this repo: `fix:`, `feat:`, `chore:`, `docs
50
50
  ## Security
51
51
 
52
52
  This is a widely-deployed library — security-sensitive changes get extra scrutiny:
53
+
53
54
  - SMTP command injection: any user-controllable value that flows into a written SMTP command (envelope addresses, sizes, the `name`/EHLO option, headers) must be CRLF-stripped or rejected at the boundary. Sanitize at the assignment, not at every call site.
54
55
  - Server reply parsing in `lib/smtp-connection/index.js` uses a `'binary'` byte-container intermediate to reassemble multi-byte UTF-8 across socket chunks; the actual decode happens at line boundaries via `decodeServerResponse`. Don't change the chunk-buffering encoding without understanding why.
55
56
  - Reference the GHSA ID in commit messages for advisories.
package/SECURITY.md ADDED
@@ -0,0 +1,65 @@
1
+ # Security Policy
2
+
3
+ Nodemailer is a widely deployed, zero-dependency e-mail library. We take security
4
+ reports seriously and aim to respond quickly.
5
+
6
+ ## Supported Versions
7
+
8
+ Security fixes are released only against the latest major version. We do not
9
+ backport patches to older majors — upgrading to the current release line is the
10
+ supported way to receive security updates.
11
+
12
+ | Version | Supported |
13
+ | ------- | ------------------ |
14
+ | 8.x | :white_check_mark: |
15
+ | < 8.0 | :x: |
16
+
17
+ If you are on an older major, please upgrade. See the migration notes at
18
+ <https://nodemailer.com/> before updating.
19
+
20
+ ## Reporting a Vulnerability
21
+
22
+ **Please do not report security vulnerabilities through public GitHub issues,
23
+ pull requests, or discussions.**
24
+
25
+ Report privately through one of the following channels:
26
+
27
+ 1. **GitHub Security Advisories (preferred).** Open a private report at
28
+ <https://github.com/nodemailer/nodemailer/security/advisories/new>. This keeps
29
+ the discussion private until a fix is published and lets us credit you.
30
+ 2. **Email.** Send details to **andris@reinman.eu** (the contact listed in
31
+ [`SECURITY.txt`](SECURITY.txt)). Encrypt sensitive details if possible.
32
+
33
+ When reporting, please include as much of the following as you can:
34
+
35
+ - The affected version(s) and environment (Node.js version, OS).
36
+ - The component involved (e.g. SMTP connection, address parsing, MIME/header
37
+ generation, DKIM).
38
+ - A clear description of the issue and its impact (e.g. header/SMTP command
39
+ injection, information disclosure, DoS).
40
+ - A minimal proof of concept or reproduction steps.
41
+ - Any suggested remediation, if you have one.
42
+
43
+ Nodemailer is maintained by a single person, so there is no guaranteed response
44
+ time — sometimes reports are handled within hours, sometimes they take longer.
45
+ Accepted issues are fixed in a new release and coordinated through a GitHub
46
+ Security Advisory, and reporters who wish to be named are credited.
47
+
48
+ ## CVEs
49
+
50
+ We track and disclose vulnerabilities through GitHub Security Advisories. We do
51
+ not request or manage CVE identifiers ourselves. If you need a CVE assigned for a
52
+ reported issue, please request one yourself — for example, through GitHub's own
53
+ CVE request flow on the published advisory, or another CNA.
54
+
55
+ ## Scope
56
+
57
+ In scope: the `nodemailer` package source in this repository — message and MIME
58
+ generation, SMTP/LMTP client behaviour, address parsing, header handling, DKIM
59
+ signing, and the bundled transports.
60
+
61
+ Out of scope: vulnerabilities in your own application code, misconfiguration of
62
+ your mail server or credentials, social-engineering reports, and issues in
63
+ third-party services Nodemailer connects to.
64
+
65
+ Thank you for helping keep Nodemailer and its users safe.
@@ -83,7 +83,7 @@ class MailComposer {
83
83
  * @returns {Object} An object of arrays (`related` and `attached`)
84
84
  */
85
85
  getAttachments(findRelated) {
86
- let icalEvent, eventObject;
86
+ let eventObject;
87
87
  const attachments = [].concat(this.mail.attachments || []).map((attachment, i) => {
88
88
  if (/^data:/i.test(attachment.path || attachment.href)) {
89
89
  attachment = this._processDataUrl(attachment);
@@ -160,18 +160,7 @@ class MailComposer {
160
160
  });
161
161
 
162
162
  if (this.mail.icalEvent) {
163
- if (
164
- typeof this.mail.icalEvent === 'object' &&
165
- (this.mail.icalEvent.content || this.mail.icalEvent.path || this.mail.icalEvent.href || this.mail.icalEvent.raw)
166
- ) {
167
- icalEvent = this.mail.icalEvent;
168
- } else {
169
- icalEvent = {
170
- content: this.mail.icalEvent
171
- };
172
- }
173
-
174
- eventObject = Object.assign({}, icalEvent);
163
+ eventObject = Object.assign({}, this._getIcalEvent());
175
164
 
176
165
  eventObject.contentType = 'application/ics';
177
166
  if (!eventObject.headers) {
@@ -195,6 +184,67 @@ class MailComposer {
195
184
  };
196
185
  }
197
186
 
187
+ /**
188
+ * Returns the icalEvent value with `path`/`href`/data uri input normalized into
189
+ * a `content` entry, the same way as for regular attachments. The same event is
190
+ * included twice (as a text/calendar alternative and as an application/ics
191
+ * attachment), so the shared content object is marked to be resolved just once
192
+ * and the buffered result is reused by the second node.
193
+ *
194
+ * @returns {Object} Normalized icalEvent data
195
+ */
196
+ _getIcalEvent() {
197
+ if (!this._icalEvent) {
198
+ let icalEvent;
199
+ if (
200
+ typeof this.mail.icalEvent === 'object' &&
201
+ (this.mail.icalEvent.content || this.mail.icalEvent.path || this.mail.icalEvent.href || this.mail.icalEvent.raw)
202
+ ) {
203
+ icalEvent = Object.assign({}, this.mail.icalEvent);
204
+ } else {
205
+ icalEvent = {
206
+ content: this.mail.icalEvent
207
+ };
208
+ }
209
+
210
+ if (/^data:/i.test(icalEvent.path || icalEvent.href)) {
211
+ icalEvent = this._processDataUrl(icalEvent);
212
+ }
213
+
214
+ if (/^https?:\/\//i.test(icalEvent.path)) {
215
+ icalEvent.href = icalEvent.path;
216
+ icalEvent.path = undefined;
217
+ }
218
+
219
+ if (!icalEvent.raw) {
220
+ // map file path and URL values into `content`, otherwise the content
221
+ // nodes would render an empty body
222
+ if (icalEvent.path) {
223
+ icalEvent.content = {
224
+ path: icalEvent.path
225
+ };
226
+ icalEvent.path = undefined;
227
+ } else if (icalEvent.href) {
228
+ icalEvent.content = {
229
+ href: icalEvent.href,
230
+ httpHeaders: icalEvent.httpHeaders
231
+ };
232
+ icalEvent.href = undefined;
233
+ }
234
+ }
235
+
236
+ if (icalEvent.content && typeof icalEvent.content === 'object') {
237
+ // we are going to have the same attachment twice, so mark this to be
238
+ // resolved just once
239
+ icalEvent.content._resolve = true;
240
+ }
241
+
242
+ this._icalEvent = icalEvent;
243
+ }
244
+
245
+ return this._icalEvent;
246
+ }
247
+
198
248
  /**
199
249
  * List alternatives. Resulting objects can be used as input for MimeNode nodes
200
250
  *
@@ -202,7 +252,7 @@ class MailComposer {
202
252
  */
203
253
  getAlternatives() {
204
254
  const alternatives = [];
205
- let text, html, watchHtml, amp, icalEvent, eventObject;
255
+ let text, html, watchHtml, amp, eventObject;
206
256
 
207
257
  if (this.mail.text) {
208
258
  if (
@@ -248,24 +298,7 @@ class MailComposer {
248
298
 
249
299
  // NB! when including attachments with a calendar alternative you might end up in a blank screen on some clients
250
300
  if (this.mail.icalEvent) {
251
- if (
252
- typeof this.mail.icalEvent === 'object' &&
253
- (this.mail.icalEvent.content || this.mail.icalEvent.path || this.mail.icalEvent.href || this.mail.icalEvent.raw)
254
- ) {
255
- icalEvent = this.mail.icalEvent;
256
- } else {
257
- icalEvent = {
258
- content: this.mail.icalEvent
259
- };
260
- }
261
-
262
- eventObject = Object.assign({}, icalEvent);
263
-
264
- if (eventObject.content && typeof eventObject.content === 'object') {
265
- // we are going to have the same attachment twice, so mark this to be
266
- // resolved just once
267
- eventObject.content._resolve = true;
268
- }
301
+ eventObject = Object.assign({}, this._getIcalEvent());
269
302
 
270
303
  eventObject.filename = false;
271
304
  eventObject.contentType =
@@ -281,7 +281,11 @@ class MailMessage {
281
281
  comment = mimeFuncs.encodeWord(comment);
282
282
  }
283
283
 
284
- return (value.comment ? comment + ' ' : '') + this._formatListUrl(value.url).replace(/^<[^:]+\/{,2}/, '');
284
+ // List-ID expects a bare domain-like identifier, so strip the
285
+ // scheme prefix that _formatListUrl adds or passes through
286
+ return (
287
+ (value.comment ? comment + ' ' : '') + this._formatListUrl(value.url).replace(/^<[^:]+:\/{0,2}/, '<')
288
+ );
285
289
  }
286
290
 
287
291
  // List-*: <http://domain> (comment)
@@ -2087,7 +2087,7 @@ module.exports = {
2087
2087
  if (!mimeType) {
2088
2088
  return defaultExtension;
2089
2089
  }
2090
- const parts = (mimeType || '').toLowerCase().trim().split('/');
2090
+ const parts = mimeType.toLowerCase().trim().split('/');
2091
2091
  const rootType = parts.shift().trim();
2092
2092
  const subType = parts.join('/').trim();
2093
2093
 
package/lib/nodemailer.js CHANGED
@@ -147,11 +147,20 @@ module.exports.getTestMessageUrl = function (info) {
147
147
  }
148
148
 
149
149
  const infoProps = new Map();
150
- info.response.replace(/\[([^\]]+)\]$/, (m, props) => {
151
- props.replace(/\b([A-Z0-9]+)=([^\s]+)/g, (m, key, value) => {
152
- infoProps.set(key, value);
153
- });
154
- });
150
+
151
+ // Extract the trailing "[...]" part of the response (no "]" allowed inside)
152
+ // with linear string scanning; the equivalent regex /\[([^\]]+)\]$/ was
153
+ // flagged for polynomial backtracking on adversarial server responses
154
+ const response = info.response.toString();
155
+ if (response.length > 2 && response.charAt(response.length - 1) === ']') {
156
+ const open = response.indexOf('[', response.lastIndexOf(']', response.length - 2) + 1);
157
+ if (open >= 0 && open < response.length - 2) {
158
+ const props = response.substring(open + 1, response.length - 1);
159
+ props.replace(/\b([A-Z0-9]+)=([^\s]+)/g, (m, key, value) => {
160
+ infoProps.set(key, value);
161
+ });
162
+ }
163
+ }
155
164
 
156
165
  if (infoProps.has('STATUS') && infoProps.has('MSGID')) {
157
166
  return (testAccount.web || ETHEREAL_WEB) + '/message/' + infoProps.get('MSGID');
@@ -4,6 +4,8 @@ const { spawn } = require('child_process');
4
4
  const packageData = require('../../package.json');
5
5
  const shared = require('../shared');
6
6
  const errors = require('../errors');
7
+ const LeWindows = require('../mime-node/le-windows');
8
+ const LeUnix = require('../mime-node/le-unix');
7
9
 
8
10
  /**
9
11
  * Generates a Transport object for Sendmail
@@ -46,6 +48,8 @@ class SendmailTransport {
46
48
  this.args = options.args;
47
49
  }
48
50
  }
51
+
52
+ this.winbreak = ['win', 'windows', 'dos', '\r\n'].includes((options.newline || '').toString().toLowerCase());
49
53
  }
50
54
 
51
55
  /**
@@ -178,7 +182,15 @@ class SendmailTransport {
178
182
  );
179
183
 
180
184
  const sourceStream = mail.message.createReadStream();
181
- sourceStream.once('error', err => {
185
+ let stream = sourceStream;
186
+ if (this.options.newline) {
187
+ // apply the transport-level line ending transform; the message-level
188
+ // `newline` option is handled by MimeNode in createReadStream()
189
+ stream = sourceStream.pipe(this.winbreak ? new LeWindows() : new LeUnix());
190
+ sourceStream.once('error', err => stream.emit('error', err));
191
+ }
192
+
193
+ stream.once('error', err => {
182
194
  this.logger.error(
183
195
  {
184
196
  err,
@@ -193,7 +205,7 @@ class SendmailTransport {
193
205
  callback(err);
194
206
  });
195
207
 
196
- sourceStream.pipe(sendmail.stdin);
208
+ stream.pipe(sendmail.stdin);
197
209
  } else {
198
210
  const err = new Error('sendmail was not found');
199
211
  err.code = errors.ESENDMAIL;
@@ -3,9 +3,22 @@
3
3
  const EventEmitter = require('events');
4
4
  const packageData = require('../../package.json');
5
5
  const shared = require('../shared');
6
+ const errors = require('../errors');
6
7
  const LeWindows = require('../mime-node/le-windows');
7
8
  const MimeNode = require('../mime-node');
8
9
 
10
+ /**
11
+ * Tags AWS SDK rejections that carry no `code` property (SDK v3 errors only
12
+ * have a `name`) with the generic SES transport error code, keeping the
13
+ * original error object intact
14
+ */
15
+ function tagSesError(err) {
16
+ if (err && typeof err === 'object' && !err.code) {
17
+ err.code = errors.ESES;
18
+ }
19
+ return err;
20
+ }
21
+
9
22
  /**
10
23
  * Generates a Transport object for AWS SES
11
24
  *
@@ -157,6 +170,7 @@ class SESTransport extends EventEmitter {
157
170
  });
158
171
  })
159
172
  .catch(err => {
173
+ tagSesError(err);
160
174
  this.logger.error(
161
175
  {
162
176
  err,
@@ -188,7 +202,7 @@ class SESTransport extends EventEmitter {
188
202
 
189
203
  const cb = err => {
190
204
  if (err && !['InvalidParameterValue', 'MessageRejected'].includes(err.code || err.Code || err.name)) {
191
- return callback(err);
205
+ return callback(tagSesError(err));
192
206
  }
193
207
  return callback(null, true);
194
208
  };
@@ -205,15 +219,13 @@ class SESTransport extends EventEmitter {
205
219
  }
206
220
  };
207
221
 
208
- this.getRegion((err, region) => {
209
- if (err || !region) {
210
- region = 'us-east-1';
211
- }
212
-
222
+ // the region value is not used for anything when verifying, but the lookup
223
+ // exercises the client configuration the same way as send() does
224
+ this.getRegion(() => {
213
225
  const command = new this.ses.SendEmailCommand(sesMessage);
214
226
  const sendPromise = this.ses.sesClient.send(command);
215
227
 
216
- sendPromise.then(data => cb(null, data)).catch(err => cb(err));
228
+ sendPromise.then(() => cb(null)).catch(err => cb(err));
217
229
  });
218
230
 
219
231
  return promise;
@@ -367,7 +367,16 @@ module.exports._logFunc = (logger, level, defaults, data, message, ...args) => {
367
367
  const entry = Object.assign({}, defaults || {}, data || {});
368
368
  delete entry.level;
369
369
 
370
- logger[level](entry, message, ...args);
370
+ let logLevel = level;
371
+ if (typeof logger[logLevel] !== 'function') {
372
+ // Provided logger does not implement this level. Fall back to a
373
+ // lower-severity handler instead of throwing.
374
+ logLevel = ['info', 'debug', 'log', 'trace', 'warn', 'error'].find(name => typeof logger[name] === 'function');
375
+ }
376
+
377
+ if (logLevel) {
378
+ logger[logLevel](entry, message, ...args);
379
+ }
371
380
  };
372
381
 
373
382
  /**
@@ -521,6 +530,12 @@ module.exports.resolveContent = (data, key, options, callback) => {
521
530
  });
522
531
  }
523
532
 
533
+ resolveContentValue(data, key, options, callback);
534
+
535
+ return promise;
536
+ };
537
+
538
+ function resolveContentValue(data, key, options, callback) {
524
539
  let content = (data && data[key] && data[key].content) || data[key];
525
540
  const encoding = ((typeof data[key] === 'object' && data[key].encoding) || 'utf8')
526
541
  .toString()
@@ -558,10 +573,7 @@ module.exports.resolveContent = (data, key, options, callback) => {
558
573
  } else if (/^data:/i.test(content.path || content.href)) {
559
574
  const parsedDataUri = module.exports.parseDataURI(content.path || content.href);
560
575
 
561
- if (!parsedDataUri || !parsedDataUri.data) {
562
- return callback(null, Buffer.from(0));
563
- }
564
- return callback(null, parsedDataUri.data);
576
+ return callback(null, parsedDataUri && parsedDataUri.data ? parsedDataUri.data : Buffer.alloc(0));
565
577
  } else if (content.path) {
566
578
  if (options.disableFileAccess) {
567
579
  return setImmediate(() => {
@@ -580,9 +592,7 @@ module.exports.resolveContent = (data, key, options, callback) => {
580
592
 
581
593
  // default action, return as is
582
594
  setImmediate(() => callback(null, content));
583
-
584
- return promise;
585
- };
595
+ }
586
596
 
587
597
  /**
588
598
  * Copies properties from source objects to target objects
@@ -51,9 +51,9 @@ function decodeServerResponse(str) {
51
51
  * * **requireTLS** - forces the client to use STARTTLS
52
52
  * * **name** - the name of the client server
53
53
  * * **localAddress** - outbound address to bind to (see: http://nodejs.org/api/net.html#net_net_connect_options_connectionlistener)
54
- * * **greetingTimeout** - Time to wait in ms until greeting message is received from the server (defaults to 10000)
55
- * * **connectionTimeout** - how many milliseconds to wait for the connection to establish
56
- * * **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)
57
57
  * * **dnsTimeout** - Time to wait in ms for the DNS requests to be resolved (defaults to 30 seconds)
58
58
  * * **lmtp** - if true, uses LMTP instead of SMTP protocol
59
59
  * * **logger** - bunyan compatible logger interface
@@ -838,7 +838,7 @@ class SMTPConnection extends EventEmitter {
838
838
  return;
839
839
  }
840
840
 
841
- let data = (chunk || '').toString('binary');
841
+ let data = chunk.toString('binary');
842
842
  let lines = (this._remainder + data).split(/\r?\n/);
843
843
  let lastline;
844
844
 
@@ -71,13 +71,19 @@ class SMTPTransport extends EventEmitter {
71
71
 
72
72
  getAuth(authOpts) {
73
73
  if (!authOpts) {
74
+ if (this.auth && this.auth.oauth2 && this.mailer) {
75
+ // Transport-level auth is resolved in the constructor, before the Mail wrapper
76
+ // assigns `this.mailer`, so a provision callback registered with
77
+ // `transporter.set('oauth2_provision_cb', ...)` has to be re-checked here
78
+ this.auth.oauth2.provisionCallback = this.mailer.get('oauth2_provision_cb') || this.auth.oauth2.provisionCallback;
79
+ }
74
80
  return this.auth;
75
81
  }
76
82
 
77
83
  const authData = Object.assign(
78
84
  {},
79
85
  this.options.auth && typeof this.options.auth === 'object' ? this.options.auth : {},
80
- authOpts && typeof authOpts === 'object' ? authOpts : {}
86
+ typeof authOpts === 'object' ? authOpts : {}
81
87
  );
82
88
 
83
89
  if (Object.keys(authData).length === 0) {
@@ -2,6 +2,8 @@
2
2
 
3
3
  const packageData = require('../../package.json');
4
4
  const shared = require('../shared');
5
+ const LeWindows = require('../mime-node/le-windows');
6
+ const LeUnix = require('../mime-node/le-unix');
5
7
 
6
8
  /**
7
9
  * Generates a Transport object for streaming
@@ -63,6 +65,13 @@ class StreamTransport {
63
65
 
64
66
  try {
65
67
  stream = mail.message.createReadStream();
68
+ if (this.options.newline) {
69
+ // apply the transport-level line ending transform; the message-level
70
+ // `newline` option is handled by MimeNode in createReadStream()
71
+ const sourceStream = stream;
72
+ stream = sourceStream.pipe(this.winbreak ? new LeWindows() : new LeUnix());
73
+ sourceStream.once('error', err => stream.emit('error', err));
74
+ }
66
75
  } catch (E) {
67
76
  this.logger.error(
68
77
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodemailer",
3
- "version": "8.0.9",
3
+ "version": "8.0.11",
4
4
  "description": "Easy as cake e-mail sending from your Node.js applications",
5
5
  "main": "lib/nodemailer.js",
6
6
  "scripts": {
@@ -27,19 +27,19 @@
27
27
  },
28
28
  "homepage": "https://nodemailer.com/",
29
29
  "devDependencies": {
30
- "@aws-sdk/client-sesv2": "3.1037.0",
30
+ "@aws-sdk/client-sesv2": "3.1065.0",
31
31
  "bunyan": "1.8.15",
32
32
  "c8": "11.0.0",
33
- "eslint": "10.2.1",
33
+ "eslint": "10.4.1",
34
34
  "eslint-config-prettier": "10.1.8",
35
- "globals": "17.5.0",
35
+ "globals": "17.6.0",
36
36
  "libbase64": "1.3.0",
37
37
  "libmime": "5.3.8",
38
38
  "libqp": "2.1.1",
39
- "prettier": "3.8.3",
39
+ "prettier": "3.8.4",
40
40
  "proxy": "1.0.2",
41
41
  "proxy-test-server": "1.0.0",
42
- "smtp-server": "3.18.4"
42
+ "smtp-server": "3.18.5"
43
43
  },
44
44
  "engines": {
45
45
  "node": ">=6.0.0"