Wednesday, November 04, 2009

Gotcha Of The Day: PHP Mail_Queue Generates Broken E-mails

Earlier today I was making use of PHP's Mail_Queue package. I've always found it to be a simple and reliable way to queue up and send batches of messages. But today, for some reason, the HTML (MIME) messages I was sending were coming out corrupted.

What was especially funky was that when I ran a test and queued up a message to just myself, it worked perfectly. But, if I also sent it to the customer, I'd get a fine version, but his version would be corrupt.

At first I thought it was an issue with the customer's mail system, but after further testing I realized that if I queue'd up multiple messages (using mailinator, naturally), the majority of them would be corrupted.

The corruption, it turned out was due to the fact that an extra blank line was being inserted between the Date header and the Content-Type header. The result: was that the Content-Type was being ignored. Here's effectively what the Mail_Queue package was generating in terms of headers:

Received: from localhost (localhost [127.0.0.1])
 by XXX
 for ; Wed,  4 Nov 2009 08:10:46 -0500 (EST)
MIME-Version: 1.0
From: XXXXX
Subject: YYYYYY
To: baz@mailinator.com
Date: Wed,  4 Nov 2009 08:10:46 -0500 (EST)
     «I'm a blank line that's hosing everything up
Content-Type: multipart/alternative;
 boundary="=_046216cf95b659c74f70aa33d3b4b6a2"

--=_046216cf95b659c74f70aa33d3b4b6a2
Content-Transfer-Encoding: 7bit
Content-Type: text/plain; charset="ISO-8859-1"

The question, of course, is how would that blank line get in there?

I Googled around, and didn't find anyone with a similar issue. I also poked around the source code for Mail_Queue (one of the joys of having an open source library!) and found nothing. I did notice that there's a debug option you can set.

I sent ahead and set it, and when sending mail, the screen filled up with the SMTP traffic.

After about 10 seconds of looking at the output, it hit me what was going on.

The blank being injected into the header stream wasn't from the Date: header - it was from the To: header. Looking back at my code, I had taken the list of e-mails from an HTML textarea and split them like so:

  $addrs = preg_split('/[\n\t;, ]/', $data['emails'])

The problem is that every address except for one, still had a \r associated with it. So, depending on the order I put the e-mails in the test message, one user would get a valid e-mail, and the rest would get corrupt ones.

I changed:

    $mime =& new Mail_mime();
    $mime->setTXTBody(strip_tags($message_html));
    $mime->setHTMLBody($message_html);
    $body = $mime->get();
    $hdrs = $mime->headers(array('From' => $from,
                                 'Subject' => $subj,
                                 'To' => $to));

To:

    $mime =& new Mail_mime();
    $mime->setTXTBody(strip_tags($message_html));
    $mime->setHTMLBody($message_html);
    $body = $mime->get();
    $hdrs = $mime->headers(array('From' => trim($from),
                                 'Subject' => trim($subj),
                                 'To' => trim($to)));

And the nasty bug was gone.

Man, that was a fun one to debug.

No comments:

Post a Comment