Skip to main content
Get your SMTP server running in just a few minutes with these hands-on examples.

Basic SMTP server

1

Create your server file

Create a new file server.js with the following code:
const { SMTPServer } = require('smtp-server');

const server = new SMTPServer({
  onData(stream, session, callback) {
    stream.pipe(process.stdout); // Print message to console
    stream.on('end', callback);
  }
});

server.listen(2525);
2

Start the server

Run your server:
node server.js
3

Test it

Send a test email using telnet or any SMTP client pointing to localhost:2525.
This basic server accepts all connections and prints received messages to the console.

Server with authentication

Add authentication to require credentials before accepting mail:
const { SMTPServer } = require('smtp-server');

const server = new SMTPServer({
  // Enable authentication
  onAuth(auth, session, callback) {
    const username = 'testuser';
    const password = 'testpass';

    // Validate credentials
    if (
      auth.username === username &&
      (auth.method === 'CRAM-MD5'
        ? auth.validatePassword(password)
        : auth.password === password)
    ) {
      return callback(null, {
        user: auth.username // Store user info in session
      });
    }

    return callback(new Error('Authentication failed'));
  },

  onData(stream, session, callback) {
    console.log(`Message from: ${session.envelope.mailFrom.address}`);
    console.log(`Message to: ${session.envelope.rcptTo.map(r => r.address).join(', ')}`);
    
    stream.pipe(process.stdout);
    stream.on('end', () => {
      callback(null, 'Message accepted');
    });
  }
});

server.listen(2525);
This example supports multiple authentication methods: PLAIN, LOGIN, and CRAM-MD5.

Validate addresses

Add validation logic to control which addresses can send or receive mail:
const server = new SMTPServer({
  onMailFrom(address, session, callback) {
    // Reject senders with addresses starting with 'deny'
    if (/^deny/i.test(address.address)) {
      return callback(new Error('Sender not accepted'));
    }
    callback(); // Accept
  },

  onRcptTo(address, session, callback) {
    // Reject recipients starting with 'deny'
    if (/^deny/i.test(address.address)) {
      return callback(new Error('Recipient not accepted'));
    }
    callback(); // Accept
  },

  onData(stream, session, callback) {
    stream.pipe(process.stdout);
    stream.on('end', callback);
  }
});

server.listen(2525);

Enforce size limits

Limit message sizes to prevent abuse:
const server = new SMTPServer({
  size: 10 * 1024 * 1024, // 10 MB max

  onRcptTo(address, session, callback) {
    // Check SIZE parameter from MAIL FROM command
    const size = Number(session.envelope.mailFrom?.args?.SIZE) || 0;
    
    // Reject if over quota for specific user
    if (address.address === '[email protected]' && size > 100) {
      const err = new Error('Mailbox full');
      err.responseCode = 452;
      return callback(err);
    }
    
    callback();
  },

  onData(stream, session, callback) {
    stream.pipe(process.stdout);
    stream.on('end', () => {
      // Check if size limit was exceeded
      if (stream.sizeExceeded) {
        const err = new Error('Message too large');
        err.responseCode = 552;
        return callback(err);
      }
      callback(null, 'Message accepted');
    });
  }
});

server.listen(2525);
Always set a size limit in production to prevent resource exhaustion attacks.

Production-ready example

Here’s a complete example with logging, TLS, and error handling:
const { SMTPServer } = require('smtp-server');
const fs = require('fs');

const server = new SMTPServer({
  // Enable logging
  logger: true,
  
  // Custom banner
  banner: 'Welcome to My SMTP Server',
  
  // Message size limit
  size: 10 * 1024 * 1024,
  
  // TLS configuration
  secure: false, // Use STARTTLS instead
  key: fs.readFileSync('private.key'),
  cert: fs.readFileSync('certificate.crt'),
  
  // Authentication
  onAuth(auth, session, callback) {
    // TODO: Validate against your user database
    callback(new Error('Authentication not implemented'));
  },
  
  // Sender validation
  onMailFrom(address, session, callback) {
    // TODO: Implement sender policy
    callback();
  },
  
  // Recipient validation
  onRcptTo(address, session, callback) {
    // TODO: Check if recipient exists
    callback();
  },
  
  // Message handling
  onData(stream, session, callback) {
    // TODO: Save to database or forward to mail processor
    stream.pipe(process.stdout);
    stream.on('end', () => {
      if (stream.sizeExceeded) {
        const err = new Error('Message too large');
        err.responseCode = 552;
        return callback(err);
      }
      callback(null, 'Message queued');
    });
  }
});

// Error handling
server.on('error', (err) => {
  console.error('Server error:', err);
});

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

// Graceful shutdown
process.on('SIGTERM', () => {
  server.close(() => {
    console.log('Server closed');
    process.exit(0);
  });
});

LMTP server

Create an LMTP (Local Mail Transfer Protocol) server for local delivery:
const { SMTPServer } = require('smtp-server');

const server = new SMTPServer({
  lmtp: true, // Enable LMTP mode
  
  onData(stream, session, callback) {
    stream.pipe(process.stdout);
    stream.on('end', () => {
      // LMTP requires per-recipient response
      callback(null, true);
    });
  }
});

server.listen(2424);
LMTP differs from SMTP in greeting (LHLO instead of EHLO) and supports per-recipient status responses.

Access session information

The session object contains useful connection and transaction data:
const server = new SMTPServer({
  onConnect(session, callback) {
    console.log('Connection from:', session.remoteAddress);
    callback(); // Accept connection
  },

  onData(stream, session, callback) {
    // Access envelope information
    console.log('Connection ID:', session.id);
    console.log('From:', session.envelope.mailFrom.address);
    console.log('To:', session.envelope.rcptTo.map(r => r.address));
    console.log('Client hostname:', session.clientHostname);
    console.log('Authenticated as:', session.user);
    
    stream.pipe(process.stdout);
    stream.on('end', callback);
  }
});

server.listen(2525);

Error handling

Return proper SMTP error codes for different scenarios:
const server = new SMTPServer({
  onRcptTo(address, session, callback) {
    // Temporary failure (4xx)
    if (address.address === '[email protected]') {
      const err = new Error('Mailbox temporarily unavailable');
      err.responseCode = 450;
      return callback(err);
    }
    
    // Permanent failure (5xx)
    if (address.address === '[email protected]') {
      const err = new Error('Mailbox does not exist');
      err.responseCode = 550;
      return callback(err);
    }
    
    callback(); // Success (250)
  },

  onData(stream, session, callback) {
    stream.pipe(process.stdout);
    stream.on('end', callback);
  }
});

server.listen(2525);
Use 4xx codes for temporary errors (client should retry) and 5xx codes for permanent errors (client should not retry).

Next steps

Server Configuration

Learn about all available configuration options

Authentication

Implement secure authentication methods

TLS Security

Set up TLS/STARTTLS for encrypted connections

API Reference

Explore the complete API documentation

Build docs developers (and LLMs) love