6.2. Injecting Email into Your ApplicationThe first thought that usually comes to developers who need to receive email into their application is to create an SMTP server. Stop! Think back to one of our programmer's virtues: Laziness, impatience, and hubris. --Larry Wall Laziness tells us not to reinvent the wheel every time we come to a new problem. In fact, when we look carefully, we very rarely have new problems, but rather endless rehashes of the same old issues. Email, as it happens, is a long-solved problem. There are already a whole bunch of SMTP servers out there; otherwise, email wouldn't be nearly as successful as it is. If only we could leverage the code that's already been written. Of course, leveraging this code is actually trivial. Your servers probably already have an MTA installedif you're on Linux, likely one of Sendmail, Qmail, Postfix, or Exim. We can plug in to these existing technologies to feed mail into our applications, bypassing all the mucking about with sockets and relaying and so on. All of the MTAs mentioned above have a configuration file called /etc/aliases, which contains a table of rulesincoming addresses and where to route them to. The file may reside in a different place, depending on your configuration, but takes the same format regardless. By modifying this file (and then rehashing it) you can easily change the local mail delivery rules. A typical /etc/aliases file looks like this: bin: root root: cal john: john@flickr.com cal: cal@iamcal.com cron: /var/log/cron The first two lines contain local rules. When a message comes in for the user named on the left, it should be delivered to the user named on the right. If a mail is sent to root@hostname, then it will be forwarded to cal@hostname instead. The second two lines contain remote rules. When a message comes in for the user named on the left, it should be delivered to the remote address named on the right. If a mail is sent to cal@hostname, then it will be forwarded to cal@iamcal.com. The final line tells the MTA to take any mail sent to the name on the left and append it to the file on the right. If a message comes in for cron@hostname, it will be appended to the file /var/log/cron. This is all very well, but it doesn't really help us much. We could append mail to a file and then have a cronjob check the file, but that's a very roundabout way of doing it. Luckily, /etc/aliases files also support the Unix piping syntax: uploads: "|/usr/bin/php -q /var/flickr/uploads.php" When mail comes in for the user on the left side, the mail is piped to the command on the right side. If mail is sent to uploads@hostname, it will get piped into the named PHP script; all the script has to do to read it is to read the input from STDIN: $buffer = ''; while (!feof(STDIN)) { $buffer .= fgets(STDIN, 4096); } Once we've received an email, we can then use the same processing logic that we would have if a user had uploaded a file via a web form. This allows us to create one set of core business logic (recall the layers from Chapter 2) that is called by both the vanilla web interface and additional interfaces such as email. If it all sounds too easy, that's because there's a catch. Our application will receive the email in a raw unparsed format. Before we can actually do anything with it, we'll need to parse the contents, identifying the subject, bodies, and any attachments. This is not a trivial task, so we'll look into it in a little detail. 6.2.1. An Alternative ApproachRather than feeding delivered mail straight into your application, you can use another common component of email infrastructure, letting incoming messages be delivered into a local POP or IMAP box. Your application can then periodically read the mail stored in the box and process it. While this doesn't avoid the issue of parsing the email that we're about to get into, it does allow you to get up and running fast. Without making any mail server changes, we can simply point our client code at an existing mailbox (assuming we have the mail client code available) and start reading mail. Since the reading process can be performed manually by running the client script from the command line, this also tends to be easier to debug than the mail server scenario. |