Detecting bounces from emails sent in PHP

When working on an automated email system for a client, the core requirement was to send mass emails and detect bounces, opens, clicks and unsubscribes for reporting. The majority of this was trivial, except for detecting bounces, as I had not dealt with this before. I will run through the solution I found.

The requirements

I used PHPMailer on a linux server using cPanel, so most of these instructions are based on this setup. But the core information here can be used on any setup.

First and foremost, it may seem obvious, but every email needs to be logged by the system. This needs to be in place for various other reasons, but it is a requirement to be able to detect and mark an email as bounced. When sending an email, log a unique id, the email address and a timestamp into a database at the very least.

Second, you need to be able to identify an email when it has bounced, this is accomplished by setting the "Sender" header for the email to a unique identifier. eg; 1234@yourdomain.com. The mail daemon of the server you're sending to will then send it's bounce message to that address. If, like me, you're using PHPMailer, setting this header is available by using $mail->Sender.

Note: This header is different to the "from" and "reply-to" addresses and is usually hidden in mail clients. It's specifically used by mail daemons to report bounces.

Finally, you need to be able to pipe all unrouted emails to a php script, which in turn logs the bounce. If you use cPanel, this is very easy. The option is available under advanced in the default email address settings. Set this to a php file that you will be creating, I would recommend putting this outside of your public directory.

The script

Now that you're sending emails with a sender header that identifies the email logged in your database and bounced emails are being piped to a PHP script. You now need to be able to detect the bounce and log it using a PHP script.

First you will need to fire up the PHP interpreter at the top of your script;

#!/usr/bin/php -q

Then you want to read the incoming message using PHP and break it up into lines;


// read from stdin
$fd = fopen("php://stdin", "r");
$email = "";
while (!feof($fd)) {
$email .= fread($fd, 1024);
}
fclose($fd);

// handle email
$lines = explode("\n", $email);

Now we want to run through the headers and find the to address, which should be the same as the sender header we set earlier;


// empty vars
$splittingheaders = true;

for ($i=0; $i < count($lines); $i++){
    if ($splittingheaders) {
        // this is a header
        $headers .= $lines[$i]."\n";
 
        // look out for special headers
        if (preg_match("/^From: (.*)/", $lines[$i], $matches)) {
            $from = $matches[1];
        }
        if (preg_match("/^To: (.*)/", $lines[$i], $matches)) {
            $to = $matches[1];
        }
    }
 
    if (trim($lines[$i])=="") {
        // empty line, header section has ended
        $splittingheaders = false;
    }
}

// extract ID from TO address

$return_path_arr = explode("@", $to);
$id = $return_path_arr[0];

Now we have the FROM address and the ID, we can match them both against our record in the database, mark it as a bounce and voila! You're now detecting and logging bounces.