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.

Overview

bun-smtp is designed as a drop-in replacement for the smtp-server npm package. Most servers can migrate by simply changing the import and updating the onData callback to work with Web Streams.
bun-smtp maintains API compatibility with smtp-server to make migration seamless. All constructor options, callback signatures, and session properties remain the same.

Installation

npm install smtp-server

Import Statement

const { SMTPServer } = require('smtp-server');
// or
import { SMTPServer } from 'smtp-server';

Key Differences

1. onData Stream Type

This is the primary change required when migrating. smtp-server passes a Node.js Readable stream, while bun-smtp passes a Web ReadableStream<Uint8Array>.
onData(stream, session, callback) {
  const chunks = [];
  stream.on("data", (chunk) => chunks.push(chunk));
  stream.on("end", () => {
    const body = Buffer.concat(chunks);
    console.log("Received message:", body.length, "bytes");
    callback(null);
  });
  stream.on("error", callback);
}

Discarding the Stream

To discard the message body without reading it:
onData(stream, session, callback) {
  stream.resume();
  stream.on("end", () => callback(null));
  stream.on("error", callback);
}

Stream Properties

Both implementations provide byteLength and sizeExceeded properties:
  • stream.byteLength is set after the stream closes
  • stream.sizeExceeded becomes true when the message exceeds options.size
onData(stream, session, callback) {
  async function process() {
    for await (const chunk of stream) {
      // Check if size limit exceeded during iteration
      if (stream.sizeExceeded) {
        callback(new Error("Message too large"));
        return;
      }
    }
    // byteLength is available after the stream completes
    console.log("Total bytes:", stream.byteLength);
    callback(null);
  }
  process().catch(callback);
}

2. Logger Option Removed

smtp-server accepts a logger option (bunyan-compatible). bun-smtp does not support this option.
const server = new SMTPServer({
  logger: bunyanLogger,
});

3. onSecure Socket Type

The socket argument in onSecure is a Bun Socket, not a Node.js tls.TLSSocket. TLS details are available on session.tlsOptions.
onSecure(socket, session, callback) {
  console.log("Cipher:", socket.getCipher());
  console.log("Protocol:", socket.getProtocol());
  callback();
}

What Stays the Same

Everything else is a direct drop-in replacement:
1

Constructor Options

All options have the same names, types, and default values:
const server = new SMTPServer({
  secure: true,
  key: readFileSync("server.key"),
  cert: readFileSync("server.crt"),
  authMethods: ["PLAIN", "LOGIN"],
  authOptional: false,
  size: 10 * 1024 * 1024,
  // ... all other options work identically
});
2

Callbacks

All callback signatures remain unchanged:
  • onConnect(session, callback)
  • onAuth(auth, session, callback)
  • onMailFrom(address, session, callback)
  • onRcptTo(address, session, callback)
  • onData(stream, session, callback) — only the stream type changes
  • onClose(session)
3

Auth Objects

Auth object shapes for all methods are identical:
  • PLAIN/LOGIN: { method, username, password }
  • CRAM-MD5: { method, username, challenge, challengeResponse, validatePassword() }
  • XOAUTH2: { method, username, accessToken }
4

Session & Envelope

SMTPSession and SMTPEnvelope structures are identical:
onData(stream, session, callback) {
  console.log("From:", session.envelope.mailFrom?.address);
  console.log("To:", session.envelope.rcptTo.map(r => r.address));
  console.log("Secure:", session.secure);
  console.log("User:", session.user);
}
5

Error Handling

Custom SMTP error codes work the same way:
const error = new Error("Mailbox full");
error.responseCode = 552;
callback(error);
6

TLS Options

All TLS options are identical:
  • key, cert, ca
  • sniOptions
  • requestCert, rejectUnauthorized
  • minVersion, maxVersion
7

Server Methods

  • server.listen(port, callback)
  • server.close(callback)
  • server.updateSecureContext(options)
  • Events: "listening", "close", "error", "connect"

Migration Checklist

1

Update dependencies

bun remove smtp-server
bun add bun-smtp
2

Update imports

// Change this:
import { SMTPServer } from 'smtp-server';

// To this:
import { SMTPServer } from 'bun-smtp';
3

Update onData callback

Convert Node.js stream handlers to Web Streams:
// Before:
stream.on("data", handler);
stream.on("end", handler);
stream.on("error", handler);

// After:
for await (const chunk of stream) { /* ... */ }
4

Remove logger option

If you’re using the logger option, remove it and add logging to individual callbacks.
5

Update onSecure if used

If you’re using onSecure, update TLS property access:
// Before: socket.getCipher()
// After: session.tlsOptions?.name
6

Test thoroughly

Test all authentication methods, TLS configurations, and message handling scenarios.

Complete Migration Example

const { SMTPServer } = require('smtp-server');
const { readFileSync } = require('fs');

const server = new SMTPServer({
  secure: false,
  key: readFileSync('server.key'),
  cert: readFileSync('server.crt'),
  authOptional: false,
  
  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) {
    const chunks = [];
    stream.on('data', (chunk) => chunks.push(chunk));
    stream.on('end', () => {
      const message = Buffer.concat(chunks).toString();
      console.log('Received:', message.length, 'bytes');
      callback(null);
    });
    stream.on('error', callback);
  },
});

server.listen(2525, () => {
  console.log('Server listening on port 2525');
});

Performance Benefits

By switching to bun-smtp, you get:

Faster Startup

Bun’s fast runtime means your server starts instantly, perfect for serverless deployments.

Lower Memory

Native Web Streams reduce memory overhead compared to Node.js streams.

Built-in TypeScript

No need for @types packages — everything is fully typed out of the box.

Modern APIs

Web-standard APIs make your code more portable and future-proof.

Troubleshooting

This means you’re trying to use Node.js stream methods on a Web ReadableStream. Update your onData callback to use for await...of instead of .on() event listeners.
// Wrong:
stream.on("data", handler);

// Correct:
for await (const chunk of stream) { /* ... */ }
bun-smtp doesn’t support the logger option. Remove it from your configuration and add logging directly in your callbacks:
onConnect(session, callback) {
  console.log(`[${session.id}] Connected:`, session.remoteAddress);
  callback();
}
In onSecure, the socket is a Bun Socket, not a Node.js TLS socket. Use session.tlsOptions instead:
// Wrong:
const cipher = socket.getCipher();

// Correct:
const cipher = session.tlsOptions?.name;

Need Help?

If you encounter issues during migration:
  • Check the API Reference for detailed documentation
  • Review the source code for implementation details
  • Open an issue on GitHub if you find a compatibility problem

Build docs developers (and LLMs) love