nodemailer 8.0.3 → 8.0.5

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,20 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## [8.0.5](https://github.com/nodemailer/nodemailer/compare/v8.0.4...v8.0.5) (2026-04-07)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * decode SMTP server responses as UTF-8 at line boundary ([95876b1](https://github.com/nodemailer/nodemailer/commit/95876b103e587e49583e43f88cb2c3a61556f3ac))
9
+ * sanitize CRLF in transport name option to prevent SMTP command injection (GHSA-vvjj-xcjg-gr5g) ([0a43876](https://github.com/nodemailer/nodemailer/commit/0a43876801a420ca528f492eaa01bfc421cc306e))
10
+
11
+ ## [8.0.4](https://github.com/nodemailer/nodemailer/compare/v8.0.3...v8.0.4) (2026-03-25)
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * sanitize envelope size to prevent SMTP command injection ([2d7b971](https://github.com/nodemailer/nodemailer/commit/2d7b9710e63555a1eb13d721296c51186d4b5651))
17
+
3
18
  ## [8.0.3](https://github.com/nodemailer/nodemailer/compare/v8.0.2...v8.0.3) (2026-03-18)
4
19
 
5
20
 
package/CLAUDE.md ADDED
@@ -0,0 +1,55 @@
1
+ # Nodemailer
2
+
3
+ E-mail sending library for Node.js. Zero runtime dependencies. Entry point is `lib/nodemailer.js`, which exposes `createTransport(transporter, defaults)` and routes to one of the bundled transports based on the options object.
4
+
5
+ ## Layout
6
+
7
+ - `lib/nodemailer.js` — public entry, transport dispatch (`createTransport`).
8
+ - `lib/mailer/` — `Mail` class: the user-facing transport wrapper that normalizes messages, runs the DKIM signer, and hands off to the underlying transport's `.send()`.
9
+ - `lib/mail-composer/` + `lib/mime-node/` — message → MIME tree → raw RFC822 stream.
10
+ - `lib/smtp-connection/` — low-level SMTP/LMTP/ESMTP client. Hot path; security-sensitive. Used by `smtp-transport` and `smtp-pool`.
11
+ - `lib/smtp-transport/` — single-shot SMTP transport.
12
+ - `lib/smtp-pool/` — pooled SMTP transport with rate limiting.
13
+ - `lib/sendmail-transport/`, `lib/ses-transport/`, `lib/stream-transport/`, `lib/json-transport/` — alternate transports.
14
+ - `lib/dkim/`, `lib/addressparser/`, `lib/mime-funcs/`, `lib/base64/`, `lib/qp/`, `lib/punycode/`, `lib/well-known/`, `lib/xoauth2/`, `lib/fetch/`, `lib/shared/` — supporting modules.
15
+ - `test/` — mirrors `lib/` structure. Most suites spin up real `smtp-server` instances on ephemeral ports; raw `net` servers are used when byte-exact reply control is needed (e.g. injecting non-ASCII or invalid UTF-8).
16
+
17
+ Each transport must implement `name`, `version`, and `send(mail, callback)`. `Mail` discovers them via duck typing.
18
+
19
+ ## Engine target
20
+
21
+ `engines.node = ">=6.0.0"`. The library is shipped as ES2017 script-mode CommonJS — no `import`, no top-level `await`, no optional chaining, no nullish coalescing, no class fields. ESLint enforces `ecmaVersion: 2017` and `sourceType: 'script'`. There is a Node 6 syntax-compat check (`npm run test:syntax`, runs `test/syntax-compat.js` inside `node:6-alpine`) that must keep passing — do not introduce syntax that breaks it. `'use strict';` directive at the top of every file.
22
+
23
+ ## Conventions
24
+
25
+ - CommonJS only: `const x = require('...')`, `module.exports = ...`.
26
+ - Callback-first style throughout the public API. Many internals are still callback-based — match the style of the file you are editing rather than introducing promises mid-module.
27
+ - Prettier handles formatting; ESLint handles correctness. Run `npm run format` and `npm run lint` before sending changes. The lint config disables Prettier-overlapping rules.
28
+ - Snake_case is not used; camelCase for variables and methods, PascalCase for classes.
29
+ - Prefer small, surgical diffs. The codebase is mature and load-bearing — avoid drive-by refactors, comment churn, or "improvements" outside the scope of the change.
30
+ - Every change to security-sensitive code (anything in `lib/smtp-connection/`, address parsing, header generation, DKIM) needs tests that exercise the failure mode, not just the happy path.
31
+
32
+ ## Testing
33
+
34
+ - `npm test` — full suite via `node --test` (~150s, 480+ tests, runs serially).
35
+ - `npm run test:coverage` — same suite under `c8`.
36
+ - `npm run test:syntax` — Node 6 syntax compatibility check in Docker.
37
+ - `npm run lint` / `npm run lint:fix`.
38
+ - `npm run format` / `npm run format:check`.
39
+
40
+ Always run `npm test` and `npm run lint` before considering a change done. Tests are required to pass on every commit because release-please cuts releases directly from `master`.
41
+
42
+ ## Releases
43
+
44
+ Releases, version numbers, the `version` field in `package.json`, git tags, `CHANGELOG.md` entries, and npm publication are all managed automatically by the release-please GitHub Action (`.github/workflows/release.yaml`, configured by `.release-please-config.json`). **Never edit any of these manually and never propose manual edits to them.**
45
+
46
+ Release-please derives the next version and changelog from Conventional Commit messages on `master`, opens a release PR (`chore: release X.Y.Z [skip-ci]`), and publishes to npm with provenance when that PR is merged. The only thing that should land on `master` between releases is normal commits with Conventional Commit prefixes — release-please takes care of the rest.
47
+
48
+ Conventional Commit prefixes used in this repo: `fix:`, `feat:`, `chore:`, `docs:`, `refactor:`, `test:`. Use `fix:` for anything users would benefit from seeing in the changelog, including security fixes (reference the GHSA in the body).
49
+
50
+ ## Security
51
+
52
+ This is a widely-deployed library — security-sensitive changes get extra scrutiny:
53
+ - 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
+ - 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
+ - Reference the GHSA ID in commit messages for advisories.
@@ -17,6 +17,28 @@ const GREETING_TIMEOUT = 30 * 1000; // how much to wait after connection is esta
17
17
  const DNS_TIMEOUT = 30 * 1000; // how much to wait for resolveHostname
18
18
  const TEARDOWN_NOOP = () => {}; // reusable no-op handler for absorbing errors during socket teardown
19
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
+ }
41
+
20
42
  /**
21
43
  * Generates a SMTP connection object
22
44
  *
@@ -68,7 +90,7 @@ class SMTPConnection extends EventEmitter {
68
90
  this.secureConnection = true;
69
91
  }
70
92
 
71
- this.name = this.options.name || this._getHostname();
93
+ this.name = (this.options.name || this._getHostname()).toString().replace(/[\r\n]+/g, '');
72
94
 
73
95
  this.logger = shared.getLogger(this.options, {
74
96
  component: this.options.component || 'smtp-connection',
@@ -895,15 +917,15 @@ class SMTPConnection extends EventEmitter {
895
917
  let serverResponse = false;
896
918
 
897
919
  if (this._remainder && this._remainder.trim()) {
920
+ this.lastServerResponse = serverResponse = decodeServerResponse(this._remainder.trim());
898
921
  if (this.options.debug || this.options.transactionLog) {
899
922
  this.logger.debug(
900
923
  {
901
924
  tnx: 'server'
902
925
  },
903
- this._remainder.replace(/\r?\n$/, '')
926
+ serverResponse
904
927
  );
905
928
  }
906
- this.lastServerResponse = serverResponse = this._remainder.trim();
907
929
  }
908
930
 
909
931
  this.logger.info(
@@ -1023,7 +1045,7 @@ class SMTPConnection extends EventEmitter {
1023
1045
  return false;
1024
1046
  }
1025
1047
 
1026
- let str = (this.lastServerResponse = (this._responseQueue.shift() || '').toString());
1048
+ let str = (this.lastServerResponse = decodeServerResponse((this._responseQueue.shift() || '').toString()));
1027
1049
 
1028
1050
  if (/^\d+-/.test(str.split('\n').pop())) {
1029
1051
  // keep waiting for the final part of multiline response
@@ -1159,7 +1181,10 @@ class SMTPConnection extends EventEmitter {
1159
1181
  }
1160
1182
 
1161
1183
  if (this._envelope.size && this._supportedExtensions.includes('SIZE')) {
1162
- args.push('SIZE=' + this._envelope.size);
1184
+ const sizeValue = Number(this._envelope.size) || 0;
1185
+ if (sizeValue > 0) {
1186
+ args.push('SIZE=' + sizeValue);
1187
+ }
1163
1188
  }
1164
1189
 
1165
1190
  // If the server supports DSN and the envelope includes an DSN prop
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodemailer",
3
- "version": "8.0.3",
3
+ "version": "8.0.5",
4
4
  "description": "Easy as cake e-mail sending from your Node.js applications",
5
5
  "main": "lib/nodemailer.js",
6
6
  "scripts": {
@@ -27,10 +27,10 @@
27
27
  },
28
28
  "homepage": "https://nodemailer.com/",
29
29
  "devDependencies": {
30
- "@aws-sdk/client-sesv2": "3.1011.0",
30
+ "@aws-sdk/client-sesv2": "3.1025.0",
31
31
  "bunyan": "1.8.15",
32
32
  "c8": "11.0.0",
33
- "eslint": "10.0.3",
33
+ "eslint": "10.2.0",
34
34
  "eslint-config-prettier": "10.1.8",
35
35
  "globals": "17.4.0",
36
36
  "libbase64": "1.3.0",
@@ -40,7 +40,7 @@
40
40
  "prettier": "3.8.1",
41
41
  "proxy": "1.0.2",
42
42
  "proxy-test-server": "1.0.0",
43
- "smtp-server": "3.18.1"
43
+ "smtp-server": "3.18.3"
44
44
  },
45
45
  "engines": {
46
46
  "node": ">=6.0.0"