Sending email from PHP scripts

The PHP builtin function, mail() can be used to send email using either SMPT or using the operating system’s sendmail program. These features can be configured in the php.ini file, but I found I was having problems authenticating to my smtp relay server, and I wanted to minimize the number of SpamAssassin test failures so my messages would go to the recipients’ inboxes instead of their spam folders.

This post is about the gotchas I dealt with, not a tutorial on sending email in general.

First of all, the PHP mail() function does not let you set up authentication with an smtp relay so far as I can tell, so that’s out. And I have nothing to contribute to using sendmail.

The PEAR repository has two packages that can be used: Mail.php lets you pick among mail(), sendmail, and smtp for sending message; Mail/mime.php lets you set up MIME messages. It seems that Mail/mail.php is another “good” package to use because it provides a mail() function to use for actually sending the email that is better than PHP's builtin mail() function. I can’t say anything about that because I simply used it without testing the option of using the builtin mail().

Here is an outline of the code I ended up with, followed by some notes:

$hdrs = array(
'From' => "$sender_name <$sender_email>",
'To' => "$recipient_name <$recipient_email>",
'Reply-to' => "$reply_name <$reply_email>",
'Cc' => "$copy1_name <$copy1_email>, $copy2_name <$copy2_email>",
'Bcc' => "$bcc_name <$bcc_email>",
'Date' => date('r'),
'Subject' => "The subject of this message",
'Message-ID' => '<' . uniqid() . '@example>'
$recipents = "$sender_email, $recipient_email, $reply_email, $copy1_email, $copy2_email, $bcc_email";
$crlf = "\n";
$mime = new Mail_mime(array('eol' => $crlf, 'text_charset' => 'utf-8', 'html_charset' => 'utf-8'));
$body = $mime->get();
$hdrs = $mime->headers($hdrs);
$smtpinfo["host"] = "";
$smtpinfo["port"] = "25";
$smtpinfo["auth"] = true;
$smtpinfo["username"] = $smtp_user;
$smtpinfo["password"] = $smtp_pass;
$smtpinfo["localhost"] = '';
$smtpinfo["persist"] = false;
$mail =& Mail::factory('smtp', $smtpinfo);
$mime_status = $mail->send($recipient_list, $hdrs, $body);
if ( PEAR::isError($mime_status) )
error_exit('Unable to send email: '. $mime_status->getMessage());

  1. Headers and recipients are separate beasts.
    The mail() function takes three arguments: the recipients, the headers, and the body. Just putting addresses in the headers list (To, Cc, Bcc) is not sufficient to get the mail delivered to them, you must also put their addresses in the recipients list. The documentation for mail() calls the first argument “to” rather than “recipients” and it took me a long time to realize that that first argument is not the same as the To header. I found it explained in a note by “arminfrey” on the document page,
  2. Reply-to sets the envelope.
    Using Gmail’s invaluable “Show Original” feature (located in the drop-down menu next to the Reply button at the top of the message), I learned that SpamAssassin will generate a “missing envelope” problem, which can be eliminated by setting a Reply-To header.
    Also, set the Date and Message-ID headers to eliminate additional SpamAssassin test failures.
  3. Set the character set.
    You do this when you create the new Mail_mime object. The default is ISO-8859-1.
  4. There is a debug option.

    $smtpinfo['debug'] = true;

    This is great: it echoes the protocol exchanges between PHP and the smtp host to the web page. (View source if you are using XHTML, which will choke on some of the text.)
    Documentation says this relies on Net/SPTP.php -- I didn’t have to include it in my code, but it was installed.
  5. Gmail is your friend, but sometimes too much so.
    As mentioned above, the “Show Original” feature is especially valuable. But also be aware the Gmail fixes things up for you if you have configured it to let you send mail “as” different people. If you send email to your Gmail account from one of these alternative addresses, they short-circuit the process. In particular, you can’t test the Reply-To header because it will come up as a reply to your own Gmail address rather than the address specified in the header. This is a feature, not a bug. I guess.