Sending Emails with PHP

Tweet

We know why you’re here… you want to write a script to send emails to your friends with funny return addresses. I’m here to tell you you’re better than that. There’s so much more you could be doing with your life! But, what other reasons could you possibly have?

Well, maybe you have a cron job that you want to inform you of issues, have a user-initiated script you’d like to be notified when run, have a “Contact Us” form that forwards messages to you, want to flex your PHP muscles and write your own web-based mail client, need to set up a confirmation-by-email script – there are plenty of other reasons to send email with PHP. Plus, it’s so easy!

In most cases your installation of PHP will be capable of sending emails. If you are using a shared host, or if you installed PHP using a package management system like apt-get, more than likely you’re all set. You’ll really only need to worry about extra configuration if you’re compiling PHP from source or if you’re running it on Windows. In either case, there are plenty of resources available online to help you out. Because that’s all beyond the scope of this article, I’ll assume you’re set. If not, Google will be your friend.

The Super-Basic Example

To send just a really simple email, the code is:

<?php
mail($address, $subject, $message);

Really, that’s all there is to sending basic plain-text emails (if it doesn’t work for you, again check with Google to see how to configure PHP correctly).

Now let’s see how this would look in a script. Let’s say, for example, you want the script to email you every time a query fails:

<?php
$query = "SELECT left_arm AS arm_in, right_leg AS leg_in, front_head AS head_in FROM hokey_pokey WHERE its_about = 'all'";
try {
    $result = $db->query($query);
    // ...
}
catch (PDOException $e) {
    mail("bad_things@my_database.com", "Error in " . $_SERVER["SCRIPT_NAME"], $e->getMessage());
}

If there’s some unforeseen error that happens with the execution of your query then you’ll get an email informing you what script had an error and what that error was.

A Full-Blown HTML Mail Example

Now, let’s check out a full-blown multipart mail() example that has an HTML body with a plain text alternative and a file attachment:

<?php
$emailList = array("first_sucker@wazoo.com",
                   "nextSucker@hemail.com",
                   "othersucker32@clotmail.com",
                   "sckrfnl@mayohell.com");

$headers = "From: "Fluffy Mumsy" <sincere@dumsing.com>rn" .
   "Reply-To: weregonnaberich@shhhsecret.comrn" .
   "MIME-Version: 1.0rn" .
   "Content-Type: multipart/mixed; boundary="YaGottaKeepEmSeparated"rn";
$subject = "Please donate all your moneys to us";
$goodAttachment = chunk_split(base64_encode(file_get_contents( "novyrus.zip")));
$body = "--YaGottaKeepEmSeparatedrn" .
    "Content-Type: multipart/alternative; boundary="EachEmailAlternative"rn" .
     "--EachEmailAlternativern" .
      "Content-Type: text/plain; charset="iso-8859-1"rn" .
      "Content-Transfer-Encoding: 7bitrn" .
      "You have cheap text email you have no money. Please ignore.rn" .
     "--EachEmailAlternativern" .
      "Content-Type: text/html; charset="iso-8859-1"rn" .
       "<html>
 <head>
  <title>We need money to give you</title>
 </head>
 <body>
  <p>We found some <span style='color:green'>money</span>. And we need you give us money to give some you. We is good people. You can trust. Please install contact information attachment</p>
 </body>
</html>rn" .
    "--YaGottaKeepEmSeparatedrn" .
     "Content-Type: application/zip; name="novyrus.zip"rn" .
     "Content-Transfer-Encoding: base64rn" .
     "Content-Disposition: attachmentrn" .
     $goodAttachment . "rn" .
    "--YaGottaKeepEmSeparated--";

foreach ($emailList AS $addy) {
   $success = mail($addy, $subject, $body, $headers);
   if (!$success) {
       echo "Mail to " . $addy . " is fail.";
   }
}

Certain aspects of more typical email scripts had been simplified to make the general concept easier to grasp, though I will touch on those in this breakdown.

First, the $emailList array is populated with some email addresses I’d like to share my message with. The array is iterated through at the end of the script and each address will be sent a copy of my email.

Next, the $headers string is built with various mail headers. Each header in the string is separated with a CRLF (rn) per RFC 2822, the standard that defines the format of email messages.

"From: "Fluffy Mumsy" <sincere@dumsing.com>rn"
The From header specifies the email address the recipient will see the message as having come from.

"Reply-To: weregonnaberich@shhhsecret.comrn"
The Reply-To header is the email address to which an email reply should be sent. By making it different than the “From:” header, the chances of this email being identified as spam increases (though if this were the only thing that caused an email client to raise a red flag this message is spam then it would probably get through).

"MIME-Version: 1.0rn"
The MIME-Version header tells the server to expect Multipurpose Internet Mail Extensions in the body, which allows you to have a more advanced email than simple text.

