Send PHP HTML mail with attachments
Asked Answered
P

4

12

I got a problem: Until today, I sent HTML mails with PHP using a header which contains

Content-type: text/html;

Now, I added functionality to add attachments. For this, I had to change this line to

Content-Type: multipart/mixed;

Now, with multipart/mixed, the rest of the mail, so the normal text, gets shown just as text/plain. How can I realize that attachments work and the mailtext is still HTML?

Peterman answered 1/3, 2012 at 16:4 Comment(5)
each part of your multipart mail has its own content-type look at the source of a proper multipart mail.Messalina
possible duplicate of php send email with attachment. There are quite a few others on here that deal with email and attachments, take a look through some of the Related.Hade
use MIME header in your mail templatePanoply
php.net/manual/en/function.mail.php where check the post of ANDA Anda 05-Sep-2011 11:57 Send Multi attachment emailPanoply
Why re-invent the wheel? PHP has a great PEAR extension for sending mail with attachments.Irradiate
E
12

To send an email with attachment we need to use the multipart/mixed MIME type that specifies that mixed types will be included in the email. Moreover, we want to use multipart/alternative MIME type to send both plain-text and HTML version of the email.Have a look at the example:

<?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"; 
?>

As you can see, sending an email with attachment is easy to accomplish. In the preceding example we have multipart/mixed MIME type, and inside it we have multipart/alternative MIME type that specifies two versions of the email. To include an attachment to our message, we read the data from the specified file into a string, encode it with base64, split it in smaller chunks to make sure that it matches the MIME specifications and then include it as an attachment.

Emission answered 1/3, 2012 at 16:23 Comment(2)
It's proper to include source when you copy paste from some other source. I don't even know if this is THE source, but they have comments dating back to 2007: webcheatsheet.com/php/send_email_text_html_attachment.phpBergerac
Yes, Sanjay blatantly cut and pasted text from another website and did not attribute it.Shayshaya
F
27

I tried Answer 1 for a couple of hours with no luck. I found a solution here: http://www.finalwebsites.com/forums/topic/php-e-mail-attachment-script

Works like a charm- less than 5 min! You might want to change (like I did), the first content type from text/plain to text/html.

Here is my slightly modified version to handle multiple attachments:

function mail_attachment($files, $path, $mailto, $from_mail, $from_name, $replyto, $subject, $message) {
$uid = md5(uniqid(time()));

$header = "From: ".$from_name." <".$from_mail.">\r\n";
$header .= "Reply-To: ".$replyto."\r\n";
$header .= "MIME-Version: 1.0\r\n";
$header .= "Content-Type: multipart/mixed; boundary=\"".$uid."\"\r\n\r\n";
$header .= "This is a multi-part message in MIME format.\r\n";
$header .= "--".$uid."\r\n";
$header .= "Content-type:text/html; charset=iso-8859-1\r\n";
$header .= "Content-Transfer-Encoding: 7bit\r\n\r\n";
$header .= $message."\r\n\r\n";

    foreach ($files as $filename) { 

        $file = $path.$filename;

        $file_size = filesize($file);
        $handle = fopen($file, "r");
        $content = fread($handle, $file_size);
        fclose($handle);
        $content = chunk_split(base64_encode($content));

        $header .= "--".$uid."\r\n";
        $header .= "Content-Type: application/octet-stream; name=\"".$filename."\"\r\n"; // use different content types here
        $header .= "Content-Transfer-Encoding: base64\r\n";
        $header .= "Content-Disposition: attachment; filename=\"".$filename."\"\r\n\r\n";
        $header .= $content."\r\n\r\n";
    }

$header .= "--".$uid."--";
return mail($mailto, $subject, "", $header);
}
Fears answered 19/11, 2012 at 17:43 Comment(6)
This just in... some servers have been having trouble with \r\n and the attachments and any html is not rendered. The fix is to use PHP_EOL in all cases. That is all.Fears
I like your approach. However I could not get it to send an Android APK to a tablet. Well. it sent the APK but it would not install.Nonfulfillment
note your $path will need to have a / at the end, or at the beginning of all $filename in $files array, and filenames cannot have whitespace, or change $file = $path.$filename; to $file = "$path/$filename"; Also, this line is pointless: $name = basename($file); and should be removed.Ansilme
My path variable standard is to always include the trailing slash and that's a good point. And, yes indeed, that is an extraneous line of code you found in there.Fears
For PHP >=5.6 you will need to split the message from the header to avoid the Multiple or malformed newlines found in additional_header error as mentioned here. You will also need to drop the double \r\n after the first Content-type (but this may be PHP7-specific ... not sure)Vagus
This example doesn't work at all. Please make a working sample for supporting bretty html messages and symbols like õ ä ö and also chinese and russian symbols.Gravedigger
E
12

