24

How to send email with a file attachment.

7 Answers 7

25

M2 does not come with out of the box however it is a feature built into the zend framework. Here is a good reference how to add this functionality into magento: https://blog.bitexpert.de/blog/sending-mails-with-attachments-in-magento-2/

In case link goes dead, create the following

<?php
namespace Your\CustomModule\Magento\Mail\Template;

class TransportBuilder 
    extends \Magento\Framework\Mail\Template\TransportBuilder
{
    public function addAttachment(
        $body,
        $mimeType    = Zend_Mime::TYPE_OCTETSTREAM,
        $disposition = Zend_Mime::DISPOSITION_ATTACHMENT,
        $encoding    = Zend_Mime::ENCODING_BASE64,
        $filename    = null
    ) {
        $this->message->createAttachment($body, $mimeType, $disposition, $encoding, $filename);
        return $this;
    }
}

then add to etc/di.xml

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="\Magento\Framework\Mail\Template\TransportBuilder"
                type="\Your\CustomModule\Magento\Mail\Template\TransportBuilder" />
</config>

Now you can use addAttachment() throughout your site.

7
  • 10
    I still wonder why magento TransportBuilder does not have this method Commented Jun 28, 2017 at 8:15
  • 5
    How can we attached file in custom email magento 2.3 ? because its using zendframework 2 and this answer not working any more Commented Dec 7, 2018 at 5:51
  • 3
    How to Send Email with Attachment in Magento 2.3? Commented Dec 31, 2018 at 5:35
  • @ManishMaheshwari & Mitesh Have you got the solution? Commented Jan 8, 2019 at 10:14
  • 2
    This solution is not working in Magento2.3 anymore. Is anyone have alternative for attachment.?
    – nishu
    Commented Aug 6, 2019 at 13:24
17

As of Magento 2.2.7 the solutions described above do not work anymore since \Magento\Framework\Mail\Message dropped extending \Zend_Mail.
To circumvent the lack of an easy way to add attachments through the transport builder (which currently seems to be the correct place for such a function), one needs to create a replacement for the TransportBuilder and make use \Zend\Mime\Part:

<?php
namespace Your\CustomModule\Magento\Mail\Template;

use Magento\Framework\Mail\MessageInterface;
use Magento\Framework\Mail\MessageInterfaceFactory;
use Magento\Framework\Mail\Template\FactoryInterface;
use Magento\Framework\Mail\Template\SenderResolverInterface;
use Magento\Framework\Mail\TransportInterfaceFactory;
use Magento\Framework\ObjectManagerInterface;
use Zend\Mime\Mime;
use Zend\Mime\Part as MimePart;
use Zend\Mime\PartFactory as MimePartFactory;
use Zend\Mime\Message as MimeMessage;
use Zend\Mime\MessageFactory as MimeMessageFactory;

class TransportBuilder extends \Magento\Framework\Mail\Template\TransportBuilder
{
    /** @var MimePart[] */
    private $parts = [];

    /** @var MimeMessageFactory */
    private $mimeMessageFactory;

    /** @var MimePartFactory */
    private $mimePartFactory;

    public function __construct(
        FactoryInterface $templateFactory,
        MessageInterface $message,
        SenderResolverInterface $senderResolver,
        ObjectManagerInterface $objectManager,
        TransportInterfaceFactory $mailTransportFactory,
        MimePartFactory $mimePartFactory,
        MimeMessageFactory $mimeMessageFactory,
        MessageInterfaceFactory $messageFactory = null
    ) {
        parent::__construct(
            $templateFactory,
            $message,
            $senderResolver,
            $objectManager,
            $mailTransportFactory,
            $messageFactory
        );

        $this->mimePartFactory    = $mimePartFactory;
        $this->mimeMessageFactory = $mimeMessageFactory;
    }

    protected function prepareMessage()
    {
        parent::prepareMessage();

        $mimeMessage = $this->getMimeMessage($this->message);

        foreach ($this->parts as $part) {
            $mimeMessage->addPart($part);
        }

        $this->message->setBody($mimeMessage);

        return $this;
    }

    public function addAttachment(
        $body,
        $mimeType = Mime::TYPE_OCTETSTREAM,
        $disposition = Mime::DISPOSITION_ATTACHMENT,
        $encoding = Mime::ENCODING_BASE64,
        $filename = null
    ) {
        $this->parts[] = $this->createMimePart($body, $mimeType, $disposition, $encoding, $filename);
        return $this;
    }

