Articles: 843 | Categories: 148   
   
   
Home Articles Contact Us
 
 
 
 
Advanced email in PHP (0 Comments)
Admin: Posted Date: March 3, 2010

In this article, I'll try to recapture some of that magic for those of you who have never created a Web site that sends email messages. We'll see how the PHP server-side scripting language may be set up to send email.

Advanced email in PHP

In this article, I'll try to recapture some of that magic for those of you who have never created a Web site that sends email messages. We'll see how the PHP server-side scripting language may be set up to send email, and explore how to send complex message types such as HTML email or emails with file attachments.

Note: This article was written back when things like HTML email and file attachments were a lot more difficult to do in PHP than they are today. Before you dive into the from-scratch solutions presented in this article, you might consider investigating PHPMailer, a free library for PHP that provides all of these features with minimal hair-pulling and gnashing of teeth.

PHP Email Setup

Before we can send email with PHP, we need to set it up to do so, just as you need to set up your email program before it can send messages. Configuration for sending email in PHP is done with the php.ini file, so open up your Web server's php.ini in whichever editor you normally use.

If you don't run your own server, but instead have a PHP-equipped Web host, you can safely assume that everything in this section has been done for you, and skip ahead.

In the section entitled [mail function] in the php.ini file, you'll find three settings: SMTP, sendmail_from, and sendmail_path. If your server runs on a Windows machine, you'll want to set the SMTP option to point to your SMTP server (or your ISP's SMTP server, if you're setting up PHP on your home machine). If instead you're setting up PHP on a Linux (or other Unix-based OS) server, you'll want to set the sendmail_path option to point to the sendmail program on your server, passing it the -t option. You can use the SMTP option in Linux instead if you don't have sendmail set up.

In either case, you'll want to set the sendmail_from option to your email address, or whichever address you'd like to appear as the default 'from' address for emails sent from PHP scripts.

Here's how the section might look on a typical Windows server, or on a Linux server without sendmail:

[mail function]
; Setup for Windows systems
SMTP = smtp.my.isp.net
sendmail_from = me@myserver.com

And here's how it might look on a Linux server with sendmail:

[mail function]
; Setup for Linux systems
sendmail_path = /usr/sbin/sendmail -t
sendmail_from = me@myserver.com

With those settings in place, restart your Web server and you're ready to go!

Sending Plain Email

It doesn't come much easier than the procedure to send plain text email in PHP. In fact, you can do it in just one line in a PHP script:

<?php  
mail('recipient@some.net', 'Subject', 'Your message here.');  
?>

The above line would send an email message to  recipient@some.net with 'Subject' as the subject line and 'Your message here.' as the message body. As you can see, PHP's mail function makes sending email exceedingly simple, but there are a few advanced tricks we can use to get more out of this simple function.

First of all, if the mail system you configured in your php.ini file rejects a message you try to send (for example, if the 'to' address is not a valid email address), this function will display an error message in the user's browser. Like most PHP functions, however, error messages may be suppressed by preceding the function name with an @ symbol. Combine this with the fact that the mail function returns either true or false depending on whether the email was accepted by the mail sending system, and you have a formula to send email with appropriate error checking:

<?php  
if (@mail($to, $subject, $message)) {  
 echo('<p>Mail sent successfully.</p>');  
} else {  
 echo('<p>Mail could not be sent.</p>');  
}  
?>

Note that just because an email message could be sent doesn't guarantee it will arrive at its destination. An email address can be valid (i.e. correctly formed) but not actually exist. For instance, you can successfully send a message to nonexistant.user@hotmail.com -- that is, the mail function will return true -- but the message will bounce because no such user exists. PHP provides no built-in means of detecting when this happens.

When you want to send a message to multiple recipients, you can just list their email addresses one after another, separated by commas, in the first parameter. For example:

<?php  
mail('recipient1@some.net, recipient2@some.net',  
    'An email to two people', 'Message goes here.');  
?>

That about covers the basics of the mail function; now let's really get fancy and explore mail headers and what we can do with them!

Mixed Messages

In life, it's rarely good to send mixed messages. At least, that's what my last girlfriend told me! When it comes to email, however, mixed messages offer a whole lot of power. A single email message can contain both text and HTML versions of your message. That makes it viewable in most any email program, and you don't sacrifice the power of HTML formatting for readers that are appropriately equipped.

Be aware that mixed messages have their weaknesses. First of all, since you're sending two versions of your message, the message will typically be a lot larger than it would be if you sent just one format or the other. Also note that old email programs that don't recognize the mixed message format may display both versions of the message as file attachments (one text, the other HTML).