To send an email with attachment we need to use the multipart/mixed MIME type that specifies that mixed types will be included in the email. Moreover, we want to use multipart/alternative MIME type to send both plain-text and HTML version of the email.Have a look at the example:

<?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"; 
?>

As you can see, sending an email with attachment is easy to accomplish. In the preceding example we have multipart/mixed MIME type, and inside it we have multipart/alternative MIME type that specifies two versions of the email. To include an attachment to our message, we read the data from the specified file into a string, encode it with base64, split it in smaller chunks to make sure that it matches the MIME specifications and then include it as an attachment.

Emission answered 1/3, 2012 at 16:23 Comment(2)
It's proper to include source when you copy paste from some other source. I don't even know if this is THE source, but they have comments dating back to 2007: webcheatsheet.com/php/send_email_text_html_attachment.phpBergerac
Yes, Sanjay blatantly cut and pasted text from another website and did not attribute it.Shayshaya
E
4

SWIFTMAIL in php works gr8 for attachment with mails.

Download swiftmailer from here http://swiftmailer.org/

Look at the simple code below

INCLUDE FILE

require_once('path/to/swiftMailer/lib/swift_required.php');

CREATE TRANSPORT

//FOR SMTP
// Create the Transport
$transport = Swift_SmtpTransport::newInstance('smtp.googlemail.com', 465, 'ssl')
    ->setUsername('[email protected]')
    ->setPassword('gmailpassword');

OR

//FOR NORMAL MAIL
$transport = Swift_MailTransport::newInstance();

MAILER OBJECT

// Create the Mailer using your created Transport
$mailer = Swift_Mailer::newInstance($transport);

CREATE MESSAGE OBJECT

$message = Swift_Message::newInstance($subject)
    ->setFrom(array($from => $from))
    ->setTo($to)
    ->setBody($body);
$message->attach(Swift_Attachment::fromPath($filepath));

SEND MESSAGE

$result = $mailer->send($message);
Eleventh answered 19/6, 2013 at 7:59 Comment(1)
This actually looks quite nice, I'll take a look at it! Thanks :)Heuser
A
2

If you really want to learn how to format an Internet Message then you should refer its Request For Comments (aka RFC). The one that defines "Multipurpose Internet Mail Extensions - Format of Internet Message Bodies" is RFC2045 issued on November 1996.

The format is somehow very strict and must be followed as-is.

Basically the message contains a header and the body. The header defines the type of the message, the way it's formated, some other fields which differ from one type to another.

The body is formed by different entities. An entity can be for instance just a plain-text like "Hello there!" but also can be an image, an attachment, whatever.

NOTE In the following examples everything enclosed into brackets (eg. {hello}) should be replaced with your real value. Any newline is in reality CRLF (ie. ASCII 13 + ASCII 10). Where you see two CRLF stick to it. It would be the worst moment to show how creative you are.

Basically for an email message that has attachments the header should look like this:

MIME-Version: 1.0
To: {email@domain}
Subject: {email-subject}
X-Priority: {2 (High)}
Content-Type: multipart/mixed; boundary="{mixed-boudary}"

In the above example the {mixed-boudary} may be any unique hash value, like 000008050800060107020705. The others are self-explanatory.

Now, anytime we want to append a new entity to the message (like the message body, an image, an attachment) we have to tell the email agent that a new section is coming, ie. to prefix that entity with the {mixed-boundary} value. We call this "open the boundary". Note that by opening a boundary we don't insert that boundary as was defined initially, we use 2 more minus signs in front, like --{mixed-boudary}. When we close a boundary we proceed likewise, except that we have to use other 2 minus signs at the end, like --{mixed-boudary}--

