smtp-server

repository·master·Indexed 21 days ago

https://github.com/nodemailer/smtp-server

A programmable SMTP and LMTP server implementation for Node.js, part of the Nodemailer ecosystem. It allows developers to create custom servers on the fly to intercept, process, or relay incoming emails using the SMTPServer class and SMTPStream. Features include support for TLS with SNI, PROXY protocol v1, message size limiting via the SIZE extension, and a variety of session handlers such as onConnect, onAuth, and onData.

Tokens
3.4K
Snippets
8
Records
13
Agent score
75%

What's inside smtp-server

  1. Using the SIZE extension to limit message size

    master

    You can advertise a maximum message size to clients by setting the size option in the SMTPServer configuration.

    When a client declares a size in the MAIL FROM:<addr> SIZE=nnn parameter, the server checks it against your limit. However, the server does not automatically block the transfer; instead, it streams the data to your application. You must handle enforcement within your onData handler by checking the stream.sizeExceeded flag.

    To prevent large transfers from consuming too much bandwidth, you can check the stream.sizeExceeded flag during the data event to stop the transfer early, rather than waiting for the end event.

    const server = new SMTPServer({
        size: 1024 * 1024, // 1 MiB limit
        onData(stream, session, callback) {
            stream.on('end', () => {
                if (stream.sizeExceeded) {
                    const err = new Error('Message too large');
                    err.responseCode = 552;
                    return callback(err);
                }
                callback(null, 'OK');
            });
            stream.resume(); // Consume the stream
        }
    });
  2. Test your SMTP server using sendmail

    master

    Once your SMTPServer is running, you can test it by sending an email from your terminal using the sendmail command.

    1. Create a text file (e.g., email.txt) containing the email headers and body.
    2. Run your server script (e.g., node smtp.js).
    3. Use sendmail to pipe the file content to a local address.

    Example workflow:

    # 1. Create the email file
    cat <<EOF > email.txt
    Subject: Terminal Email Send
    
    Email Content line 1
    Email Content line 2
    EOF
    
    # 2. Run the server (may require sudo for port 25)
    sudo node smtp.js &
    
    # 3. Send the email
    sendmail test@localhost < email.txt
    sudo node smtp.js &
    sendmail test@localhost < email.txt
  3. Handle SMTP session events with SMTPServer handlers

    master

    Instead of using an event emitter for everything, you can provide shorthand handler functions directly in the options object passed to the SMTPServer constructor. These handlers allow you to intercept different stages of the SMTP lifecycle:

    • onConnect(session, callback): Called when a client connects.
    • onSecure(socket, session, callback): Called when a connection is secured via TLS.
    • onAuth(auth, session, callback): Called when a client attempts to authenticate. auth contains the method and credentials.
    • onMailFrom(address, session, callback): Called when the MAIL FROM command is received.
    • onRcptTo(address, session, callback): Called when the RCPT TO command is received.
    • onData(stream, session, callback): Called when the DATA command is received. The stream argument is a readable stream containing the email content.
    • onClose(session): Called when the connection is closed.
    const server = new SMTPServer({
        onConnect: (session, callback) => {
            console.log('Client connected');
            callback();
        },
        onData: (stream, session, callback) => {
            stream.on('data', (chunk) => {
                console.log('Received chunk:', chunk.toString());
            });
            stream.on('end', () => {
                callback();
            });
        }
    });
  4. Create a basic SMTP server with SMTPServer

    master

    To create a basic SMTP server, instantiate SMTPServer from the smtp-server package. You can configure the server using an options object.

    Key configuration options used in this example:

    • disabledCommands: An array of SMTP commands to disable (e.g., ['STARTTLS', 'AUTH']).
    • logger: Set to true to enable logging.
    • onData: A callback function triggered when data is received. It receives a stream (the email data), the session object, and a callback to signal completion.

    After instantiation, call .listen(port) to start the server on the specified port.

    // smtp.js
    const { SMTPServer } = require('smtp-server');
    
    const server = new SMTPServer({
        // disable STARTTLS to allow authentication in clear text mode
        disabledCommands: ['STARTTLS', 'AUTH'],
        logger: true,
        onData(stream, session, callback) {
            stream.pipe(process.stdout); // print message to console
            stream.on('end', callback);
        }
    });
    
    server.listen(25);
  5. Configure maxCommandLength in SMTPStream

    master

    When instantiating SMTPStream, you can provide an options object to set the maxCommandLength. This defines the maximum number of bytes allowed for a single command line before an error is thrown. If not provided, it defaults to 4096 (4KB).

    const options = { maxCommandLength: 8192 };
    const stream = new SMTPStream(options);
  6. Use PROXY protocol for connection information

    master

    The SMTPServer supports the PROXY protocol (v1) to retrieve the original client's IP address and port when the server is behind a load balancer or proxy.

    To enable this, set useProxy in your options. useProxy can be a boolean or an array of allowed IP addresses (or '*'). If useProxy is enabled, the server will attempt to read the PROXY header from the start of the connection. If the header is malformed or exceeds MAX_PROXY_HEADER_LENGTH (1024 bytes), the connection will be rejected with a * BAD Invalid PROXY header response.

  7. Configure SNI and multiple TLS contexts

    master

    The SMTPServer supports Server Name Indication (SNI), allowing you to serve different TLS certificates based on the hostname requested by the client.

    You can configure this using the sniOptions property in the server options. sniOptions can be an object where keys are domain names and values are TLS option objects, or a Map with the same structure.

    Example using an object:

    const server = new SMTPServer({
        secure: true,
        sniOptions: {
            'example.com': { key: fs.readFileSync('example.key'), cert: fs.readFileSync('example.crt') },
            'test.org': { key: fs.readFileSync('test.key'), cert: fs.readFileSync('test.crt') }
        }
    });
  8. Get TLS options for the SMTP server

    master

    Use the getTLSOptions function to generate a configuration object for TLS. This function merges your custom options with a set of default values.

    Warning: The default configuration includes pregenerated certificates for localhost. These are intended for development and testing only; do not use them in production.

    Default settings include:

    • honorCipherOrder: true
    • requestOCSP: false
    • minVersion: 'TLSv1' (to support older SMTP clients)
    • sessionIdContext: A hash derived from process.argv
    const getTLSOptions = require('smtp-server/lib/tls-options');
    
    const options = getTLSOptions({
        key: 'path/to/your/private-key.pem',
        cert: 'path/to/your/certificate.pem'
    });
  9. Close the SMTPServer gracefully

    master

    To shut down the server, call the .close(callback) method. This method performs a two-step shutdown:

    1. It stops accepting new connections via the underlying network server.
    2. It waits for a period defined by options.closeTimeout (or a default of 30 seconds) for existing connections to finish. If connections are still active after this timeout, the server will forcibly close them by sending a 421 Server shutting down response and terminating the connections.

    If a callback is provided to .close(), it will be executed once the server has stopped accepting new connections. If you want to wait for the full timeout/cleanup, you may need to manage that logic via the close event emitted by the server.

  10. Create an SMTP server with SMTPServer

    master

    The SMTPServer class is the primary entrypoint for creating and configuring an SMTP or LMTP server instance. You instantiate it by passing an options object containing connection and SMTP settings. The server can be started by calling .listen() and stopped by calling .close().

    Common configuration options include:

    • secure: Boolean indicating if the server should use TLS.
    • authMethods: Array of allowed authentication methods (defaults to ['LOGIN', 'PLAIN']).
    • disabledCommands: Array of SMTP commands to disable.
    • hideENHANCEDSTATUSCODES: Boolean (defaults to true).
    • hideDSN: Boolean (defaults to true).
    • hideREQUIRETLS: Boolean (defaults to true).
    • closeTimeout: Time in milliseconds to wait for pending connections to close before forcing them shut (defaults to 30,000ms).
    const { SMTPServer } = require('smtp-server');
    
    const server = new SMTPServer({
        host: '127.0.0.1',
        port: 587,
        secure: false,
        auth: async (data, session, callback) => {
            // Implement authentication logic
            callback(null, { user: 'user' });
        }
    });
    
    server.listen(587, () => {
        console.log('Server is listening');
    });
  11. Use startDataMode() to receive message content

    master

    When an SMTP DATA command is received, you should switch the SMTPStream from command mode to data mode using startDataMode(maxBytes). This allows you to receive the actual email body as a stream.

    Workflow:

    1. Switch Modes: Call startDataMode(maxBytes) inside your oncommand handler. This returns a PassThrough stream representing the message content.
    2. Handle Data: Listen to the returned stream for data. The stream automatically handles unescaping dots (e.g., .. at the start of a line is treated as a literal dot).
    3. Monitor Size: The returned stream has two useful properties you can monitor mid-transfer:
      • byteLength: The current number of bytes received.
      • sizeExceeded: A boolean that becomes true if the received bytes exceed the maxBytes limit you provided.
    4. Finish Processing: Once you have finished processing the data stream, call continue() on the SMTPStream instance to return to command mode.
    // Inside your oncommand override:
    if (cmd === 'DATA') {
        const dataStream = this.startDataMode(10 * 1024 * 1024); // 10MB limit
    
        dataStream.on('data', (chunk) => {
            console.log('Received chunk:', chunk.length, 'bytes');
            if (dataStream.sizeExceeded) {
                console.warn('Warning: Message size exceeded limit');
            }
        });
    
        dataStream.on('end', () => {
            console.log('Data transfer complete');
            this.continue(); // Return to command mode
        });
    }