Receiving Emails Over SMTP With Elixir

Jon Lunsford
4 min readNov 6, 2017
Simple Mail Transfer Protocol

Let’s see just how simple SMTP is with elixir/erlang. I began experimenting with this mostly as a learning tool to get more familiar with elixir/erlang, I’m also trying to get better at documenting my learning. I will preface this post with that, I am no expert, so any suggestions or recommendations are welcome.

What Is SMPT?

Simply put, it’s a text based protocol in which mail is sent and received by issuing a few simple text commands. Typically this connection is made over TCP. You can think of an SMTP session as writing a letter, you need an envelope that defines who the message is for, who it’s from, and also to hold the message you wish to send. In addition, you can include extra meta information in the form of headers. Here’s an example of a simple telnet session with our SMPT server:

Left: Server. Right: Client

There were four key text commands issued from the client in that session, they were:

HELO: This command is a way for clients to discover what options a server supports. You can think of it as the initial “Hello”. It’s important to note that many servers support ESMTP (extended SMTP) and fallback to plain SMTP. In the example below you can see an EHLO command with the server responding with the enabled options:

MAIL FROM: Establishes the sender, the server will expect the sender to reside on the same domain as was sent with the initial HELO/EHLO commands.

RCPT TO: Establishes the receiver. RCPT sort of reads like “recipient”.

DATA: Indicates the following stream will consist of the message content. It’s here that you can also set any meta data for the message. In our example we redundantly set From: and To:, you would specify things such as the subject, content-type, and a whole litany of other headers.

The Elixir App

With the crash course out of the way, let’s get to what you came here for, the app itself. We’re going to build an Elixir OTP app called Messenger that will receive emails for us. We are going to leverage the gen_smtp erlang library to achieve this, we are really standing on the shoulders of giants here, gen_smtp is a near full implementation of the SMTP protocol in erlang.

First let’s generate a new OTP app, be sure to supply the — sup command so mix will generate our supervision tree for us:

Next, open up mix.exs and add gen_smtp as dependency:

Then run mix deps.get to pull down gen_smtp. gen_smtp expects some configuration when it is started so let’s add that now in config/config.exs:

We are providing the port of 2525 instead of the standard SMTP port 25, since the BEAM process does not run as root (thankfully) we cannot bind to ports under 1024. There are a few ways to get around this in production, either edit your ip tables to forward 2525 to 25. Or set capabilities on the BEAM binary: sudo setcap ‘cap_net_bind_service=ep’ Elixir.Messenger.beam so it can bind to the ports without needing full super user permissions. Here is the full list of options you can supply.

Now we can setup our supervisor that will start :gen_smtp_server as a child process, for the sake of organization I’m going to change lib/messenger.ex (our main module) to be the application entry point:

And change mix.exs to use the main module as well:

Now for the actual supervisor, I’ve renamed lib/messenger/application.ex to lib/messenger/supervisor.ex:

Ok, let’s quickly go over what we just did with our supervisor. We defined a worker for the :gen_smtp_server module (this is the erlang module from gen_smtp). We’ve provided the options Messenger.Server (the callback module that will implement the :gen_smtp_server_session behaviour) and our smtp_opts we defined earlier. We’re almost ready to run our app, first we need to implement the :gen_smtp_server_session behaviour, which itself implements the gen_server behaviour. I’m only going to show the functions needed to handle the main commands demonstrated earlier, you must implement the full behaviour though. You can see the whole example in the GitHub repo. Let’s create the file at lib/messenger/server.ex:

Here we are handling all of the incoming text commands we’ve been talking about. Each time we encounter a piece of data we care about, the from and to fields for example, we simply put them into our state which started out as an empty map. handle_DATA is the most interesting function, this is where you will receive everything provided after the DATA command. This is where you could parse other headers coming in, save the message for later processing, etc.

This was a bare minimum example of how to receive emails over SMTP with ellixir. We haven’t even discussed testing, or different email parsing methods. In production there will be many other factors to consider as well, enabling STARTTLS, handling any extensions you wish, etc.

I’m more than happy to cover testing, email parsing, and all of the other options available if people are interested, simply “applaud” or drop a comment if you would find more posts on this subject useful.

You can find the whole app on GitHub here: https://github.com/jonlunsford/messenger

--

--

Jon Lunsford

Engineer @convertkit, programming enthusiast, father, and musician