Wiki

Clone wiki

alexandria / WorkingWithMail

Working With Mail

Web apps don't typically need to send much mail on a page by page basis, but some areas of some apps do need to be able to efficiently inject a decent chunk of mail to a local MTA for delivery. The kinds of scenarios that we see are people sending out updates to subscribers, either to let them know about their favorite football team or to send out news or health information to the public or health professionals.

One of the problems that we typically see for this kind of app is that the existing mailing features in PHP and those of the popular add-on mailing classes can either be generating the mail or be busy sending a given mail. There's no parallelism in the architecture because PHP itself doesn't allow for it.

One of the modules present in Alexandria is designed to provide a way to construct mail messages and efficiently send out batches. It does this using a PHP extension that forks off a child process to manage the SMTP injection portion of the mailing, leaving the main PHP script free to query information from a database and templatize the mail being sent.

The asychronous nature of this injection process makes error and status handling a little different from what you might be used to using, but it's not too huge a hurdle.

Let's walk through setting up the injector and then look at building an app that sends a batch of mail.

The SMTP Injector Extension

The smtp_injector extension source code can be checked out of the subversion repository. It's intended to be used with the mailer classes we provide in Alexandria, so its interface is fairly low level.

How to install the injector extension

Check the code out of bitbucket.

hg clone https://wez@bitbucket.org/wez/alexandria/

Build and install the extension.

$ cd smtp_injector
$ phpize
$ ./configure
$ make
$ sudo make install

Now add the following line to your php.ini

extension=smtp_injector.so

Restart your web server, if you plan to inject from there.

We provide a class named OmniTI_Mail_Mailer that allows you to generate and send mail, read on for an example of how to use it.

<?php
# This example uses the mail injection extension for sending a batch of mail
# asynchronously and then waiting for the results
include 'OmniTI/Mail/Mailer.php';

$t = new OmniTI_Mail_Transport_SMTP_Injector;
OmniTI_Mail_Mailer::setTransport($t);

$m = new OmniTI_Mail_Mailer;
$m->setFrom('user@example.com', 'My Sender Name');
$m->setVerpPattern('bounces-%TO%@example.com');
$m->addRecipient('wez@example.com', 'That wez guy');
$m->setSubject('Testing');
$m->setBodyText("This is a simple test \xa9 2007.", "iso-8859-1");
$m->setBodyHTML("<em>This is a simple test \xa9 2007.</em>", "iso-8859-1");
$res = $m->send();
var_dump($res);

do {
  $st = $t->getAsyncStatus(true);
  if ($st === true) continue;
  if ($st === false) break;
  var_dump($st);
} while (true);

The code example above creates an instance of the injector transport, which by default submits mail to an MTA listening on the loopback address (127.0.0.1) on port 25 for SMTP submissions. It then sets the mailer class up to use that transport for all mail submissions.

It then creates an instance of the mailer class and sets up an email message that will appear to be from “My Sender Name” with the email address user@example.com. This will be the address that humans will reply to if they decide to do so.

The setVerpPattern call effectively sets the Return-Path for the message to bounces-wez=example.com@example.com. This is the address to which bounce mail will be sent if this message cannot be delivered successfully.

The message is then constructed using “That wez guy” as the name of the recipient and wez@example.com as his email address. The subject is set to “Testing” and a message with both plain text and HTML parts is created, both of which contain the same content, although the HTML part is rendered in italics. The mysterious \xa9 is the iso-8859-1 code value for the Copyright symbol, so the message text is actually “This is a simple test © 2007.”

The send method causes the mail to be sent. It returns and array containing status information for each recipient of the message. If mail submission fails immediately, the status information will include the return path address, the recipient address and the SMTP status code explaining the reason for the failure. Usually, this function will just return an empty array which means that the message send hasn't completed yet.

After having sent our batch of 1 message, we go into a loop to wait for all the mail in the batch to complete. The getAsyncStatus method of the transport returns information in a similar format to the send method of the mailer class, except that here, true means that it has nothing to report currently, but that you should continue to wait. false means that there are no more pending messages, and that your job is done.

It's usually very important that you check the SMTP status codes and feed those back into your mail generation system, so that you can take affirmative action on bounces or record information on successful sends so that you don't continue to send mail to bad mail boxes and that you don't send duplicate mail to a given recipient.

Updated