views:

940

answers:

4

How can I send an email with attachments from a PHP form?

+6  A: 

The HTML

There are really just two requirements in your HTML for sending file attachments.

  • Your form needs to have this attribute: enctype="multipart/form-data"
  • You need at least one field like <input type="file" name="examplefile">. This allows the user to browse for a file to attach.

If you have both of these, the browser will upload any attached files along with the form submission.

Side note: These are saved as temporary files on the server. For this example, we'll take their data and email it, but if you move the temporary files to a permanent location, you've just created a file upload form.

The MIME Email Format

This tutorial is great for understanding how to build up a MIME email (which can contain HTML content, a plain text version, attachments, etc) in PHP. I used it as a starting point.

Basically, you do three things:

  • Declare up front that this email will have multiple types of content in it
  • Declare a string of text that you will use to separate the different sections
  • Define each section and stick in the appropriate content. In the case of file attachments, you have to specify the type and encode them in ASCII.
    • Each section will have a content-type such as image/jpg or application/pdf. More info can be found here. (My example script pulls this information from each file using built-in PHP functions.)

The PHP

Once the form is submitted, any files uploaded by the browser (see the HTML section) will be available via the $_FILES variable, which contains 'An associative array of items uploaded to the current script via the HTTP POST method.'

The documentation on $_FILES is lousy, but after an upload, you can run print_r($_FILES) to see how it works. It will output something like this:

Array ( [examplefile] => Array ( [name] => your_filename.txt 
[type] => text/plain [tmp_name] => 
C:\path\to\tmp\file\something.tmp [error] => 0 [size] => 200 ) ) 

You can then get the data in the associated temp file by using file_get_contents($_FILES['examplefile']['tmp_name']).

Side note on file size limits

php.ini has some settings that limit attachment size. See this discussion for more info.

An Example PHP Function

I created the following function, which can be included on the page and used to gather up any file attachments submitted with a form. Feel free to use it and/or adapt it for your needs.

The total attachment limit is arbitrary, but large amounts may bog down the mail() script or be rejected by a sending or receiving email server. Do your own testing.

(Note: The mail() function in PHP depends on information in php.ini to know how to send your email.)

function sendWithAttachments($to, $subject, $htmlMessage){
  $maxTotalAttachments=2097152; //Maximum of 2 MB total attachments, in bytes
  $boundary_text = "anyRandomStringOfCharactersThatIsUnlikelyToAppearInEmail";
  $boundary = "--".$boundary_text."\r\n";
  $boundary_last = "--".$boundary_text."--\r\n";

  //Build up the list of attachments, 
  //getting a total size and adding boundaries as needed
  $emailAttachments = "";
  $totalAttachmentSize = 0;
  foreach ($_FILES as $file) {
    //In case some file inputs are left blank - ignore them
    if ($file['error'] == 0 && $file['size'] > 0){
      $fileContents = file_get_contents($file['tmp_name']);
      $totalAttachmentSize += $file['size']; //size in bytes
      $emailAttachments .= "Content-Type: " 
      .$file['type'] . "; name=\"" . basename($file['name']) . "\"\r\n"
      ."Content-Transfer-Encoding: base64\r\n"
      ."Content-disposition: attachment; filename=\"" 
      .basename($file['name']) . "\"\r\n"
      ."\r\n"
      //Convert the file's binary info into ASCII characters
      .chunk_split(base64_encode($fileContents))
      .$boundary;
    }
  }
  //Now all the attachment data is ready to insert into the email body.
  //If the file was too big for PHP, it may show as having 0 size
  if ($totalAttachmentSize == 0) {
    echo "Message not sent. Either no file was attached, or it was bigger than PHP is configured to accept.";
   }
  //Now make sure it doesn't exceed this function's specified limit:
  else if ($totalAttachmentSize>$maxTotalAttachments) {
    echo "Message not sent. Total attachments can't exceed " .  $maxTotalAttachments . " bytes.";
  }
  //Everything is OK - let's build up the email
  else {    
    $headers =  "From: [email protected]\r\n";
    $headers .=     "MIME-Version: 1.0\r\n"
    ."Content-Type: multipart/mixed; boundary=\"$boundary_text\"" . "\r\n";  
    $body .="If you can see this, your email client "
    ."doesn't accept MIME types!\r\n"
    .$boundary;

    //Insert the attachment information we built up above.
    //Each of those attachments ends in a regular boundary string    
    $body .= $emailAttachments;

    $body .= "Content-Type: text/html; charset=\"iso-8859-1\"\r\n"
    ."Content-Transfer-Encoding: 7bit\r\n\r\n"
    //Inert the HTML message body you passed into this function
    .$htmlMessage . "\r\n"
    //This section ends in a terminating boundary string - meaning
    //"that was the last section, we're done"
    .$boundary_last;

    if(mail($to, $subject, $body, $headers))
    {
      echo "<h2>Thanks!</h2>Form submitted to " . $to . "<br />";
    } else {
      echo 'Error - mail not sent.';
    }
  }    
}

If you want to see what's going on here, comment out the call to mail() and have it echo the output to your screen instead.

Nathan Long
I have just finished working all this out and thought I'd share with everyone else. If anyone can suggest improvements, please do! The error handling in particular is probably not great.
Nathan Long
+1  A: 

nice tutorial here

The code

<?php
//define the receiver of the email
$to = '[email protected]';
//define the subject of the email
$subject = 'Test email with attachment';
//create a boundary string. It must be unique
//so we use the MD5 algorithm to generate a random hash
$random_hash = md5(date('r', time()));
//define the headers we want passed. Note that they are separated with \r\n
$headers = "From: [email protected]\r\nReply-To: [email protected]";
//add boundary string and mime type specification
$headers .= "\r\nContent-Type: multipart/mixed; boundary=\"PHP-mixed-".$random_hash."\"";
//read the atachment file contents into a string,
//encode it with MIME base64,
//and split it into smaller chunks
$attachment = chunk_split(base64_encode(file_get_contents('attachment.zip')));
//define the body of the message.
ob_start(); //Turn on output buffering
?>
--PHP-mixed-<?php echo $random_hash; ?> 
Content-Type: multipart/alternative; boundary="PHP-alt-<?php echo $random_hash; ?>"

--PHP-alt-<?php echo $random_hash; ?> 
Content-Type: text/plain; charset="iso-8859-1"
Content-Transfer-Encoding: 7bit

Hello World!!!
This is simple text email message.

--PHP-alt-<?php echo $random_hash; ?> 
Content-Type: text/html; charset="iso-8859-1"
Content-Transfer-Encoding: 7bit

<h2>Hello World!</h2>
<p>This is something with <b>HTML</b> formatting.</p>

--PHP-alt-<?php echo $random_hash; ?>--

--PHP-mixed-<?php echo $random_hash; ?> 
Content-Type: application/zip; name="attachment.zip" 
Content-Transfer-Encoding: base64 
Content-Disposition: attachment 

<?php echo $attachment; ?>
--PHP-mixed-<?php echo $random_hash; ?>--

<?php
//copy current buffer contents into $message variable and delete current output buffer
$message = ob_get_clean();
//send the email
$mail_sent = @mail( $to, $subject, $message, $headers );
//if the message is sent successfully print "Mail sent". Otherwise print "Mail failed"
echo $mail_sent ? "Mail sent" : "Mail failed";
?>
Phill Pafford
+1  A: 

For sending out the actual e-mail I would recommend using the PHPMailer library, it makes everything much easier.

Anti Veeranna
+1  A: 

You might want to check out SwiftMailer. It has a nice tutorial on this.

Zed