How to send email with a file attachment.
8 Answers 8
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.
- 
 10I still wonder why magento TransportBuilder does not have this method2017年06月28日 08:15:47 +00:00Commented Jun 28, 2017 at 8:15
- 
 5How can we attached file in custom email magento 2.3 ? because its using zendframework 2 and this answer not working any moreManish Maheshwari– Manish Maheshwari2018年12月07日 05:51:14 +00:00Commented Dec 7, 2018 at 5:51
- 
 3How to Send Email with Attachment in Magento 2.3?Dhaduk Mitesh– Dhaduk Mitesh2018年12月31日 05:35:46 +00:00Commented Dec 31, 2018 at 5:35
- 
 @ManishMaheshwari & Mitesh Have you got the solution?Sameer Bhayani– Sameer Bhayani2019年01月08日 10:14:15 +00:00Commented Jan 8, 2019 at 10:14
- 
 2This solution is not working in Magento2.3 anymore. Is anyone have alternative for attachment.?nishu– nishu2019年08月06日 13:24:31 +00:00Commented Aug 6, 2019 at 13:24
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
- 
 Hi! You have a method addAttachment in your code, but where did you call them? I don't see it.Mykola Silin– Mykola Silin2019年05月21日 15:36:23 +00:00Commented May 21, 2019 at 15:36
- 
 thanks! I added loop to prepareMessage method and everithing works.Mykola Silin– Mykola Silin2019年05月21日 16:03:38 +00:00Commented May 21, 2019 at 16:03
- 
 @NikolaiSilin how to send a png or other files.sumeet bajaj– sumeet bajaj2019年06月05日 12:18:01 +00:00Commented 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 timeMohit Rane– Mohit Rane2020年09月02日 12:08:37 +00:00Commented Sep 2, 2020 at 12:08
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;
 }
}
- 
 1I 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– hallleron2019年11月06日 09:30:40 +00:00Commented 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. TheMessageInterface::getBodymethod signature shows a string return type. You might have to dig around in yourTransportInterfaceobject, but I can tell you that theaddPartmethod exists on aZend\Mime\Messageobject. Since magento likely extended that class for their ownMessageclass, I think it would be smart to try$transport->getMessage()->addpart($part);domdambrogia– domdambrogia2019年11月06日 17:01:15 +00:00Commented Nov 6, 2019 at 17:01
- 
 How to apply this in Magento 2.3?fudu– fudu2020年07月29日 04:34:10 +00:00Commented Jul 29, 2020 at 4:34
- 
 1getBody() call was missing before addPart() call. I updated the answer.Roman Snitko– Roman Snitko2021年02月05日 15:12:17 +00:00Commented Feb 5, 2021 at 15:12
- 
 1Thank you @RomanSnitkodomdambrogia– domdambrogia2021年02月05日 19:15:39 +00:00Commented Feb 5, 2021 at 19:15
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;
 }
- 
 1Can you help to achieve same in magento 2.3 ?Sameer Bhayani– Sameer Bhayani2019年01月08日 10:15:20 +00:00Commented Jan 8, 2019 at 10:15
- 
 Created attachments in this way until 2.2.7. 2.2.8 and 2.3+ are not workingMatthias Kleine– Matthias Kleine2019年03月30日 17:05:16 +00:00Commented Mar 30, 2019 at 17:05
- 
 I just posted an answer for 2.3.x @MatthiasKleinedomdambrogia– domdambrogia2019年09月20日 21:18:25 +00:00Commented Sep 20, 2019 at 21:18
- 
 hello,how can i attach if i have base64 encoding string?Ketan Borada– Ketan Borada2019年09月24日 13:09:24 +00:00Commented Sep 24, 2019 at 13:09
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();
- 
 before give -1, it is suggested by so to use this comment textarea, then everyone could understand what is wrong, thxLucScu– LucScu2019年04月02日 07:57:39 +00:00Commented Apr 2, 2019 at 7:57
- 
 $transport->getMessage()->setBody($bodyPart);imtiazau– imtiazau2019年11月22日 05:54:57 +00:00Commented Nov 22, 2019 at 5:54
