One of my pet projects,
www.dayfindr.com, integrates with email to send notifications to users.
I use
Google Apps for email infrastructure, so you need an SMTP client that supports
TLS. At the time, I couldn't find a simple Erlang SMTP client that could handle TLS, so I used a command-line SMTP client.
For my new pet project, for want of a better name temporarily called
The Isabelle Project, I need to add some email functionality. This time I would prefer to use an Erlang solution with proper error handling and logging.
I looked at the
SMTP protocol on Wikipedia, and it didn't seem to difficult. Erlang's built-in ssl module also seemed to support TLS. So, with a bit of trial and error, here's the result:
-module(smtp).
-export([connect/0]).
connect() ->
{ok, Socket} = ssl:connect("smtp.gmail.com", 465, [{active, false}], 1000),
recv(Socket),
send(Socket, "HELO localhost"),
send(Socket, "AUTH LOGIN"),
send(Socket, binary_to_list(base64:encode("___@gmail.com"))),
send(Socket, binary_to_list(base64:encode("johngalt"))),
send(Socket, "MAIL FROM: <___@gmail.com>"),
send(Socket, "RCPT TO:<___@gmail.com>"),
send(Socket, "DATA"),
send_no_receive(Socket, "From: <___@gmail.com>"),
send_no_receive(Socket, "To: <___@gmail.com>"),
send_no_receive(Socket, "Date: Tue, 15 Jan 2008 16:02:43 +0000"),
send_no_receive(Socket, "Subject: Test message"),
send_no_receive(Socket, ""),
send_no_receive(Socket, "This is a test"),
send_no_receive(Socket, ""),
send(Socket, "."),
send(Socket, "QUIT"),
ssl:close(Socket).
send_no_receive(Socket, Data) ->
ssl:send(Socket, Data ++ "\r\n").
send(Socket, Data) ->
ssl:send(Socket, Data ++ "\r\n"),
recv(Socket).
recv(Socket) ->
case ssl:recv(Socket, 0, 1000) of
{ok, Return} -> io:format("~p~n", [Return]);
{error, Reason} -> io:format("ERROR: ~p~n", [Reason])
end.
And the output from the Erlang shell:
3> application:start(ssl).
ok
4> smtp:connect().
"220 mx.google.com ESMTP y37sm613282mug.19\r\n"
"250 mx.google.com at your service\r\n"
"334 VXNlcm5hbWU6\r\n"
"334 UGFzc3dvcmQ6\r\n"
"235 2.7.0 Accepted\r\n"
"250 2.1.0 OK y37sm613282mug.19\r\n"
"250 2.1.5 OK y37sm613282mug.19\r\n"
"354 Go ahead y37sm613282mug.19\r\n"
"250 2.0.0 OK 1242683885 y37sm613282mug.19\r\n"
"221 2.0.0 closing connection y37sm613282mug.19\r\n"
ok
The only tricky bit is that for the AUTO LOGIN, the received text and the username and password you send is base-64 encoded. By default the connect is active=false, which means the responses are send to the creating process directly. Using passive mode requires explicit receiving of the response using ssl:recv/2
You'll have to handle errors better if you use this in production, but the basic protocol is pretty straightforward...