    private function createMimePart(
        $content,
        $type = Mime::TYPE_OCTETSTREAM,
        $disposition = Mime::DISPOSITION_ATTACHMENT,
        $encoding = Mime::ENCODING_BASE64,
        $filename = null
    ) {
        /** @var MimePart $mimePart */
        $mimePart = $this->mimePartFactory->create(['content' => $content]);
        $mimePart->setType($type);
        $mimePart->setDisposition($disposition);
        $mimePart->setEncoding($encoding);

        if ($filename) {
            $mimePart->setFileName($filename);
        }

        return $mimePart;
    }

    private function getMimeMessage(MessageInterface $message)
    {
        $body = $message->getBody();

        if ($body instanceof MimeMessage) {
            return $body;
        }

        /** @var MimeMessage $mimeMessage */
        $mimeMessage = $this->mimeMessageFactory->create();

        if ($body) {
            $mimePart = $this->createMimePart((string)$body, Mime::TYPE_TEXT, Mime::DISPOSITION_INLINE);
            $mimeMessage->setParts([$mimePart]);
        }

        return $mimeMessage;
    }
}

Don't forget to replace the original \Magento\Framework\Mail\Template\TransportBuilder by your implementation via di.xml.

Note that this implementation will probably break with an upcoming release of Magento as \Magento\Framework\Mail\MessageInterface::setBody() is deprecated and may be removed soonish.

HTH

4
  • Hi! You have a method addAttachment in your code, but where did you call them? I don't see it. Commented May 21, 2019 at 15:36
  • thanks! I added loop to prepareMessage method and everithing works. Commented May 21, 2019 at 16:03
  • @NikolaiSilin how to send a png or other files. Commented Jun 5, 2019 at 12:18
  • @Jean-Bernard Valentaten it worked in Magento 2.2.11, thank you so much this saves a lot of time
    – Mohit Rane
    Commented Sep 2, 2020 at 12:08
13

Magento 2.3.x Compatible:

This was my answer for Magento 2.3 since this was a top question on google and there seems to be a lot of people in the comments looking.

There seems to be a lot of desire in other posts about overwriting the default TransportBuilder class via etc/di.xml, however the module I'm working on is so small that I don't want it to be responsible for the default TransportBuilder so I built a Helper class (should probably be a model based on how coupled it is to the declared email template - but I digress).

The TransportBuilder doesn't have public access to the TransportInterface, but instead generates a clone everytime and then resets the builder. I found it easier to build my TransportInterface instance and then attach my attachment Part objects to the transport's message. If you do find it necessary to overwrite the default TransportBuilder via dependency injection preference, be careful about updating public methods. Remember to practice the O when keeping your code SOLID!

<?php
namespace Vendor\Module\Helper;

use Magento\Framework\App\Area;
use Magento\Framework\App\Helper\AbstractHelper;
use Magento\Framework\App\Helper\Context;
use Magento\Framework\DataObject;
use Magento\Framework\Filesystem\Io\File;
use Magento\Framework\Mail\Template\TransportBuilder;
use Magento\Framework\Mail\TransportInterface;
use Magento\Store\Model\StoreManagerInterface;
use Zend_Mime;
use Zend\Mime\Part;

/**
 * This was initially built out to send a single email. Abstract this as you 
 * wish.
 *
 * @package Vendor\Module\Helper
 */
class Mail extends AbstractHelper
{
    /**
     * @var Context
     */
    protected $context;

    /**
     * @var TransportBuilder
     */
    protected $transportBuilder;

    /**
     * @var StoreManagerInterface
     */
    protected $storeManager;

    /**
     * @var Config
     */
    protected $config;

    /**
     * Mail constructor.
     *
     * @param Context $context
     * @param TransportBuilder $transportBuilder
     * @param StoreManagerInterface $storeManager
     * @param Config $config
     * @param File $file
     */
    public function __construct(
        Context $context,
        TransportBuilder $transportBuilder,
        StoreManagerInterface $storeManager,
        Config $config,
        File $file
    ) {
        parent::__construct($context);
        $this->transportBuilder = $transportBuilder;
        $this->storeManager = $storeManager;
        $this->config = $config;
        $this->file = $file;
    }