"Content-Type: multipart/mixed; boundary="YaGottaKeepEmSeparated"rn"
The “Content-Type” header actually does two things: it indicates that there will be multiple parts in the body of various kinds, and it specifies what string is used to divide each part. This boundary string needs cannot appear anywhere else in the email or the mail client will not be able to parse the message correctly. You could use “12” as your boundary for example, though chances are that it will show up elsewhere in the message. I chose “YaGottaKeepEmSeparated”. Most people assign a randomly generated hash to serve as the boundary, like $boundary = md5(time()), since the chance of collision is very low.

The contents of novyrus.zip, which here happens to be located in the same directory as the script, is base64-encoded and broken into “chunks” for easier digestion by the mail client. The result is stored in $goodAttachment which will make an appearance later.

Finally, the body of the email message is composed…

"--YaGottaKeepEmSeparatedrn"
This is the first instance of using the boundary defined earlier and says to the mail client, “hey, here’s the start of the first section of the email message.”, and it always starts with the double-dash in front of your chosen boundary string.

"Content-Type: multipart/alternative; boundary="EachEmailAlternative"rn"
Besides “multipart/mixed” given in the email’s headers, you can also use a Content-Type header with “multipart/alternative” within the body and with a different boundary specific to this breakdown to provide alternate formats for the message.

"--EachEmailAlternativern"
This is the first instance of the nested boundary and starts the first alternate version of the message.

"Content-Type: text/plain; charset="iso-8859-1"rn"
This Content-Type header tells the mail client this alternative is plain text. If the client isn’t capable of displaying more complex formats, such as HTML, then it will use this version of the message.

"Content-Transfer-Encoding: 7bitrn"
The Content-Transfer-Encoding header specifies the encoding scheme used in the message. For historical reasons, “7bit” is the default value and so this could be omitted. I included it so just so you’ll be aware of it.

"You have cheap text email you have no money. Please ignore.rn"
This is the text-only version of the message which people using non-HTML-capable readers will see.

"--EachEmailAlternativern"
The end of the first alternative has been reached, and you’re ready to start the next alternate version.

"Content-Type: text/html; charset="iso-8859-1"rn"
This Content-Type header informs the client this version is formatted as HTML, and the set of characters that is used.

"<html> ... </html>rn"
Notice that this version, besides the inclusion of HTML tags, has content that is considerably different than the plain text version. Some spam filters may view this as one more reason to block me message from getting to the inbox.

"--YaGottaKeepEmSeparatedrn"
This is the multipart/mixed boundary, indicating that you’ve reached the end of the message body section with all its alternatives.

"Content-Type: application/zip; name="novyrus.zip"rn"
The Content-Type header signals the next part of the email is an attachment (the novyrus.zip file), and that it is ZIP file.

"Content-Transfer-Encoding: base64rn"
7bit encoding restricts characters to seven bits and might not be able to faithfully represent all the necessary binary characters for the ZIP file, which was why the file was base64-encoded and chunked. The Content-Transfer-Encoding header here lets the client know how to decode the attachment file.

"Content-Disposition: attachmentrn"
The Content-Disposition header details how content should be presented; there are two possible values: attachment and inline. While it would hardly make sense to display a ZIP file inline element in a message, it comes in handy to embed images.

$goodAttachment . "rn"
The contents of the attached file is simply dumped into the mix.

"--YaGottaKeepEmSeparated--"
This is the final boundary declaring that nothing more shall follow by ending with one last set of double-dashes.

Summary

There you have it! You’ve seen how to send super-basic text email message, and full-fledge HTML emails with attachments. Simple emails are just a matter of calling the mail() function. For HTML messages, you need to break your email into segments using the MIME standards, divided by a boundary of your choosing. Then, you define what the content is, how it’s encoded, possibly the disposition, and then the content itself. Depending to whom you plan on sending emails, you will want to be conscientious about factors that may cause your message to be more likely flagged as spam, just in case you want to actually want to send something serious.