Let's look at a simple example of a mixed email message, and then look at the PHP code to send it:

Date: Mon, 11 Feb 2002 16:08:19 -0500    
To: The Receiver <recipient@some.net>    
From: The Sender <sender@some.net>    
Subject: A simple mixed message    
MIME-Version: 1.0    
Content-Type: multipart/alternative;    
             boundary="==Multipart_Boundary_xc75j85x"    
   
This is a multi-part message in MIME format.    
   
--==Multipart_Boundary_xc75j85x    
Content-Type: text/plain; charset="iso-8859-1"    
Content-Transfer-Encoding: 7bit    
   
This is the text portion of the mixed message.    
   
--==Multipart_Boundary_xc75j85x    
Content-Type: text/html; charset="iso-8859-1"    
Content-Transfer-Encoding: 7bit    
   
<html>    
<body>    
<p>This is the <b>HTML portion</b> of the mixed message.</p>    
</body>    
</html>    
   
--==Multipart_Boundary_xc75j85x--

After the initial, standard headers at the top of the message, we have the MIME-Version: 1.0 header that enables the advanced email features we need. The Content-Type: header is where things start to get funky:

Content-Type: multipart/alternative;    
             boundary="==Multipart_Boundary_xxc75885"

We specify a content type of multipart/alternative, which is the special type that allows us to send a message with two or more alternative versions of the message (from which the recipient's email program will pick the most suitable for display). In addition, we use the Content-Type header to set a boundary string.

To keep the header lines short, this part of the header appears on a second line (as mentioned in a note above, the second line must begin with one or more spaces to indicate that it's a continuation of the previous line).

In this case, I chose "==Multipart_Boundary_xc75j85x" as the boundary string. There is no special significance to this string, other than it is unlikely to appear as part of the message itself. I used characters like equals signs and underscores, and semi-random strings of letters and numbers to help ensure this. We then use this string to divide up our message into parts.

The text "This is a multi-part message in MIME format." is included for the benefit of older mail programs, so that the user has some idea of why the email may not appear quite as expected. With that disclaimer out of the way, we use our boundary to mark the start of the first part of our message:

--==Multipart_Boundary_xc75j85x    
Content-Type: text/plain; charset="iso-8859-1"    
Content-Transfer-Encoding: 7bit    
   
This is the text portion of the mixed message.

Notice that we add two dashes (--) to the beginning of the boundary string when we actually use it.

After the first boundary, we begin the text version of the message. Each part of the message begins with a couple of headers to indicate its content type and encoding. In the case of the text part, the content type is text/plain (with the standard character set, iso-8859-1), and the encoding is 7bit (plain ASCII text). A blank line marks the end of the headers, which are followed by the message body.

The HTML version then follows:

--==Multipart_Boundary_xc75j85x    
Content-Type: text/html; charset="iso-8859-1"    
Content-Transfer-Encoding: 7bit    
   
<html>    
<body>    
<p>This is the <b>HTML portion</b> of the mixed message.</p>    
</body>    
</html>

The headers are almost identical to the text part, but this time we specify text/html as the content type. After the body of the HTML document, all that remains is the closing boundary, to which we add an extra two dashes on the end to mark the end of the message:

--==Multipart_Boundary_xc75j85x--

As you can see, mixed messages may look complicated, but they're actually pretty simple when you take a closer look. The only tricky part from the standpoint of a PHP developer who wants to send a message like this is the task of generating a boundary string.

Here's how I like to do it:

<?php    
$semi_rand = md5(time());    
$mime_boundary = "==Multipart_Boundary_x{$semi_rand}x";    
?>

We take the current Unix timestamp (the number of seconds since January 1, 1970), as given by time(), and use the MD5 algorithm to convert it to a semi-random string. This string is then used to make up part of the boundary string. Feel free to use whatever method you like to generate your boundary strings.

With all this in mind, you should be well-equipped to generate mixed messages in PHP. Since mixed messages are relatively uncommon, I'll leave the actual code for you to write as an exercise. This business of splitting up messages into parts with a boundary string is important, however, when it comes to file attachments -- the subject of our final section.

File Attachments

File attachments work just like mixed email, except that a different content type is used for the message as a whole (multipart/mixed instead of multipart/alternative), and there's a new Content-Disposition header that tells the email client how to handle each part of the message.

Let's write a PHP script that processes a form submission that contains an email message to be sent, possibly with a file attachment, and sends it out. I'll talk you through it line by line so that by the end you'll not only have a useful snippet of PHP code, but also an understanding of how file attachments work. You can download the script (and the form for it) if you want to try it out for yourself.

First, we grab the submitted values and place them in PHP variables. Most people have their servers set up to create global variables for submitted values automatically, but as of PHP 4.1 this is no longer the default, so we do it by hand just in case. Since we want to accept file attachments, it's safe to assume that the form will be submitted with a POST request:

<?php    
// Read POST request params into global vars    
$to      = $_POST['to'];    
$from    = $_POST['from'];    
$subject = $_POST['subject'];    
$message = $_POST['message'];

File uploads in PHP 4.1 are placed in a special $_FILES array, so we fetch the values we need out of it:

// Obtain file upload vars    
$fileatt      = $_FILES['fileatt']['tmp_name'];    
$fileatt_type = $_FILES['fileatt']['type'];    
$fileatt_name = $_FILES['fileatt']['name'];

For the sake of brevity, we'll assume that the required parameters ($to and $from) now have valid values (email addresses) in them. Normally we would check their format with regular expressions.

Next, we use the $from value to begin building the extra headers for the email:

$headers = "From: $from";

Next we check the $fileatt variable, which may or may not contain the path and filename to an uploaded file attachment. We use PHP's is_uploaded_file function to find out:

if (is_uploaded_file($fileatt)) {    
 // Read the file to be attached ('rb' = read binary)    
 $file = fopen($fileatt,'rb');    
 $data = fread($file,filesize($fileatt));    
 fclose($file);

Having read in the data for the file attachment, we need to set up the message headers to send a multipart/mixed message:

 // Generate a boundary string    
 $semi_rand = md5(time());    
 $mime_boundary = "==Multipart_Boundary_x{$semi_rand}x";    
     
 // Add the headers for a file attachment    
 $headers .= "\nMIME-Version: 1.0\n" .    
             "Content-Type: multipart/mixed;\n" .    
             " boundary=\"{$mime_boundary}\"";

Now for the message body itself. This works just as we saw for the text part of a mixed message in the previous section:

 // Add a multipart boundary above the plain message    
 $message = "This is a multi-part message in MIME format.\n\n" .    
            "--{$mime_boundary}\n" .    
            "Content-Type: text/plain; charset=\"iso-8859-1\"\n" .    
            "Content-Transfer-Encoding: 7bit\n\n" .    
            $message . "\n\n";

Now, to allow for binary file types, we need to use Base64 encoding to convert the (possibly binary) file attachment data to a text-only format suitable for sending by email. All email programs in popular use support Base64 encoding of file attachments, so this is the best way to go. Fortunately, PHP provides a function for Base64 encoding:

 // Base64 encode the file data    
 $data = chunk_split(base64_encode($data));

We now have everything we need to write the portion of the message that contains the file attachment. Here's the code:

 // Add file attachment to the message    
 $message .= "--{$mime_boundary}\n" .    
             "Content-Type: {$fileatt_type};\n" .    
             " name=\"{$fileatt_name}\"\n" .    
             "Content-Disposition: attachment;\n" .    
             " filename=\"{$fileatt_name}\"\n" .    
             "Content-Transfer-Encoding: base64\n\n" .    
             $data . "\n\n" .    
             "--{$mime_boundary}--\n";    
}

That completes the modifications necessary to accommodate a file attachment. We can now send the message with a quick call to mail:

// Send the message    
$ok = @mail($to, $subject, $message, $headers);    
if ($ok) {    
 echo "<p>Mail sent! Yay PHP!</p>";    
} else {    
 echo "<p>Mail could not be sent. Sorry!</p>";    
}    
?>

And that's how we send emails with file attachments in PHP!

Summary

In this article, you probably learned a lot more about email than you ever wanted to know. That intimate knowledge about what makes email tick allowed us to do some pretty special things with PHP's deceptively simple mail function.

  • we learned how to manipulate email headers to play with the recipients of an email
  • we saw the headers that permit you to send HTML email
  • we combined the HTML and text versions of an email into a single, mixed message

and finally...

  • we brought it all together with a couple of new tricks to send email messages with file attachments.

If you're in the mood for a challenge, try expanding the file attachment script in this article to allow for multiple file attachments, or to allow for an HTML message. If you're an object oriented PHP type, try developing a class that encapsulates the functionality we explored in this article (there are a number of such scripts available on the Internet if you're after something ready-made).

If you're really interested, check out the RFC for MIME extensions (RFC 2045) to discover everything that email is capable of, then go wild!

 

 

 

 

 

 

 

 

 

 

 

 
 
Add a Comment:
 
(You must be signed in to comment on an article. Not a member? Click here to register)
   
Title:

Comments: