Send HTML and Plain Text Versions of Email

Who has two thumbs and is recycling artwork again? This guy.

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:

  1. Headers
  2. Plain text version
  3. 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!

Posted May 26, 2009 by Jason Lengstorf.
This entry is filed under instant tip tuesday, email, php, and regular expressions.

Want more content like this? Subscribe for FREE!

Comments for This Entry

Gravatarshin07:49AM on June 10, 2009

Excellent !
I am looking forward to reading more tutorials.

GravatarSylvin_France03:03PM on June 11, 2009

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

GravatarJason Lengstorf10:21AM on June 12, 2009

@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!

GravatarDaniel05:20PM on July 09, 2009

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.

GravatarJason Lengstorf11:42AM on July 10, 2009

@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!

Gravatar50kobo.com10:17AM on July 15, 2009

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.

Gravatarbitkahuna08:54PM on September 06, 2009

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)

GravatarSylvin02:31AM on September 07, 2009

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

Gravatarbitkahuna03:17PM on September 08, 2009

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.

GravatarJason Lengstorf03:21PM on September 08, 2009

@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!

Gravatarbitkahuna04:01PM on September 08, 2009

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.

Gravatarbitkahuna04:07PM on September 08, 2009

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? :)

GravatarJason Lengstorf04:10PM on September 08, 2009

@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!

Gravatarbitkahuna07:30PM on September 08, 2009

i like the look of your site and probably wouldn't be that bad to re-do in latest WordPress.

GravatarPHP Form12:26AM on February 27, 2010

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.

GravatarForm Creator12:54PM on March 09, 2010

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!

ALLOWED TAGS: <tt><strong><em>