Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/puiusabin/bun-smtp/llms.txt

Use this file to discover all available pages before exploring further.

This example shows how to configure TLS for your SMTP server, supporting both STARTTLS (opportunistic TLS) and implicit TLS.

STARTTLS (port 587)

STARTTLS allows clients to upgrade a plain TCP connection to TLS:
starttls-server.ts
import { SMTPServer } from "bun-smtp";

const server = new SMTPServer({
  // TLS credentials
  key: await Bun.file("./cert/key.pem").text(),
  cert: await Bun.file("./cert/cert.pem").text(),
  
  // Allow plain connections but require upgrade before AUTH
  secure: false,
  needsUpgrade: true,
  allowInsecureAuth: false,
  
  onAuth(auth, session, callback) {
    // session.secure is true here (STARTTLS completed)
    if (auth.username === "user" && auth.password === "pass") {
      callback(null, { user: auth.username });
    } else {
      callback(new Error("Invalid credentials"));
    }
  },
  
  onData(stream, session, callback) {
    stream.pipeTo(new WritableStream()).then(
      () => callback(null),
      callback
    );
  },
});

await server.listen(587);
console.log("STARTTLS server listening on port 587");
needsUpgrade: true means the server will reject AUTH and MAIL commands until the client runs STARTTLS.

Implicit TLS (port 465)

Implicit TLS starts encryption immediately upon connection:
implicit-tls-server.ts
import { SMTPServer } from "bun-smtp";

const server = new SMTPServer({
  // TLS credentials
  key: await Bun.file("./cert/key.pem").text(),
  cert: await Bun.file("./cert/cert.pem").text(),
  
  // Start in secure mode
  secure: true,
  
  onAuth(auth, session, callback) {
    if (auth.username === "user" && auth.password === "pass") {
      callback(null, { user: auth.username });
    } else {
      callback(new Error("Invalid credentials"));
    }
  },
  
  onData(stream, session, callback) {
    stream.pipeTo(new WritableStream()).then(
      () => callback(null),
      callback
    );
  },
});

await server.listen(465);
console.log("Implicit TLS server listening on port 465");

Generating self-signed certificates

For development, generate a self-signed certificate:
mkdir -p cert
openssl req -x509 -newkey rsa:4096 -keyout cert/key.pem -out cert/cert.pem \
  -days 365 -nodes -subj "/CN=localhost"
Never use self-signed certificates in production. Use certificates from a trusted CA like Let’s Encrypt.

Using Let’s Encrypt certificates

Load production certificates from disk:
const server = new SMTPServer({
  key: await Bun.file("/etc/letsencrypt/live/mail.example.com/privkey.pem").text(),
  cert: await Bun.file("/etc/letsencrypt/live/mail.example.com/fullchain.pem").text(),
  secure: true,
});

Hot-reloading certificates

Rotate certificates without restarting:
const server = new SMTPServer({
  key: await Bun.file("./cert/key.pem").text(),
  cert: await Bun.file("./cert/cert.pem").text(),
  secure: true,
});

await server.listen(465);

// Watch for certificate updates
setInterval(async () => {
  const newKey = await Bun.file("./cert/key.pem").text();
  const newCert = await Bun.file("./cert/cert.pem").text();
  
  server.updateSecureContext({ key: newKey, cert: newCert });
  console.log("TLS certificates reloaded");
}, 60_000); // Check every minute
New connections will use the updated certificates immediately. Existing connections continue with the old certificate until they close.

Inspecting the TLS connection

Use the onSecure callback to inspect TLS details:
const server = new SMTPServer({
  key: await Bun.file("./cert/key.pem").text(),
  cert: await Bun.file("./cert/cert.pem").text(),
  secure: true,
  
  onSecure(socket, session, callback) {
    console.log("TLS handshake completed");
    console.log("Cipher:", session.tlsOptions?.name);
    console.log("Protocol:", session.tlsOptions?.version);
    console.log("SNI servername:", session.servername);
    callback(null);
  },
});

Client certificate validation

Require clients to present a valid certificate:
const server = new SMTPServer({
  key: await Bun.file("./cert/key.pem").text(),
  cert: await Bun.file("./cert/cert.pem").text(),
  ca: await Bun.file("./cert/ca.pem").text(),
  
  requestCert: true,
  rejectUnauthorized: true,
  
  secure: true,
  
  onSecure(socket, session, callback) {
    // Client cert was validated by Bun's TLS layer
    console.log("Client authenticated via certificate");
    callback(null);
  },
});

SNI (Server Name Indication)

Serve different certificates for different domains:
const server = new SMTPServer({
  // Default certificate
  key: await Bun.file("./cert/default-key.pem").text(),
  cert: await Bun.file("./cert/default-cert.pem").text(),
  
  // Per-domain certificates
  sniOptions: {
    "mail.example.com": {
      key: await Bun.file("./cert/example-key.pem").text(),
      cert: await Bun.file("./cert/example-cert.pem").text(),
    },
    "smtp.another.com": {
      key: await Bun.file("./cert/another-key.pem").text(),
      cert: await Bun.file("./cert/another-cert.pem").text(),
    },
  },
  
  secure: true,
  
  onSecure(socket, session, callback) {
    console.log(`Connected via SNI: ${session.servername}`);
    callback(null);
  },
});

TLS options reference

key
string | Buffer
required
Private key in PEM format
cert
string | Buffer
required
Certificate in PEM format
ca
string | Buffer | Array<string | Buffer>
Certificate authority bundle for client cert verification
secure
boolean
default:"false"
Start in implicit TLS mode (true) or allow STARTTLS (false)
needsUpgrade
boolean
default:"false"
Require STARTTLS before AUTH and MAIL commands
requestCert
boolean
default:"false"
Request a client certificate during TLS handshake
rejectUnauthorized
boolean
default:"false"
Reject clients with invalid or unverifiable certificates
minVersion
string
Minimum TLS version (e.g., "TLSv1.2")
maxVersion
string
Maximum TLS version (e.g., "TLSv1.3")
sniOptions
Record<string, TLSOptions>
Per-hostname TLS configuration for SNI

Testing TLS connections

openssl s_client -starttls smtp -connect localhost:587
You’ll see:
220 hostname ESMTP
EHLO localhost
250-hostname
250-STARTTLS
250 AUTH PLAIN LOGIN
STARTTLS
250 Ready to start TLS
# TLS handshake begins

Next steps

TLS guide

Learn more about TLS configuration

Authentication

Add authentication to your server

Configuration reference

Explore all TLS options

Callbacks reference

Learn about onSecure callback

Build docs developers (and LLMs) love