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.

Quickstart

Get your first SMTP server running with bun-smtp in just a few steps.

Installation

1

Install bun-smtp

Add bun-smtp to your project:
bun add bun-smtp
Make sure you have Bun 1.2.0 or higher installed. Check your version with bun --version.
2

Create your server file

Create a new file called server.ts:
server.ts
import { SMTPServer } from "bun-smtp";

const server = new SMTPServer({
  authOptional: true,
  onData(stream, session, callback) {
    async function drain() {
      const chunks: Uint8Array[] = [];
      for await (const chunk of stream) {
        chunks.push(chunk);
      }
      console.log(`Received ${chunks.length} chunks from ${session.envelope.mailFrom?.address}`);
      callback(null);
    }
    drain().catch(callback);
  },
});

await server.listen(2525);
console.log("SMTP server listening on port 2525");
This example sets authOptional: true to accept unauthenticated connections. In production, you should implement proper authentication.
3

Run your server

Start the server with Bun:
bun server.ts
You should see:
SMTP server listening on port 2525
4

Test your server

Test your server using curl or telnet. Here’s an example using curl:
curl --url 'smtp://localhost:2525' \
  --mail-from 'sender@example.com' \
  --mail-rcpt 'recipient@example.com' \
  --upload-file - <<EOF
From: sender@example.com
To: recipient@example.com
Subject: Test Email

This is a test message.
EOF
Your server should log the received chunks.

Understanding the Code

Let’s break down what’s happening in the example:

SMTPServer Options

const server = new SMTPServer({
  authOptional: true,  // Allow connections without authentication
  onData(stream, session, callback) {
    // Handle incoming email data
  },
});
The SMTPServer constructor accepts various options. The most important ones for getting started are:
  • authOptional: Set to true to allow unauthenticated connections
  • onData: Callback invoked when email data is ready to be processed

The onData Callback

onData(stream, session, callback) {
  async function drain() {
    const chunks: Uint8Array[] = [];
    for await (const chunk of stream) {
      chunks.push(chunk);
    }
    callback(null);  // Signal success
  }
  drain().catch(callback);  // Handle errors
}
The onData callback receives:
  • stream: A ReadableStream<Uint8Array> containing the email data
  • session: An SMTPSession object with connection and envelope information
  • callback: Call this with null on success, or an error object on failure
The stream must be consumed before calling the callback. Use a for await loop to read all chunks.

Starting the Server

await server.listen(2525);
The listen() method starts the server on the specified port. It returns a Promise that resolves when the server is ready.

Next Steps

Now that you have a basic server running, you can:

Add Authentication

Secure your server with SASL authentication

Enable TLS

Encrypt connections with STARTTLS or implicit TLS

Handle Messages

Save emails to a database or forward them

Configure Options

Explore all available server options

Common Patterns

Save Email to File

import { SMTPServer } from "bun-smtp";
import { join } from "path";

const server = new SMTPServer({
  authOptional: true,
  onData(stream, session, callback) {
    async function saveEmail() {
      const chunks: Uint8Array[] = [];
      for await (const chunk of stream) {
        chunks.push(chunk);
      }
      
      const buffer = Buffer.concat(chunks);
      const filename = `${session.id}-${Date.now()}.eml`;
      await Bun.write(join("./emails", filename), buffer);
      
      callback(null);
    }
    saveEmail().catch(callback);
  },
});

await server.listen(2525);

Access Envelope Information

import { SMTPServer } from "bun-smtp";

const server = new SMTPServer({
  authOptional: true,
  onMailFrom(address, session, callback) {
    console.log(`Mail from: ${address.address}`);
    callback();  // Accept the sender
  },
  onRcptTo(address, session, callback) {
    console.log(`Recipient: ${address.address}`);
    callback();  // Accept the recipient
  },
  onData(stream, session, callback) {
    async function drain() {
      const chunks: Uint8Array[] = [];
      for await (const chunk of stream) {
        chunks.push(chunk);
      }
      
      console.log(`Envelope:`, {
        from: session.envelope.mailFrom?.address,
        to: session.envelope.rcptTo.map(r => r.address),
      });
      
      callback(null);
    }
    drain().catch(callback);
  },
});

await server.listen(2525);

Reject Invalid Senders

import { SMTPServer } from "bun-smtp";
import type { SMTPError } from "bun-smtp";

const server = new SMTPServer({
  authOptional: true,
  onMailFrom(address, session, callback) {
    const allowedDomain = "example.com";
    
    if (!address.address.endsWith(`@${allowedDomain}`)) {
      const error = new Error("Invalid sender domain") as SMTPError;
      error.responseCode = 550;
      return callback(error);
    }
    
    callback();  // Accept the sender
  },
  onData(stream, session, callback) {
    async function drain() {
      const chunks: Uint8Array[] = [];
      for await (const chunk of stream) {
        chunks.push(chunk);
      }
      callback(null);
    }
    drain().catch(callback);
  },
});

await server.listen(2525);
Always validate and sanitize email addresses and data before processing them to prevent security vulnerabilities.

Build docs developers (and LLMs) love