TLS
Configure STARTTLS and implicit TLS (SMTPS) in fumi.
fumi supports both TLS modes via FumiOptions, which pass directly to smtp-server.
Modes
STARTTLS — port 587
The server advertises STARTTLS in the EHLO response. The client upgrades before sending mail. After upgrade, ctx.session.secure === true.
import { Fumi } from "@puiusabin/fumi";
import { requireTls } from "@puiusabin/fumi/plugins/require-tls";
const app = new Fumi({
key: Bun.file("server.key").arrayBuffer(),
cert: Bun.file("server.cert").arrayBuffer(),
authOptional: true,
});
// Reject MAIL FROM if client skipped STARTTLS
app.use(requireTls());
await app.listen(587);Implicit TLS / SMTPS — port 465
The connection is TLS from the start. No STARTTLS command.
const app = new Fumi({
secure: true,
key: Bun.file("server.key").arrayBuffer(),
cert: Bun.file("server.cert").arrayBuffer(),
authOptional: true,
});
await app.listen(465);Enforcement
Protocol-level
Set requireTLS: true in options to let smtp-server reject MAIL FROM at the protocol level before middleware runs. This is simpler but less customizable than the requireTls() plugin.
const app = new Fumi({
key: Bun.file("server.key").arrayBuffer(),
cert: Bun.file("server.cert").arrayBuffer(),
requireTLS: true,
});Middleware-level
For middleware-level enforcement with custom error messages, use the requireTls() plugin shown in the STARTTLS example above. It runs in onMailFrom and gives you full control over the rejection code and message.
Local development
Generating a certificate
Generate a self-signed certificate with mkcert:
brew install mkcert
mkcert -install
mkcert localhost
# produces: localhost.pem + localhost-key.pemPassing to FumiOptions
const app = new Fumi({
key: await Bun.file("localhost-key.pem").text(),
cert: await Bun.file("localhost.pem").text(),
});mkcert certificates are trusted by your local machine only. Do not use them
in production — get a certificate from a public CA (e.g. Let's Encrypt).