    /**
     * Send the email for a Help Center submission.
     *
     * @param DataObject $templateParams
     * @param array $attachments
     * @return void
     */
    public function send(DataObject $templateParams, array $attachments = [])
    {
        $storeId = $this->storeManager->getStore()->getId();

        // Build transport
        /** @var \Magento\Framework\Mail\TransportInterface $transport */
        $transport = $this->transportBuilder
            ->setTemplateOptions(['area' => Area::AREA_FRONTEND, 'store' => $storeId])
            ->setTemplateIdentifier($this->config->getEmailTemplate())
            ->setTemplateVars($templateParams->toArray())
            ->setFrom($this->config->getEmailSender())
            ->addTo($this->config->getEmailRecipient(), 'Help Center')
            /**
             * Something important to note is that when the getTransport()
             * function is run, the message is compiled and then the builder 
             * class resets (as of 2.3.1). 
             * 
             * This is note worthy because if you want to send > 1 attachment,
             * your $builder will be reset -- losing all of the ->set* functions
             * you just used above as well as your attachment.
             * 
             * Since we append attachments to the transport, it's easier to:
             * build -> attach -> send. And this way multiple attachments 
             * can be included. :thumbsup:
             */
            ->getTransport();

        // Attach Images to transport
        foreach ($attachments as $a) {
            $transport = $this->addAttachment($transport, $a);
        }

        // Send transport
        $transport->sendMessage();
    }

    /**
     * Add an attachment to the message inside the transport builder.
     *
     * @param TransportInterface $transportBuilder
     * @param array $file Sanitized index from $_FILES
     * @return TransportInterface
     */
    protected function addAttachment(TransportInterface $transport, array $file): TransportInterface
    {
        $part = $this->createAttachment($file);
        $transport->getMessage()->getBody()->addPart($part);

        return $transport;
    }

    /**
     * Create an zend mime part that is an attachment to attach to the email.
     * 
     * This was my usecase, you'll need to edit this to your own needs.
     *
     * @param array $file Sanitized index from $_FILES
     * @return Part
     */
    protected function createAttachment(array $file): Part
    {
        $ext =  '.' . explode('/', $file['type'])[1];
        $fileName = md5(uniqid(microtime()), true) . $ext;

        $attachment = new Part($this->file->read($file['tmp_name']));
        $attachment->disposition = Zend_Mime::TYPE_OCTETSTREAM;
        $attachment->encoding = Zend_Mime::ENCODING_BASE64;
        $attachment->filename = $fileName;

        return $attachment;
    }
}
5
  • 1
    I can't get it to work properly, I always get an exception saying "Uncaught Error: Call to a member function addPart() on string"... any idea about that? :/
    – hallleron
    Commented Nov 6, 2019 at 9:30
  • 1
    @hallleron Oddly enough this is different than what I was getting, but it looks like you're correct. The MessageInterface::getBody method signature shows a string return type. You might have to dig around in your TransportInterface object, but I can tell you that the addPart method exists on a Zend\Mime\Message object. Since magento likely extended that class for their own Message class, I think it would be smart to try $transport->getMessage()->addpart($part); Commented Nov 6, 2019 at 17:01
  • How to apply this in Magento 2.3?
    – fudu
    Commented Jul 29, 2020 at 4:34
  • 1
    getBody() call was missing before addPart() call. I updated the answer. Commented Feb 5, 2021 at 15:12
  • 1
    Thank you @RomanSnitko Commented Feb 5, 2021 at 19:15
2

Magento 2 Custom email from Module, Doesn't provide image attachment.

If you want to use Image attachment with email templates in Magento 2 you need to override class, Magento\Framework\Mail\Template\TransportBuilder

Magento Out-of-box doesn't provide attachment feature for email. You can refer blogs for send image attachment in details,

You need to add logic like below way,

 public function addAttachment(
        $body,
        $mimeType    = \Zend_Mime::TYPE_OCTETSTREAM,
        $disposition = \Zend_Mime::DISPOSITION_ATTACHMENT,
        $encoding    = \Zend_Mime::ENCODING_BASE64,
        $filename    = null
    ) {
        $this->message->createAttachment($body, $mimeType, $disposition, $encoding, $filename);
        return $this;
    }
