|
| 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. |
0 commit comments