--{mixed-boudary}
the entity content
--{mixed-boudary}--

Because the email agent should understand what type has the content of our new inserted entity we have to declare that right after the opening of boundary. The declaration is just a header which contains only those parameters/values that are compatible with the entity.

For a HTML body content my entity header would look like:

Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: 7bit

so the whole body (enclosed into boundaries) will finally look like:

--{mixed-boudary}
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: 7bit

<html>
<head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head>
<body bgcolor="#FFFFFF" text="#000000">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque vel 
dapibus arcu. Duis quam dui, ornare non mi nec, luctus faucibus massa. Vivamus 
quis purus in erat euismod ullamcorper vitae eget dolor. Aliquam tempor erat 
accumsan, consectetur ex et, rhoncus risus.
</body>
</html>

If another entity has to be inserted we proceed exactly like above. When there is no more data to add to the message we close the mixed-boundary, ie. CRLF + --{mixed-boudary}--.

If for any reason an entity has to be inserted with an alternative representation (eg. a body message is inserted both as plain-text format and also as HTML format), then the entity content has to be declared with content-type multipart/alternative (although the global multipart/mixed header still remains!). Each alternative representation will be enclosed by this new boundary.

A complete example below:

MIME-Version: 1.0
To: {email@domain}
Subject: {email-subject}
X-Priority: {2 (High)}
Content-Type: multipart/mixed; boundary="{mixed-boudary}"

--{mixed-boudary}
Content-Type: multipart/alternative; boundary="{alternative-boudary}"

--{alternative-boudary}
Content-Type: text/plain; charset=utf-8;
Content-Transfer-Encoding: 7bit

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque vel 
dapibus arcu. Duis quam dui, ornare non mi nec, luctus faucibus massa. Vivamus 
quis purus in erat euismod ullamcorper vitae eget dolor. Aliquam tempor erat 
accumsan, consectetur ex et, rhoncus risus.

--{alternative-boudary}
Content-Type: text/html; charset=utf-8;
Content-Transfer-Encoding: 7bit

<html>
<head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head>
<body bgcolor="#FFFFFF" text="#000000">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque vel 
dapibus arcu. Duis quam dui, ornare non mi nec, luctus faucibus massa. Vivamus 
quis purus in erat euismod ullamcorper vitae eget dolor. Aliquam tempor erat 
accumsan, consectetur ex et, rhoncus risus.
</body>
</html>

--{alternative-boudary}--

--{mixed-boudary}
Content-Type: application/pdf; name="myfile.pdf"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="myfile.pdf"

JVBERi0xLjINOCAwIG9iag08PCAvTGVuZ3RoIDkgMCBSIC9GaWx0ZXIgL0ZsYXRlRGVjb2Rl
ID4+DXN0cmVhbQ1oQ51bbY/cNg7+BfsfhAUO11w3riW/B7gPaZEAAdpcm06RL8EBzoyn68uM
vZ3xZLv//khKsuUxNaMNiiabpUg+pKiHsmxJEcN/UsgiilP4ab2/+XF1I81vszSqclHIOEpj
sdrf/PC2EFVUpmK1vXkZxVKs1uJlJJVYPYrvPra7XVvvxYdIrE7rL83hhVj97+bNyjUoFam7
FnOB+tubGI3FZEkwmhpKXpVRnqJi0PCyjBJ1DjyOYqWBxxXp/1h3X+ov9abZt434pV0feoG/
ars/xU/9/qEZmm7diJ+abmgOr0TGeFNFEuXx5M4B95Idns/QAaJMI1IpKeXi9+ZhaPafm4NQ
cRwzNpK0iirlRvisRBZpVJa+PP51091kkjBWBXrJxUuZRjIXh0Z8FN3MnB5X5st5Kay9355n

--{mixed-boudary}--

TIPS

Use your preferred email client (mine is Thunderbird) and send to yourself one message plain-text only, one HTML only, one mixed, and then each of the earlier but with one file attachment enclosed. When you receive the message just study its source (View -> Message source).

@Edit: a very well documented case study + PHP example can be found at here

Archiearchiepiscopacy answered 8/4, 2015 at 11:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.