Receiving email with Node.js

Servers use the SMTP protocol for sending and receiving email. SMTP is an application layer protocol, like HTTP, FTP, and SSH.

To start receiving email, one needs to have an SMTP server listening on port 25. An SMTP server is a computer/machine/process/application that accepts TCP connections and uses the SMTP protocol. Similarly, the HTTP client normally connects to the host's TCP port 80.

smtp-server

Thesmtp-server` npm module is a library for creating SMTP servers in Node.js similar to how you create HTTP servers.

The cool thing is that this way it's very easy to see everything that is sent your way by accepting all incoming mail. Most full-blown email server applications like Postfix by default restrict recipient addresses and reject other emails. With your own email server, you can check emails imperatively. Similar to how Apache Server and nginx are configurable using httpd.conf and nginx.conf, but with Express.js you can achieve anything you want much easier.

Start SMTP server

const SMTPServer = require('smtp-server').SMTPServer;
const server = new SMTPServer({
onData: (stream, session, callback) => {
// Process message
}
});
server.listen(25);

An incoming email fires the onData callback. Use this handler to get the stream for the incoming message:

const SMTPServer = require('smtp-server').SMTPServer;
const server = new SMTPServer({
onData: (stream, session, callback) => {
stream.pipe(process.stdout); // Print the message to stdout
stream.on('end', callback); // Must run the callback once done
}
});
server.listen(25);

The session object passed to the handler includes useful properties such as remoteAddress (the IP address of the connected client) and envelope. The envelope is the email message metadata sent outside of the message headers and body and includes the mailFrom and rcptTo properties.

The cool thing is that the stream includes the message as-is with no modifications.

Notifying the sender that an error has occurred

If your server was unable to process (save or relay) the incoming message, your server should notify the connected sender client of an error by rejecting the message. Pass an error object to the callback:

const SMTPServer = require('smtp-server').SMTPServer;
const server = new SMTPServer({
onData: (stream, session, callback) => {
// Failed to process the message
callback(new Error('Oops, something went wrong'));
}
});
server.listen(25);

Finding the recipient and sender of the email

The recipient is passed in with the RCPT TO command (that address is included in the envelope) and as a message header. Check both values because they may differ.

SMTP does not have a mechanism for verifying the sender. It's similar to seeing "From" on a physical envelope.

Testing SMTP server

When a client connects to your SMTP server, they exchange SMTP commands that include HELO, EHLO, DATA, and so on.

Security

An email server listening on port 25 should support upgrading to TLS. SMTPServer supports it by default, and all you need to do is add key and cert to the options passed to the SMTPServer constructor:

const SMTPServer = require('smtp-server').SMTPServer;
const server = new SMTPServer({
secure: false,
key: fs.readFileSync('private.key'),
cert: fs.readFileSync('server.crt'),
onData: (stream, session, callback) => {
// Process incoming messages
}
});
server.listen(25);

Notice that we also have secure: false (it is false by default). Setting secure to true makes the SMTP server only understand TLS and reject non-TLS. Email clients that support TLS still start with non-TLS when they connect to port 25.

This is similar to configuring an HTTPS server in Node.js. HTTPS will normally listen on port 443, and HTTP on port 80.

Relaying email

If your server receives an email addressed to someone else, you can choose to relay it (send it to the destination). This qualifies your email server as an open relay.

See also

Made by Anton Vasetenkov.

If you want to say hi, you can reach me on LinkedIn or via email. If you like my work, you can support me by buying me a coffee.