Pragmatism in the real world

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.

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:

    $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:

        $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:

            $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:

    $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.

One thought on “Sending attachments in multipart emails with Zend\Mail

  1. 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.

Comments are closed.