Get your SMTP server running in just a few minutes with these hands-on examples.
Basic SMTP server
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);
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.
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