- 
 getting this Uncaught Error: Call to undefined method Magento\\Framework\\Mail\\EmailMessage::setBody()imtiazau– imtiazau2019年11月22日 05:55:18 +00:00Commented Nov 22, 2019 at 5:55
- 
 These comments are not related to the answerLucScu– LucScu2019年11月22日 07:20:29 +00:00Commented Nov 22, 2019 at 7:20
- 
 i am getting this error in magento 2.3.3imtiazau– imtiazau2019年11月22日 07:59:34 +00:00Commented Nov 22, 2019 at 7:59
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();
- 
 Hi, It is throwing a fatal error: Uncaught Error: Call to a member function generateMessage() on nullgajjala sandeep– gajjala sandeep2019年09月11日 05:42:59 +00:00Commented 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.domdambrogia– domdambrogia2019年09月20日 21:13:37 +00:00Commented Sep 20, 2019 at 21:13
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;
 }
The solution has been tested on version 2.4.7 and later.
Override the TransportBuilder class to include attachments and modify the prepareMessage method to add attachments.
<?php
declare(strict_types=1);
namespace Vendor\Customer\Model\Mail\Template;
use Magento\Framework\Mail\Template\TransportBuilder as MagentoTransportBuilder;
use Laminas\Mime\Mime;
use Laminas\Mime\Message as LaminasMimeMessage;
use Laminas\Mime\Part as LaminasMimePart;
class TransportBuilder extends MagentoTransportBuilder
{
 /**
 * @var array
 */
 protected $attachments = [];
 /**
 * Add an attachment to the email
 *
 * @param string $content The content of the attachment
 * @param string $fileName The name of the file to be attached
 * @param string $fileType The MIME type of the file
 * @return $this
 */
 public function addAttachment($content, $fileName, $fileType)
 {
 // Create a new MIME part for the attachment
 $attachmentPart = new LaminasMimePart($content);
 $attachmentPart->type = $fileType;
 $attachmentPart->filename = $fileName;
 $attachmentPart->disposition = Mime::DISPOSITION_ATTACHMENT;
 $attachmentPart->encoding = Mime::ENCODING_BASE64;
 $attachmentPart->charset = null;
 $this->attachments[] = $attachmentPart;
 return $this;
 }
 /**
 * Prepare the message with HTML content and attachments
 *
 * @return $this
 */
 protected function prepareMessage()
 {
 parent::prepareMessage();
 if (!empty($this->attachments)) {
 // Create HTML content part
 $bodyContent = $this->getTemplate()->processTemplate();
 $htmlPart = new LaminasMimePart($bodyContent);
 $htmlPart->type = 'text/html';
 $htmlPart->charset = 'utf-8';
 $htmlPart->encoding = Mime::ENCODING_QUOTEDPRINTABLE;
 $parts = [$htmlPart];
 // Add attachments directly without modification
 foreach ($this->attachments as $attachment) {
 $parts[] = $attachment;
 }
 // Create MIME message
 $bodyMessage = new LaminasMimeMessage();
 $bodyMessage->setParts($parts);
 // Set the message body
 $this->message->setBody($bodyMessage);
 }
 return $this;
 }
}
Declaration in di.xml :
 <preference for="Magento\Framework\Mail\Template\TransportBuilder" type="Vendor\Customer\Model\Mail\Template\TransportBuilder" />
Script for send mail :
try {
 $transport = $this->transportBuilder->setTemplateIdentifier([TEMPLATE_NAME])
 ->setTemplateOptions(['area' => 'frontend', 'store' => [STORE_ID]])
 ->setTemplateVars([EMAIL_TEMPLATE_VARIABLES])
 ->setFrom([SENDER_INFO])
 ->addTo([RECEIVER_INFO]);
 $fileContent = file_get_contents([FILE_PATH]);
 $transport = $this->transportBuilder->addAttachment($fileContent, [FILE_NAME],[FILE_TYPE]);
 $transport = $this->transportBuilder->getTransport();
 $transport->sendMessage();
} catch (\Exception $e) {
 $this->managerInterface->addErrorMessage(__('Email send fail'));
}