4
  • 1
    Can you help to achieve same in magento 2.3 ? Commented Jan 8, 2019 at 10:15
  • Created attachments in this way until 2.2.7. 2.2.8 and 2.3+ are not working Commented Mar 30, 2019 at 17:05
  • I just posted an answer for 2.3.x @MatthiasKleine Commented Sep 20, 2019 at 21:18
  • hello,how can i attach if i have base64 encoding string? Commented Sep 24, 2019 at 13:09
2

As mentioned by the previous answers, magento2 does not have a function out-of-the-box to send mails with attachments.

I don't know if it is a best practice, but you could call directly Zend_Mail class to do it, without create a custom function and override Magento\Framework\Mail\Template\TransportBuilder, like below

$mail = new \Zend_Mail('utf-8');
$mail->setFrom($senderEmail);
$mail->addTo($receiverEmail);
$mail->setSubject($subject);
$mail->setBodyHtml($text);

$content = file_get_contents($attachmentAbsolutePath);

$attachment = new \Zend_Mime_Part($content);
$attachment->type = 'text/xml'; // attachment's mime type
$attachment->disposition = \Zend_Mime::DISPOSITION_ATTACHMENT;
$attachment->encoding = \Zend_Mime::ENCODING_BASE64;
$attachment->filename = $filename;
$mail->addAttachment($attachment);
$mail->send();
7
  • before give -1, it is suggested by so to use this comment textarea, then everyone could understand what is wrong, thx
    – LucScu
    Commented Apr 2, 2019 at 7:57
  • $transport->getMessage()->setBody($bodyPart);
    – imtiazau
    Commented Nov 22, 2019 at 5:54
  • getting this Uncaught Error: Call to undefined method Magento\\Framework\\Mail\\EmailMessage::setBody()
    – imtiazau
    Commented Nov 22, 2019 at 5:55
  • These comments are not related to the answer
    – LucScu
    Commented Nov 22, 2019 at 7:20
  • i am getting this error in magento 2.3.3
    – imtiazau
    Commented Nov 22, 2019 at 7:59
1

Here is perfect answer to send pdf in Email in magetno 2.3

$transport = $_transportBuilder->setTemplateIdentifier(20)
    ->setTemplateOptions($templateOptions)                                                 
    ->setTemplateVars($templateVars)
    ->setFrom($from)
    ->addTo($vendor_email)
    ->getTransport();

$html = $transport->getMessage()>getBody()->generateMessage();            
$bodyMessage = new \Zend\Mime\Part($html);
$bodyMessage->type = 'text/html';
$attachment = $_transportBuilder->addAttachment($pdfData,$fileName);      
$bodyPart = new \Zend\Mime\Message();
$bodyPart->setParts(array($bodyMessage,$attachment));
$transport->getMessage()->setBody($bodyPart);                
$transport->sendMessage();
$inlineTranslation->resume();
2
  • Hi, It is throwing a fatal error: Uncaught Error: Call to a member function generateMessage() on null Commented Sep 11, 2019 at 5:42
  • You're creating a new message which is unnecessary when your transport already has a message. Why not just add a part to the one in place? This is messy and hard to follow. Not to mention you're doubling the work and memory needed to solve this problem. Commented Sep 20, 2019 at 21:13
0

Override \Magento\Framework\Mail\Template\TransportBuilder in di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="\Magento\Framework\Mail\Template\TransportBuilder" type="TM\ShipmentEmail\Model\Mail\Template\TransportBuilder" />
</config>

Define this constant if you need in overrides file and add following data

const MIME_TYPES = [
        'png' => 'image/png',
        'jpg' => 'image/jpg'
    ];
    
  //check file type  
protected function prepareMessage()
{
$snapshotFileTypes = self::MIME_TYPES[ (string) substr($snapshot, strrpos($snapshot, '.') + 1); ];

 //call for attachment in custom function    
        $this->addAttachment(file_get_contents($fullSnapshotPath), $endSnap, $snapshotFileType );
}
       // finally add attachment in this funtion

        public function addAttachment(?string $content, ?string $fileName, ?string $fileType)
        {
            $attachmentPart = $this->partFactory->create();
            $attachmentPart->setContent($content)
                ->setType($fileType)
                ->setFileName($fileName)
                ->setDisposition(Mime::DISPOSITION_ATTACHMENT)
                ->setEncoding(Mime::ENCODING_BASE64);
            $this->attachments[] = $attachmentPart;
    
            return $this;
        }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.