How it works...

SMTP is based upon a series of plain text communications between an SMTP client and server over a TCP connection. The smtp-protocol module carries out these communications for us.

When we call the createServer method, we pass an event handler that is passed a request object (req). This object is an event emitter (it inherits from the core events module EventEmitter constructor). We listen for to, message, and error events.

The to event supplies two arguments to the handler callback (our filter function). The first argument is the full recipient address, the second (named ack) allows us to accept or reject the incoming message.

Our filter function uses parameter object deconstruction to pull out the accept and reject functions from the ack object and then uses assignment array deconstruction to define the user and host references. When we call split on the to string, splitting by the at sign (@), we should have an array with two elements. Using deconstruction, user points to index 0 of the resulting array while host points at index 1.

Our hosts and users whitelists are native Set objects. We use the has method to determine whether each side of the email address matches our criteria. If it does, we call accept; if it doesn't we reject the recipient. At a protocol level a message prefixed with a 250 code (successful action) will be sent when we call accept. By default, reject will respond with a code of 500 (command unrecognized), but we specify 550 (mailbox unavailable), which is more appropriate to the case.

The SMTP protocol allows for multiple recipients, until a message data message is sent. Each accepted recipient is added to an array stored on req.to (the smtp-protocol builds this array internally). When a client sends a data message, the message event handler is fired.

The message event handler is passed two arguments, stream and ack. As with the to event handler, ack allows us to accept or reject. The stream object is a stream of the message body.

We call our save function in the message event handler, with the req, stream, and ack objects.

The save function (like the filter function) deconstructs the ack object at the parameter level (we only need the accept function in this case). The to and from properties are pulled from the req object, also via deconstruction. Since we aren't performing any validating steps here, we can just immediately call the accept function.

The to reference is always an array (since there may be multiple recipients). We look through it with forEach and create a write stream (mail), pointing to the user's mailbox, to a filename constructed from the from email address, and a timestamp (dest). We write a simple header to the file, with From and To fields, and then pipe from our incoming message body stream (stream) to our file write stream (mail). When we call the pipe method, we supply a second options argument, with end set to false. This is important when piping from a source stream to multiple destinations. Without passing this option, the first destination stream to finish would call end on the message body stream. Any other reading from the stream to other destination streams would cease, so data would be lost.