Yii 1.1: Catching bounce messages (NDR) and piping them to a Yii command

5 followers

Why bother?

In order to avoid bounce messages and unsolicited registrations, most webapps send automatic activation emails upon user registration, and the account remain inactive as long as it is not activated. Better yet, you can periodically purge your data by removing accounts that have not yet been activated.

But in the supposedly small share of webapps that don't ask users to activate their accounts, you can end up with a number of bounce messages and non-accessible active accounts.

Of course, you can prevent at least wrong domain names by using the checkMX property of CEmailValidator in your model rules. But:

  1. it's not perfect
  2. it works only if PHP's checkdnsrr function is enabled on your server (so if you're on a shared host, they may not enable it)
  3. it doesn't check for invalid mailboxes

All that said, we can go on further.

Please read before going any further :)

I wrote this wiki after looking around over the Web several times, and ending up with the solution I'm presenting here, based on the references quoted where applicable.

However, I've stumbled upon two other ways that seem way more advanced while apparently using the same scenario described here:

  1. PHPMailer-BMH seems (is) much more advanced.

  2. There's an implementation by Jacmoe's in his Bugitor project under protected/FetchEmailCommand.php, which uses a library called MIME E-mail message parser (mime_parser) by Manuel Lemos. As Bugitor project seems down as far as I'm editing this wiki, you can find another implementation here: PHP: Parse emails with email piping, Part 1 and Part 2.

Requirements (tested environment)

  • cPanel
  • Access to email filtering
  • Yii 1.1.10+

The idea

It's heavily inspired from this article: Piping Incoming Mail with PHP

The implementation

The User Table

We'll use this sample user table. Adapt to your taste.

CREATE TABLE IF NOT EXISTS `User` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `status` tinyint unsigned NOT NULL,
  `login` varchar NOT NULL,
  `password` tinytext NOT NULL,
  `email` varchar NOT NULL,
  `firstName`varchar NOT NULL,
  `lastName`varchar NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `login` (`login`)
);

The handleBounceMessage console command

The idea is once we get the bounce email from the buffer (see next step), we search it for what we need to take further action.

In this example, I assume that the email is surely present in the bounce message in a string that matches one of two patterns: - The first for wrong domain names - The second for mailbox unavailable at an existing domain name

Of course, it's more straightforward to search for a specific string that is present in your original email (that has bounced). That way, you are sure of a match.

Here's our example code for the console command, which we'll save as HandleBounceMessageCommand.php and put in protected/commands

<?php
class HandleBounceMessageCommand extends CConsoleCommand
{
    public function run($args)
    {
        $socket = fopen ("php://stdin", 'r');
        $email = '';
        // Read e-mail into buffer
        while (!feof($socket)) {
            $email .= fread($socket, 1024);
        }
        // Close socket
        fclose($socket);
 
        // Do a regex match on one of two patterns
        // Adapt to taste
        if (preg_match("/X-Failed-Recipients\:\s*(.*)/", $email, $emailMatch) == 0) {
            preg_match("/RCPT TO:<(.*)>/", $email, $emailMatch);            
        }
 
        // Fetch the user record matching the email address
        $connection = Yii::app()->db;
        $sql = 'SELECT id, login, firstName, lastName FROM User WHERE email = "' . trim($emailMatch[1]) . '"';
        $command = $connection->createCommand($sql);
        $user = $command->queryRow();
 
        // Deactivate the user (by setting status to 1)
        $sql = 'UPDATE User SET status = 1 WHERE id = ' . $user['id'];
        $command = $connection->createCommand($sql);
        $command->execute();
 
        // Send a custom e-mail to admin
        $header = "MIME-Version: 1.0\r\nContent-type: text/plain; charset=UTF-8\r\nFrom: Bounce Watchdog <bounce-watchdog@yourdomain.com>";
        $header .= "\r\nX-Priority: 1 (Highest)\r\nX-MSMail-Priority: High\r\nImportance: High";
        $subject = 'A user has given a wrong email address';
        $body = 'Hi admin,
 
The user ‘' . $user['firstName'] . ' ' . $user['lastName'] . '’ has given a wrong email address in the registration form.
 
The matching login ‘' . $user['login'] . '’ has been automatically deactivated.
 
No further action is necessary.';
 
        mail('admin@yourdomain.com', '=?UTF-8?B?'.base64_encode($subject).'?=', $body . '
 
For your reference, here is the bounce message transcript:
 
' . $email, $header);
    }
}

Piping bounce emails to our console command

In your cPanel GUI, go to the Mail block's User Level Filtering, select click on 'Manage Filters' for the desired account (generally the default one if you use php's mail() function, or a custom one otherwise).

Then click 'Create a New Filter', give it a name, and select 'is an error message' in the Rules parameter. Then in the Actions parameter, select 'Pipe to a program' and put the following in the next input

|/path/to/protected/yiic handleBounceMessage

I'd also recommend --at least in tests-- to add an additional action, 'Redirect to email' (and specify your email address) because you won't find the bounce email anymore in your default mailbox once you used a filter on it.

Before saying it doesn't work :)

Make sure you run correctly console commands as cron jobs. Don't mute their output so you know what may be the error.

A general note is to edit your protected/yiic.php to make sure it has the correct information.

You can also insert some mail() actions for debugging into your console command.

Side note

Unless you find this way of doing things ugly, you can do a lot of variations, process your contact forms for specific words and send back automatic emails, implement auto-responders, and what have you.

Version française

Tutoriel Yii : Intercepter les rapports de non remise d’e-mail (NDR) pour exécuter une commande spécifique

Be the first person to leave a comment

Please to leave your comment.

Write new article