Download Latest Version v3.0.0 source code.zip (1.4 MB) Google Add to Preferred Sources
Home / maildev@3.0.0
Name Modified Size InfoDownloads / Week
Parent folder
README.md 2026-09-23 9.5 kB
v3.0.0 source code.tar.gz 2026-09-23 1.3 MB
v3.0.0 source code.zip 2026-09-23 1.4 MB
Totals: 3 Items   2.8 MB 0

Major Changes

Patch Changes

  • #550 d34aaa3 Thanks @djfarrelly! - Fix the Docker healthcheck so containers report healthy out of the box. A dedicated healthcheck entrypoint (dist/bin/healthcheck.js) now:
  • probes 127.0.0.1 instead of localhost, so it no longer fails when localhost resolves to IPv6 (::1) while the web server binds IPv4 only (#537);
  • falls back to a TCP check on the SMTP port when the web UI is disabled with --disable-web, instead of probing an endpoint that isn't there (#544);
  • normalizes MAILDEV_BASE_PATHNAME so a trailing slash can't produce a // in the probe URL (#542).

  • #578 a2eb3a1 Thanks @djfarrelly! - Honor port: 0 and expose the bound port on the programmatic API (#567).

Previously new MailDev({ smtp: 0 }) was silently ignored — options.port || DEFAULT_PORT treated an explicit 0 as absent and fell back to 1025. It now uses ??, so 0 requests an OS-assigned ephemeral port, and the port that was actually bound is read back from the listening socket.

Both servers now expose their bound address:

  • SMTPServer#getAddress() → { host, port } and SMTPServer#getPort()
  • APIServer#getAddress() → { host, port } | null and APIServer#getPort() (null until listening)

Together these let you run one MailDev per worker under a parallel test runner without hand-assigning ports.

  • #581 0e79a6a Thanks @cpruijsen! - Honor MAILDEV_AUTO_RELAY and MAILDEV_AUTO_RELAY_RULES so Docker and other env-based setups can enable auto-relay the same way --auto-relay and --auto-relay-rules do. true, 1, and a present-but-empty value relay to each mail's own recipients; false and 0 disable; any other value is the override recipient.

  • 4b515c6 Thanks @djfarrelly! - Fix the integrated MCP HTTP transport (maildev --mcp) so it supports more than one session. Each session now gets its own MCP server instance instead of sharing a single one, which previously made the second client fail to connect with "Already connected to a transport." Requests carrying an unknown session ID now return a proper JSON-RPC error, and open MCP sessions are closed on shutdown.

  • #555 196f277 Thanks @djfarrelly! - Serve the web UI / REST API over HTTPS when --https (with --https-cert and --https-key) is set. The Fastify server now actually honors these options — previously the flags existed but the web server always served plain HTTP. HTTPS can also be configured via MAILDEV_HTTPS, MAILDEV_HTTPS_CERT, and MAILDEV_HTTPS_KEY, and the Docker healthcheck detects MAILDEV_HTTPS and probes over HTTPS so TLS-enabled containers report healthy.

  • 42d3708 Thanks @djfarrelly! - Add an opt-in maxEmails limit that also bounds the mail directory

An opt-in maxEmails limit (default 0 = unlimited; set --max-emails to cap) discards the oldest emails along with their .eml files and attachments, so both memory and the mail directory can be kept bounded. When set, leftover files from earlier runs are trimmed at startup. The default keeps MailDev's historical unbounded behaviour, so persisted mail stays durable across restarts (no data loss by default).

Breaking change:

  • @maildev/core: Storage implementations must now provide onEvicted. A new EvictHandler type and mapLimit helper are exported.

  • #551 fc7e584 Thanks @djfarrelly! - Add a configurable maximum message size and reject oversized messages. A new --max-message-size option (env MAILDEV_MAX_MESSAGE_SIZE, default 50 MB) advertises the SMTP SIZE extension and refuses messages larger than the limit. The bytes forwarded to the parser are capped at the limit, so a malicious multipart message with a huge number of parts can no longer tie up the parser (addresses the unbounded MIME sibling-part fanout in #531). Set to 0 to disable the limit.

  • #579 f78e761 Thanks @djfarrelly! - Record relay delivery status on the email. After a successful relay, the stored email now carries relayedAt (when it was last relayed) and relayedTo (the recipients it was delivered to), so the REST API can report whether a message was relayed and to which addresses. This hooks into the shared relayEmail path used by both auto-relay and the manual relay endpoint (closes #199).

  • #549 781cc0c Thanks @djfarrelly! - Restore persisted emails on startup. When --mail-directory (MAILDEV_MAIL_DIRECTORY) is set, existing .eml files in the directory are now loaded back into the UI when MailDev starts, so mail survives a restart (e.g. across container/pod restarts with a mounted volume).

  • 7af89ef Thanks @djfarrelly! - Reject start() on an SMTP port conflict instead of crashing the process (#568).

When the SMTP port was unavailable (e.g. EADDRINUSE), the error arrived via the server's error event and was re-thrown from inside that callback. It escaped as an uncaught exception and took the process down, while start()'s promise stayed pending forever — a try/catch around await start() never saw it.

Startup errors are now routed to the start() promise, so await maildev.start() rejects with the underlying error (e.g. EADDRINUSE) and the caller decides what to do. Runtime errors after startup remain observable via the error event, but a listener-less emitter is no longer fatal.

Source: README.md, updated 2026-09-23