Image via Photosani / Shutterstock

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://www.georgedina.info George Dina

    A smtp authentication tutorial should a good part two of this article.
    Great and job on this one!

    • http://fawadafr.com Fawad Rashidi

      Thank you Jason. Great Article. I agree with George in regards to SMTP authentication tutorial. I look forward to your future posts.

  • Bigorangemachine

    Good article.
    There are so many issues with shared servers and email now.
    Somethings one might need to address is that some spam servers check that the ‘from’ is the same domain as the server is sending, and the ‘Reply-To’ can be whatever you wish.

    Also, some servers I’ve seen can’t attached files correctly. They’ll corrupt a zip file and you can’t extract the file (character handling issues).

    I have built a PHP class that can switch between mail() and SMTP (PHPMailer) easily due to the complications on various servers. SMTP has less issue with ‘Level 1 spam’ (the receiving/sending server side filter that most people can’t control) but not every server supports what you need.

    If your script isn’t sending email, it can be pretty complex of an issue.

    Writing an SMTP tutorial will be a bit much. If you want to use SMTP use PHPMailer :)

  • http://individualweb.wordpress.com/ John Larsen

    I’ve worked a bit with emails in PHP lately and discovered some additional good practices:

    – You should use chunk_split(base64_encode($html)); on the HTML part of the email including base64 trasfer ecnoding, because without it, the email will add a empty space for every 998 character. Specially bad when this appear inside a link or in some other way destroying the HTML.

    – You should also use the constant PHP_EOL instead of “rn” because different servers interperet newlines differentely. If you work locally on a windows machine and “rn” seems to work, it will fail on a linux server that goes well with “n” or a mac server that favors “nr”

    • http://zaemis.blogspot.com Timothy Boronczyk

      Thanks for sharing your experiences, John. Using chunk_split() sounds like a great idea.

      In this case, though, PHP_EOL wouldn’t work. The RFC that defines the format of email messages explicitly states you should use rn. So regardless if you’re on Windows, Mac, Linux, etc. you’d want to avoid PHP_EOL for this.

      • http://individualweb.wordpress.com/ John

        RFC2822 say that emails has to be an CRLF, the problem is that servers are not always consistent in what they do when they se a “rn”.

        PHP docs explains it like this:
        “If messages are not received, try using a LF (n) only. Some poor quality Unix mail transfer agents replace LF by CRLF automatically (which leads to doubling CR if CRLF is used). This should be a last resort, as it does not comply with » RFC 2822.” – http://php.net/manual/en/function.mail.php

        The only thing PHP_EOL does is to provide the correct “End of line” symbol for the platform used.

        Here is list of what newline to use on different servers – from http://en.wikipedia.org/wiki/Newline:
        Unix based OS: n
        Mac OS: r
        Windows: rn

        So, to ensure that the right CRLF is used, my personal opinion is that it is safe to use the PHP_EOL. If you check the source of PHP_EOL – https://github.com/php/php-src/blob/master/main/php.h – you see that what it does.

        PHP docs said that it “should be a last resort” to use “n”, because RFC say to use “rn”, but in PHP I can ensure that this does not work all the time. It’s totally right to use “rn” but when it fails, try PHP_EOL ans the email may work. I have often encountered problems with juggling between “rn” and “n” and PHP_EOL has always fixed the problem for me.

        I was probably wrong when I said that ‘You should also use the constant PHP_EOL instead of “rn”‘ – and maybe the right thing to say is that if “rn” fail you should try PHP_EOL (or “n”)?

    • http://www.mikehealy.com.au Mike

      Email uses rn regardless of the platform. This article links to the RFC2822 spec which says as much.

  • http://php-and-symfony.matthiasnoback.nl Matthias

    I recommend using the Swift Mailer (http://swiftmailer.org/) for this kind of advanced mail sending; the work has been done many times before and the Swift Mailer contains everything you need so you don’t have to rethink the mailer part of each project you start. Of course, it’s always good to know the “mailing internals”, especially when you expect that debugging will be necessary.

  • http://keloran.com Keloran

    If you wish to use imap_mail in order to send the email, and change the sender (as long as its a valid account on your mailserver)

    so instead of its return-path being [apache|root|nobody]@domain.com

    it would be @domain.com, i submitted a patch to fix the rpath

    https://bugs.php.net/bug.php?id=30688

  • john

    If you are sending a large number of emails. What would be best practise to ensure they are sent? I am thinking of using SLEEP() in between each email to slow down the send rate. Also, i have read of using CRON to help stagger. Should I update a mySQL table after each email is sent, so if the php script crashes, it can carry on from where it crashed. Any ideas on this? I am struggling to plan the mailout..

  • http://www.saurabhksingh.com Saurabh Kumar Singh

    The best option is MIME email if you can not use SMTP authentication. Because most of the shared server like godaddy not supported SMTP mails through PHP.

  • grizzley

    I like phpMailer with SMTP but some servers, such as the one I use have a limit on the number of emails you can send. I wrote sleep() into the script but you also need to ensure that the page doesn’t time out before completion by using set_time_limit() and setting it to something like 300 seconds.

  • http://webcitymaster.com Fred Dearman

    Jason, thanks for taking the time to give this outstandingly piece of code and the great explanation of it. I really appreciate the time you spent particularly in creating meaningful names for your variables.
    Fredz

  • http://www.xpoze.org Marian

    Very nice beginner tutorial, but I think you should have an advanced tutorial as well talking more about Headers and how to send an email correctly so it does not get flagged as Spam as this is very big issues for a lot of websites on bad shared hosting that have improperly configured servers.

  • http://WebsiteURL Hari

    very good sending email tutorial!

  • http://fiveholiday55.blogspot.com Helen Neely

    Just started working with PHP, so this will surely coming in very handy – especially when there are exceptions in the code.
    Thanks for the nice tutorial.

  • binoy

    Nice article.. It should be RFC not RCF

    • http://zaemis.blogspot.com Timothy Boronczyk

      Whoops! Thanks, binoy… I’ve fixed it!

  • http://www.whitedoggreenfrog.com Brian Coogan

    These days if you’re sending email from PHP you’d generally use PHPmailer or swiftmailer, both mentioned above, as they handle the various fiddly complexities around MIME and attachments effortlessly, and can send email in a variety of ways.

    Having said that, if your server won’t allow SMTP to localhost to send email, that’s pretty restrictive.