Send HTML and Plain Text Versions of Email
Send Both HTML and Plain Text Versions of an Email with PHP
Recently, I've been dealing with sending email from applications an awful lot. At first glance, sending a basic message from PHP is easy:
<?php
mail('test@example.com', 'Subject Line', 'A message!');
?>
Sending complex messages with HTML formatting and/or attachments, however, can be tricky. Today, we're going to cover sending HTML email to a user while still including a plain text version for email clients that don't support HTML.
See the Demo | Download the Source
Part One: Understanding Email Structure
Basic email structure is something like this:
To: someone@example.com
Subject: Test Email
From: Test <testing@example.com>
MIME-Version: 1.0
Content-Type: multipart/alternative;
boundary="==PHP-alt_xd50dc5b6ee7a015fb67e36ba692a93ad961d5f7dx"
X-Nonspam: None
--==PHP-alt_xd50dc5b6ee7a015fb67e36ba692a93ad961d5f7dx
Content-Type: text/plain; charset="iso-8859-1"
Content-Transfer-Encoding: 7bit
This Is a Plain Text Email
This message has no HTML. http://w3schools.com
--==PHP-alt_xd50dc5b6ee7a015fb67e36ba692a93ad961d5f7dx
Content-Type: text/html; charset="iso-8859-1"
Content-Transfer-Encoding: 7bit
<body>
<h1>This Is an HTML Email</h1>
<p>
This message is composed in <a href="http://w3schools.com">HTML</a>.
</p>
</body>
</html>
--==PHP-alt_xd50dc5b6ee7a015fb67e36ba692a93ad961d5f7dx--
This looks a little daunting at first, but if we take it in chunks, it's pretty easy to understand. An email with both a plain text and HTML version will have the following three parts:
- Headers
- Plain text version
- HTML version
That's all there is to it! These three sections are separated by a boundary string, which is essentially a random string to identify each section of the message.
To further our understanding, let's take each section one-by-one.
Headers
Our first section in an email is the message headers.
To: someone@example.com
Subject: Test Email
From: Test <testing@example.com>
MIME-Version: 1.0
Content-Type: multipart/alternative;
boundary="==PHP-alt_xd50dc5b6ee7a015fb67e36ba692a93ad961d5f7dx"
The "To", "Subject", and "From" headers, separated by a line break, are pretty straightforward: they determine who the message is to, what the subject is, and who the message is from.
Next, we pass a MIME-Type, which is an Internet standard that allows us to support several things in email, including multiple parts in message bodies, which is what we need for this exercise.
The "Content-Type" header is set to multipart/alternative, which tells the email client that the message is in multiple pieces: one for plain text, and one for HTML. The "alternative" means that the email client will choose one or the other based on user settings or the client's limitations.
Finally, on the next line, we add our boundary. This is a random string. The only real requirement is that it's something that wouldn't occur in the body of the message.
NOTE: Notice that the boundary string is preceded by a space. This is because it's tied to the Content-Type header.
The Plain Text Version
To note the start of the next section of the email, we add our boundary string, preceded by two hyphens. Then we tell the email client what type of content is being served, what character set to use, and how to encode it. After that, we can just add our message text.
--==PHP-alt_xd50dc5b6ee7a015fb67e36ba692a93ad961d5f7dx
Content-Type: text/plain; charset="iso-8859-1"
Content-Transfer-Encoding: 7bit
This Is a Plain Text Email
This message has no HTML. http://w3schools.com
The HTML Version
For the HTML version of the message, we again start by adding our boundary string to signify that the next section of the message is starting. Then we add the content type, character set, and encoding settings, followed by the HTML.
Finally, to end the message, we add the boundary string one more time.
--==PHP-alt_xd50dc5b6ee7a015fb67e36ba692a93ad961d5f7dx
Content-Type: text/html; charset="iso-8859-1"
Content-Transfer-Encoding: 7bit
<body>
<h1>This Is an HTML Email</h1>
<p>
This message is composed in <a href="http://w3schools.com">HTML</a>.
</p>
</body>
</html>
--==PHP-alt_xd50dc5b6ee7a015fb67e36ba692a93ad961d5f7dx--
Sending Multipart Messages with PHP
Something that really tripped me up when I first started writing scripts to send emails was the way headers seemed to break depending on how line breaks were inserted. I spent a lot of time playing with \r\n and fuming over seemingly nonsensical glitches until I finally tried the heredoc syntax.
To demonstrate one method of sending multipart emails with PHP, let's write a function to accept an email address and send a message to it with multiple parts.
Step 1: Write the Function
First and foremost, we need to write our function. We're going to call it sendEmail(), and it will accept one argument, $to, that will contain the email address.
To start, let's build our subject, headers, and message, then send the message using mail() and check the success, then output a message to the user letting them know what happened.
function sendEmail($email)
{
$to = trim($email);
$subject = "Test Email";
// Generate a random boundary string
$mime_boundary = '_x'.sha1(time()).'x';
// Using the heredoc syntax to declare the headers
$headers = <<<HEADERS
From: Test <test@example.com>
MIME-Version: 1.0
Content-Type: multipart/alternative;
boundary="==PHP-alt$mime_boundary"
HEADERS;
// Use our boundary string to create plain text and HTML versions
$message = <<<MESSAGE
--==PHP-alt$mime_boundary
Content-Type: text/plain; charset="iso-8859-1"
Content-Transfer-Encoding: 7bit
This Is a Plain Text Email
This message has no HTML. http://w3schools.com
--
This message was generated automatically as a demonstration on
www.EnnuiDesign.com
If you did not request this message, please notify answers@ennuidesign.com
--==PHP-alt$mime_boundary
Content-Type: text/html; charset="iso-8859-1"
Content-Transfer-Encoding: 7bit
<html>
<body>
<h1>This Is an HTML Email</h1>
<p>
This message is composed in <a href="http://w3schools.com">HTML</a>.
</p>
<p>
--<br />
This message was generated automatically as a demonstration on
<a href="http://www.ennuidesign.com">www.EnnuiDesign.com</a>
</p>
<p>
If you did not request this message, please notify
<a href="mailto:answers@ennuidesign.com">answers@ennuidesign.com</a>
</p>
</body>
</html>
--==PHP-alt$mime_boundary--
MESSAGE;
// Send the message
if(!mail($to, $subject, $message, $headers))
{
// If the mail function fails, return an error message
return "Something went wrong!";
}
else
{
// Return a success message if nothing went wrong
return "Message sent successfully. Check your email!";
}
}
Step 2: Validate the Email Address
We need to verify that an email address submitted to our function is valid before trying to send an email to it. Otherwise, we could see an internal server error, which is never fun to see.
To validate our email address, we're going to use a regular expression on our input after trimming the extra whitespace using trim(). If the email is valid, we can safely pass it to the rest of the function.
function sendEmail($email)
{
// This pattern will match email addresses
$pattern = "/^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$/i";
if (preg_match($pattern, trim($email)))
{
// Remove leading and trailing whitespace
$to = trim($email);
}
else
{
// Return an error message so the user knows what went wrong
return "The email address you entered was invalid. Please try again!";
}
$subject = "Test Email";
// Generate a random boundary string
$mime_boundary = '_x'.sha1(time()).'x';
// Using the heredoc syntax to declare the headers
$headers = <<<HEADERS
From: Test <test@example.com>
MIME-Version: 1.0
Content-Type: multipart/alternative;
boundary="==PHP-alt$mime_boundary"
HEADERS;
// Use our boundary string to create plain text and HTML versions
$message = <<<MESSAGE
--==PHP-alt$mime_boundary
Content-Type: text/plain; charset="iso-8859-1"
Content-Transfer-Encoding: 7bit
This Is a Plain Text Email
This message has no HTML. http://w3schools.com
--
This message was generated automatically as a demonstration on
www.EnnuiDesign.com
If you did not request this message, please notify answers@ennuidesign.com
--==PHP-alt$mime_boundary
Content-Type: text/html; charset="iso-8859-1"
Content-Transfer-Encoding: 7bit
<html>
<body>
<h1>This Is an HTML Email</h1>
<p>
This message is composed in <a href="http://w3schools.com">HTML</a>.
</p>
<p>
--<br />
This message was generated automatically as a demonstration on
<a href="http://www.ennuidesign.com">www.EnnuiDesign.com</a>
</p>
<p>
If you did not request this message, please notify
<a href="mailto:answers@ennuidesign.com">answers@ennuidesign.com</a>
</p>
</body>
</html>
--==PHP-alt$mime_boundary--
MESSAGE;
// Send the message
if(!mail($to, $subject, $message, $headers))
{
// If the mail function fails, return an error message
return "Something went wrong!";
}
else
{
// Return a success message if nothing went wrong
return "Message sent successfully. Check your email!";
}
}
See the Demo | Download the Source
Summary
This entry was aimed at showing the anatomy of a multipart email message, as well as demonstrating the use of the heredoc syntax to easily put headers in place. Additionally, we used a regular expression to match valid email patterns.
Do you have a better regex? How do you handle headers? Let me know in the comments!
Comments for This Entry
Excellent !
I am looking forward to reading more tutorials.
Really useful thanks a lot!
It seems when you use "heredoc syntax", the text file format (DOS,Unix, UTF8,ANSI...) should match the server type, otherwise you have issues. Could you comment on this?
Thanks
Regards
Sylvin_France
www.jeurencontre.com
@Sylvin_France:
I haven't ever run into issues with this, but I imagine you're correct. I would guess, though, that if the server can interpret the script you've written, it won't have any trouble reading the text out of the heredoc.
If anyone knows otherwise, I'd love to hear about it!
I tested the code on my site but I can not see the message in my mailbox. Not changed any of my code just email address. Please, I can help I appreciate.
@Daniel:
Are you getting any error messages? Have you tried to echo any of the variables throughout execution to make sure they're holding the proper values?
Let me know what you come up with!
This is nice,
I once used a full HTML document as the email body but got some weird results on some clients.
But what if you wanted to use a template file for your email.
Then I think ob_start() would be necessary.
ugh, typed a long comment and didn't get case correct on your "Type" field ($#!+).
same result as daniel. get e-mail but no content when i run the code on my server and send to my yahoo account. from yours i see html content.
however when i send to my gmail account, from your server looks good, but from mine i see plain text only. ??
hope this helps.
(copying this msg to clipboard this time so i don't have to retype if i get it wrong again)
To bitkahuna: I have noticed that it is very important to make your PHP file the same format as your server (UNIX, Windows) in your text editor.
It seems important for the line breaks in the heredoc syntax. (line break is coded differently on UNIX and Windows..)
Hope this is helpful
figured it out. line 82 ends 7bit (with a space on the end).
remove the space and insert a blank line between that line and the and it works.
holy crap that was hard to find.
@bitkahuna:
Good find! I have been running into similar issues and I didn't have a solution. Looks like that trailing space was causing some programs to choke.
Thanks for posting your solution!
Sylvin, thanks for the info, but that's not the issue. The demo on this site has the same problem. If you look at the html source of the e-mail the initial tag is missing because of the typo in the file. By making the fix I suggested you get the opening tag too.
Jason, thanks for the code. Hopefully my 'debugging' has been of some value, lol.
Jason, you're welcome. I think it might have been the missing blank line more than the trailing space that's the problem. I think some mail servers check outbound mail to see if it's well formed (yours obviously doesn't lol) and send 'nothing' for that mime section if it isn't, which explains why the exact same code on your server and mine produced different results (I'd see the plain text version on my Outlook (which must ignore the empty HTML section) and on Yahoo I'd see NOTHING in the body because maybe it still displays the empty HTML section.
Who knows - it's fixed! ONWARD... :)
Oh, and your web site's REALLY slow. Want a better hosting solution? :)
@bitkahuna:
Unfortunately, I can't blame the slowness of my site on my host. This is a REALLY old site that I built from scratch, and it's not very efficient. It's been on my "burn down and rebuild" list for a year now. :)
Thanks again!
i like the look of your site and probably wouldn't be that bad to re-do in latest WordPress.
I admire this article! Great post. I am a beginner in programming and using the resource php form tutorials . But this information is very useful. Thank you.
Many thanks of best tutorial post. Such knowledge is needed to users on the Internet.
Post a Comment
Want to show your face? Get a gravatar!