Wednesday, 20 May 2009

The Law of Conservation of Misery

At university I had a professor who would jokingly refer to his Law of Conservation of Misery:

The total amount of misery in a system is constant

which implies that when you try and decrease the misery in one aspect of a system, you will increase the misery in some of the other aspects.

There are many types of misery. In software engineering projects these could include non-maintainability, non-performance and budget constraints.

Personally, I think people are too prone to think that there are zero-sum monsters lurking under each bed, so my own take on the "law" would be:

The total amount of misery in a system is > 0

Which implies that you can make tradeoffs that decrease the total amount of misery, but you can never eliminate it completely. A bad implementation of a system will have a higher total amount of misery, and a good one will have less, but you will always have at least a little bit!

Tuesday, 19 May 2009

How to send email via Gmail using Erlang

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...