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 create a basic SMTP server that accepts emails without authentication and saves them to disk.

Complete example

server.ts
import { SMTPServer } from "bun-smtp";
import type { DataStream, SMTPSession } from "bun-smtp";

const server = new SMTPServer({
  authOptional: true,
  onData(stream: DataStream, session: SMTPSession, callback) {
    async function saveEmail() {
      const chunks: Uint8Array[] = [];
      
      // Collect all chunks from the stream
      for await (const chunk of stream) {
        chunks.push(chunk);
      }
      
      // Combine chunks into a single buffer
      const emailContent = Buffer.concat(chunks);
      
      // Save to disk with timestamp
      const filename = `email-${Date.now()}.eml`;
      await Bun.write(filename, emailContent);
      
      console.log(`Saved email to ${filename}`);
      console.log(`From: ${session.envelope.mailFrom?.address}`);
      console.log(`To: ${session.envelope.rcptTo.map(r => r.address).join(", ")}`);
      
      callback(null);
    }
    
    saveEmail().catch(callback);
  },
});

await server.listen(2525);
console.log("SMTP server listening on port 2525");

How it works

1

Configure the server

Set authOptional: true to allow clients to send mail without authenticating.
2

Handle incoming data

The onData callback receives a ReadableStream<Uint8Array> containing the email message.
3

Process the stream

Use a for await...of loop to consume all chunks from the stream.
4

Save the email

Use Bun.write() to save the combined buffer to disk as a .eml file.
5

Complete the transaction

Call callback(null) to send a success response to the client.
You must fully consume the stream before calling the callback. Otherwise, the client will hang waiting for a response.

Testing the server

Send a test email using the mail command or telnet:
telnet localhost 2525
Then type:
HELO localhost
MAIL FROM:<sender@example.com>
RCPT TO:<recipient@example.com>
DATA
Subject: Test Email

This is a test message.
.
QUIT

Accessing envelope information

The session.envelope object contains metadata about the email:
onData(stream, session, callback) {
  console.log("Sender:", session.envelope.mailFrom?.address);
  console.log("Recipients:", session.envelope.rcptTo.map(r => r.address));
  console.log("Body type:", session.envelope.bodyType); // "7bit" or "8bitmime"
  console.log("UTF8:", session.envelope.smtpUtf8);
  console.log("Require TLS:", session.envelope.requireTLS);
  
  // ... process stream
}

Error handling

Reject the email with a custom error code:
onData(stream, session, callback) {
  async function process() {
    const chunks: Uint8Array[] = [];
    for await (const chunk of stream) {
      chunks.push(chunk);
    }
    
    const content = Buffer.concat(chunks).toString();
    
    // Reject spam
    if (content.includes("SPAM")) {
      const err = new Error("Spam detected") as any;
      err.responseCode = 550;
      return callback(err);
    }
    
    callback(null);
  }
  
  process().catch(callback);
}
Always consume the entire stream even if you plan to reject the message. Otherwise, the connection may hang.

Size limits

Set a maximum message size:
const server = new SMTPServer({
  authOptional: true,
  size: 10 * 1024 * 1024, // 10 MB limit
  onData(stream, session, callback) {
    async function process() {
      for await (const chunk of stream) {
        // drain
      }
      
      if (stream.sizeExceeded) {
        const err = new Error("Message too large") as any;
        err.responseCode = 552;
        return callback(err);
      }
      
      console.log(`Received ${stream.byteLength} bytes`);
      callback(null);
    }
    
    process().catch(callback);
  },
});

Next steps

Add authentication

Require users to authenticate before sending

Enable TLS

Encrypt connections with STARTTLS

Configuration reference

Explore all server options

Callbacks reference

Learn about all lifecycle callbacks

Build docs developers (and LLMs) love