Grow your CSS skills. Land your dream job.

Sending Nice HTML Email with PHP

Published by Chris Coyier

This is going to be a continuation of the Website Change Request Form demo we've been using around here for a while. If you need to catch up, first I talked about it, then I built it, then I screencasted it, then I secured it. Throughout all of this, the end result has been a boring text-only email that gets sent to a single email address.

We're going to improve that output, and make the email into a nicer looking HTML-formatted email.

It's Not Much Different Than Text Email

Sending HTML Email through PHP uses the exact same mail function as text email:

mail($to, $subject, $message, $headers);

The last parameter, the headers, are optional for the function but required for sending HTML email, as this is where we are able to pass along the Content-Type declaration telling email clients to parse the email as HTML.

In fact, the headers area gives us the opportunity to do lots of important email functions. This is where we can set the From: and Reply To: settings if need be, as well as CC and BCC other recipients (Hey, a checkbox for CC'ing yourself would be a cool feature to add!). Here is the code used for the new and improved HTML-Sendin' Website Change Request Form:

$to = '';

$subject = 'Website Change Request';

$headers = "From: " . strip_tags($_POST['req-email']) . "\r\n";
$headers .= "Reply-To: ". strip_tags($_POST['req-email']) . "\r\n";
$headers .= "CC:\r\n";
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: text/html; charset=ISO-8859-1\r\n";

Now We Can Use HTML Tags

The message parameter (a big string we pass to the mail function with the body of our email), can now have HTML tags in it. For example:

$message = '<html><body>';
$message .= '<h1>Hello, World!</h1>';
$message .= '</body></html>';

Here is whole shebang, to create the table of data as shown above:

$message = '<html><body>';
$message .= '<img src="" alt="Website Change Request" />';
$message .= '<table rules="all" style="border-color: #666;" cellpadding="10">';
$message .= "<tr style='background: #eee;'><td><strong>Name:</strong> </td><td>" . strip_tags($_POST['req-name']) . "</td></tr>";
$message .= "<tr><td><strong>Email:</strong> </td><td>" . strip_tags($_POST['req-email']) . "</td></tr>";
$message .= "<tr><td><strong>Type of Change:</strong> </td><td>" . strip_tags($_POST['typeOfChange']) . "</td></tr>";
$message .= "<tr><td><strong>Urgency:</strong> </td><td>" . strip_tags($_POST['urgency']) . "</td></tr>";
$message .= "<tr><td><strong>URL To Change (main):</strong> </td><td>" . $_POST['URL-main'] . "</td></tr>";
$addURLS = $_POST['addURLS'];
if (($addURLS) != '') {
    $message .= "<tr><td><strong>URL To Change (additional):</strong> </td><td>" . strip_tags($addURLS) . "</td></tr>";
$curText = htmlentities($_POST['curText']);           
if (($curText) != '') {
    $message .= "<tr><td><strong>CURRENT Content:</strong> </td><td>" . $curText . "</td></tr>";
$message .= "<tr><td><strong>NEW Content:</strong> </td><td>" . htmlentities($_POST['newText']) . "</td></tr>";
$message .= "</table>";
$message .= "</body></html>";

I think that looks A LOT nicer. And since this email is (theoretically) coming directly to you, isn't it nice to know that it will be formatted to be easy on the eyes?


Some people just absolutely hate HTML email. For one, it can be a security risk as it's possible to run JavaScript in them in some email clients which can be problematic. HTML emails also have a habit of being more easily caught in Spam filters. I think it's less of a concern here as this email is essentially being created BY you FOR you.


I updated the demo and download to use the new HTML email format.

View Demo   Download Files

There is likely to be another one or two demos on this form yet to come. I would at least like to do one on writing the data to a database before sending the email. Email can be such a fragile thing, that saving the data to a DB first is surely a smart move.


  1. With HTML E-Mails I go the extra route of having a template file with the HTML and necessary variable markers. This way my HTML is separate from my PHP, and it makes it that much easier to make simple changes to the e-mails look.

    I cannot tell you how many times I’ve gone to edit HTML within a PHP variable or echo statement only to forget to add a slash before a double-quotation and then get an annoying error out of it all.

  2. Very good article, I was actually just asking someone on HTML e-mail tips last week. thanks!

  3. Nice one Chris. Just one little addition: You really should look into the PHPMailer library: It’s better and easier than the “normal” mail() function.

    You can simply create a new instance:
    $var = new phpMailer();
    Set it to HTML
    Than add the body:
    $var->body(); // Mail stuff here
    And send it!

    (This is just from the top of my head, the function names could be different)

    • I used to use a script similar to Chris’ but now I use zend’s validation class to validate the email and the mail class to send the email, it cares of everything for me, error handling etc. You can even specify a text only version, it’s pretty simple.

      This is how short it is.

      require_once ‘Zend/Mail.php';
      $mail=new Zend_Mail();
      require_once ‘Zend/Validate/EmailAddress.php';
      $validator=new Zend_Validate_EmailAddress();

      // text only version, so strip the tags
      // html version

      $mail->setFrom($_POST[‘email’],’sender name’]);
      $mail->addTo(‘’,”receiver’s name”);
      $mail->setSubject(‘Subject goe heere’);
      // send email
      foreach($validator->getMessages() as $errorMessage)
      echo “$errorMessage”;

      The errors tell the user exactly what is wrong with the email if it’s not valid. You can strip tags you don’t want like javascript tags and/or links with the strip_tags function.

  4. Robert I like your idea but am not sure exactly how to implement, if you have an email template in html how do you include it in the php file and pass variables to it?

    Any links/guides that you could point me towards?

    • The function I use simply loads a file, and then replaces variables in that file (in my case, variables are encased in brackets like {TITLE} or {BODY} ) with the specified results (ex. {TITLE} => $foo or {TITLE} => “My Homepage”.

      Since I tend to output as $variableName = callToMyFunction(“filename.tmpl”); $variableName already contains the entire bit of HTML… it’s that simple.

  5. Kudos for using “its” vs. “it’s” as the focus of your demonstration.
    It is the bane of so many otherwise-smart developers.

  6. Gareth Heyes

    Erm you get a lot of spam right?

    strip_tags is suppose to sanitize the From: address etc right? Wrong! Remember how SMTP works, \n\r can inject headers, you need to filter those. A single dot on a line can end the DATA transaction. You need to filter those.

    I suggest you read the notes on the manual page:-

  7. James

    Awesome article. I’m just about to make a contact form, so this will be very helpful indeed.

  8. I agree with Marco. People should consider using 3-rd party libs such as PHPMailer or Swift, rather then pure mail function from php, especially when you want to use SMTP Auth. But anyway good example for beginners.

  9. TG

    Could of used this like 3 days ago. :)

  10. Great article! I have a question though, how would you set it up to send both HTML email and plain-text in the same message. I know it can be done, just never figured it out.

  11. God article. However, it is prone to email injection

    Having strip_tags here:
    $headers = “From: ” . strip_tags($_POST[‘req-email’])
    does not help much.
    It only strips html tags. not \r\n

    You have to filter req-email for \r\n

    • If you have a bit of replacement code for that area, I’d be grateful, and I’ll update the example and download.

    • Like this:

      $req-email = $_POST[‘req-email’];

      $req-email = preg_replace(‘=((||0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i’, null, $req-email);

      $headers = “From: $req-email\r\n”;
      $headers .= “Reply-To: $req-email\r\n”;

    • preg_replace(‘/[^a-zA-Z0-9\._\-@]/’,”,$_POST[‘req-email’])

      Would replace any characters not allowed in an e-mail address. However, I’d still recommend doing a more comprehensive preg_match to determine if the structure is of a proper email format, too.

    • I couldn’t get either of these to work. I copied and pasted and converted the stupid wordpress curly quotes to straight quotes, but no dice. Maybe something got messed up in the posting of them. I don’t know enough about RegEx to debug myself, but if I learn I’ll attempt to get this integrated.

    • I actually just posted something similar to this, except I covered sending a message with both HTML and plain text versions.

      I also did email address validation to make sure nothing explodes. The regex there works.


      Good article!

    • Excellent.

      For the record, I stole this an implemented it so the From: business should be all nice and sanitized and safe now.

  12. I concur with other comments and recommend phpMailer. MIME messages look nice but what if the recipient doesn’t have MIME, or has chosen to not to view MIME. I believe good practice is to use MIME & Text versions of an email, which phpMailer simplifies greatly.

    However, Microsoft back-tracked on HTML email and now uses Word as a rendering vehicle for email which is far more restrictive. If a large number of email recipients are corporate people you’d be wise to read Campaign Monitor’s musings on the subject.

    Personally, I’ve now dropped back to text-only emails as this was the original intention of the developers. The email is smaller, more likely to render properly and if I *really* need to impress somebody, I just link to a web version of the broadcast. So PHP’s mail function and a regex to replace templated variables works for me every time.

    • Mark Petereit

      Don’t forget context here. In his specific example, he’s generating an HTML email to himself, so there is no reason to code for all receiving platforms.

  13. I’ve been doing something that I think works well to prevent your email client from marking emails from your site as spam.
    Simply set the “From” header to something like ‘’ and then set the “Reply-To” header to the user’s email like normal.
    Now you can just add ‘’ to your contacts and you’ll always get your emails, but can still click reply and it will be sent to the correct email.

  14. Haven’t even read this through yet but I know it’s going to be great. Thanks in advance Chris!

  15. I only used plain version so far, but its time for change ;)

  16. kobi

    I’m sorry, but I don’t really see what’s new here? HTML emails have been around for ever…
    Also, in your mail you have an image from your domain (“<img src=’…'”), you should note and warn people it will be blocked by most modern clients.

  17. It’s rather worrying that people are writing tutorials and yet seem incapable of actually writing decent code. I had thought (or at least hoped) that all programmers with half a brain had a decent understanding of email headers by now… anyway, I suppose the actual tutorial will be quite useful for a lot of beginners – though showing them how to make spam vulnerable forms !== sensible.

  18. Hey Chris,

    Nice article but I think you should also mention that you shouldn’t use divs and floats in email templates and instead use tables and inline styles. I know that’s what you have used but I’m just saying that you should mention to your readers why you are doing this.. which is because all our email clients are still stuck in the stone age :)

  19. Ben

    $message = '';
    $message .= '';
    $message .= '';
    $message .= "Name: " . strip_tags($_POST['req-name']) . "";
    $message .= "Email: " . strip_tags($_POST['req-email']) . "";
    $message .= "Type of Change: " . strip_tags($_POST['typeOfChange']) . "";
    $message .= "Urgency: " . strip_tags($_POST['urgency']) . "";
    $message .= "URL To Change (main): " . $_POST['URL-main'] . "";
    $addURLS = $_POST['addURLS'];
    if (($addURLS) != '') {
    $message .= "URL To Change (additional): " . strip_tags($addURLS) . "";
    $curText = htmlentities($_POST['curText']);
    if (($curText) != '') {
    $message .= "CURRENT Content: " . $curText . "";
    $message .= "NEW Content: " . htmlentities($_POST['newText']) . "";
    $message .= "";
    $message .= "";

    insted of doing this…. how about some heredoc:

    $name = strip_tags($_POST['req-name']) ;
    $email = strip_tags($_POST['req-email']);
    $typeChange = strip_tags($_POST['typeOfChange']);
    $urgency = strip_tags($_POST['urgency']);
    $urlMail = $_POST['URL-main'];
    $addURLS = ($_POST['addURLS'] != NULL) : "URL To Change (additional): " . strip_tags($_POST['addURLS']) . "";
    $curText = (htmlentities($_POST['curText']) == NULL) : "CURRENT Content: " . htmlentities($_POST['curText']) . "" ;
    $newText = htmlentities($_POST['newText']);
    $message = <<<EOD
    Name: $name";
    Email: $email
    Type of Change: $typeChange
    Urgency: $urgency
    URL To Change (main): $urlMail
    NEW Content: $newText";

    Save those bits and it is easier to read and use….

  20. Hey Chris,

    Nice Explanation. I have to test it out, Hope it works fine for me :)


  21. Jordan Walker

    To improve Web Accessibility you may consider adding the accesskey attribute to the label tag.

  22. I use phpmailer for this and works great. I suggest to send email in combined format (plain text/HTML).

    • Flyingrhinocmg
      Permalink to comment#

      How did you get it to work I couldn’t.

      I changed the contact information over and still nothing. Any ideas?

  23. I think there’s a problem with the URL field. I know what an URL is and how to format it, but how many of our clients know that? I think the field is to restrictive as it requires http and www to validate that field.

  24. Wow, I just had problems with HTML mails with PHP and then I got to this site. Nice work. You helped me out with some problems!

  25. Chris
    Permalink to comment#

    To those recommending PHPMailer, have a look at SwiftMailer. It provides the same sort of functionality but in (in my opinion) a much better designed set of classes.

  26. Óscar
    Permalink to comment#

    Leeeeeeeerrrrrrrrrroooooyyyyyyyyyyyy Jeeeeennnnnnnkkkkkiiiiiiiinnnnnssssss

    Nothing to do with the article, just had to say it!!!

  27. Good post, its a very simple way of formatting html emails!

  28. Here is a little trick I learned. If you don’t want to break your HTML up into a bunch of variables you can do this…

    $html = <<< newsletter



    mail($email, $subject, $html, $headers);

  29. zman

    Anyone have a PHP4 version? Mail is only sent successfully when running PHP5.

  30. Permalink to comment#

    How can you add PHP content to the $body then ?

    I will get the title of the message from a form in a different page and send it via POST in the “send_newsletter” page.

    I will then need to have the Title i wrote in the Other page displayed on the E-mails which go out.

    How can that be done?


  31. TeMc
    Permalink to comment#

    Hi Chris,

    Excellent form you’re having here.
    I’ve been following it from the first version and this is quite a cool end result here.

    A small mistake I spotted during customization.

    in “index.php” on line 135 you forgot a period (” . “) at the second line of the $message… this is causing not to be part of the sent message.

    			$message = '';
    			$message = '';
    			$message .= '';

    not that the second $message is also = instead of .=.

    For the rest… awesome form, best thing I’ve seen to date.


  32. Krinkle
    Permalink to comment#

    Hey CSS-Tricks,

    I’ve used this form for a freelancer’s website the other day and found myself customizing the mailoutput and thought I’d share the way I did it.

    In the html e-mail for all 1-line field (like names, addresses, etc.) this:

    $message .= strip_tags(stripslashes(trim($_POST['req-name'])));

    and for the text-area’s:

    $newText2 = strip_tags(stripslashes($_POST['newText']));
    $newText2 = nl2br($newText2);
    $message .= $newText2;

    stripslashes get’s right of annoying slash/’es where quotes are, which, depending on your server and framework settings are being put there automaticly.

    nl2br is handy to maintain linebreaks where entered in the form textarea’s.

    For non text-area’s the trim-function get’s rid of linebreaks and spaces at begin and end.

    My 2c,

  33. siva
    Permalink to comment#

    I am using php pear to send a mail, In this way how can i send a html formatted mail, i tried lot but all are failed. Can you help me?

  34. vikas
    Permalink to comment#

    Hi im new in web development, im looking for a code through which i can send mail to email id, with a specific format.
    when i use html tags into mail text, these tags show as a text in mail.

    help me out and tell me how i send mail using format.

    vikas siwach

  35. Zach
    Permalink to comment#

    Workd perfect!

  36. Arnie
    Permalink to comment#

    Hey Chris,

    Nice Tutorial. Can you confirm whether there is a limitation to no. of characters in mail body or limited no. of rows for table in mail body while sending mails using php mail function?
    If it is, then how can I increase the limit?

  37. Matthew
    Permalink to comment#

    Is it required to table the message? (Just started studying sending email.)

  38. Dimuthu
    Permalink to comment#

    Thank you very much Chris you’re Doing Great job !! :)

  39. ehsan

    Hi Chris,

    I spotted two typo, not very important only visible to recipient developer or
    the middle man between developer and website owner, who receives these requests.

    They aren’t within comment tags, neither variable names.

    Line 28
    IP-Adress: {$ip} \n
    // Address

    Line 173
    $subject = ‘Website Change Reqest';
    // Request

  40. Mister

    is completely missing everywhere at all. Not sure about you guys, but I need it and use it. Plus, my preference is phpmailer as well. There is no need to waste your hours and hours and hours to come up with a function when you can just spend 5 minutes to use a perfect set of classes in phpmailer. Just my 5 cents in edgeways :-) Time becomes a way too precious when you get older, so cannot afford to waste it any more.

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".