Sending attachments in multipart emails with Zend\Mail
I’ve written before about how to send an HTML email with a text alternative in Zend\Mail, but recently needed to send an attachment with my multipart email.
With help from various sources on the Internet, this is how to do it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
use Zend\Mail\Message; use Zend\Mime\Message as MimeMessage; use Zend\Mime\Part as MimePart; use Zend\Mime\Mime; use Zend\Mail\Transport\Sendmail; function sendEmail($to, $from, $subject, $html, $text, $attachments = null) { $message = new Message(); $message->addTo($to); $message->addFrom($from); $message->setSubject($subject); // HTML part $htmlPart = new MimePart($html); $htmlPart->encoding = Mime::ENCODING_QUOTEDPRINTABLE; $htmlPart->type = "text/html; charset=UTF-8"; // Plain text part $textPart = new MimePart($text); $textPart->encoding = Mime::ENCODING_QUOTEDPRINTABLE; $textPart->type = "text/plain; charset=UTF-8"; $body = new MimeMessage(); if ($attachments) { // With attachments, we need a multipart/related email. First part // is itself a multipart/alternative message $content = new MimeMessage(); $content->addPart($textPart); $content->addPart($htmlPart); $contentPart = new MimePart($content->generateMessage()); $contentPart->type = "multipart/alternative;\n boundary=\"" . $content->getMime()->boundary() . '"'; $body->addPart($contentPart); $messageType = 'multipart/related'; // Add each attachment foreach ($attachments as $thisAttachment) { $attachment = new MimePart($thisAttachment['content']); $attachment->filename = $thisAttachment['filename']; $attachment->type = Mime::TYPE_OCTETSTREAM; $attachment->encoding = Mime::ENCODING_BASE64; $attachment->disposition = Mime::DISPOSITION_ATTACHMENT; $body->addPart($attachment); } } else { // No attachments, just add the two textual parts to the body $body->setParts(array($textPart, $htmlPart)); $messageType = 'multipart/alternative'; } // attach the body to the message and set the content-type $message->setBody($body); $message->getHeaders()->get('content-type')->setType($messageType); $message->setEncoding('UTF-8'); $transport = new Sendmail(); $transport->send($message); } |
Let’s look at some key parts.
The text of the email
The HTML and plain text parts of the email are treated the same. We create a Zend\Mime\Part and set the encoding and type. For HTML, it looks like this:
1 2 3 |
$htmlPart = new MimePart($html); $htmlPart->encoding = Mime::ENCODING_QUOTEDPRINTABLE; $htmlPart->type = "text/html; charset=UTF-8"; |
Note that if your text is UTF-8, then you must set this in the part’s type, as it is not passed through from the message level when set there.
Creating the multipart/alternative
If we have attachments, then we need to create a new multipart/alternative Zend\Mime\Message for the first part which contains the HTML and plain text parts:
1 2 3 4 5 6 7 8 9 10 |
$content = new MimeMessage(); $content->addPart($textPart); $content->addPart($htmlPart); $contentPart = new MimePart($content->generateMessage()); $contentPart->type = "multipart/alternative;\n boundary=\"" . $content->getMime()->boundary() . '"'; $body->addPart($contentPart); $messageType = 'multipart/related'; |
The important section here is that $contentPart is created with the generated message from the $content object which is the text of the two parts with the correct mime sections around them. We then need to set the type to mulipart/alternative and also define the boundary as the auto-generated identifier from $content.
Adding attachments
Attachments are similar to adding text:
1 2 3 4 5 6 7 |
$attachment = new MimePart($thisAttachment['file_data']); $attachment->filename = $thisAttachment['filename']; $attachment->type = Mime::TYPE_OCTETSTREAM; $attachment->encoding = Mime::ENCODING_BASE64; $attachment->disposition = Mime::DISPOSITION_ATTACHMENT; $body->addPart($attachment); |
Again, you we create a Zend\Mime\Part for each attachment containing the raw file data. We also need to set the filename property along with the type, encoding and disposition.
Setting content-type
If we don’t have attachments, then the message type is ‘multipart/alternative’, so that’s set in the other half of the if/else. We can then add the body to the message and set its content-type:
1 2 3 |
$message->setBody($body); $message->getHeaders()->get('content-type')->setType($messageType); $message->setEncoding('UTF-8'); |
We’re all done, so we can send the email.
In this code, I’m using the Sendmail transport, but obviously Zend\Mail supports other options, including SMTP.
Thanks for sharing Rob.
Pro-tip: Never ever try to manipulate the mime headers yourself unless you know what you are doing ;) If you "just" use the respective methods everyting will work fine. We once had an issue with Outlook due to a wrong mime header which was not easy to spot.