24

How to send email with a file attachment.

asked Mar 1, 2016 at 6:28

8 Answers 8

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.

answered Jan 10, 2017 at 20:57
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.? 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

answered May 15, 2019 at 12:15
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 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;
 }
}
answered Sep 20, 2019 at 21:10
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? :/ 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? 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;
 }
answered Dec 23, 2018 at 17:44
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();
answered Mar 18, 2019 at 16:47
7
  • before give -1, it is suggested by so to use this comment textarea, then everyone could understand what is wrong, thx Commented Apr 2, 2019 at 7:57
  • $transport->getMessage()->setBody($bodyPart); Commented Nov 22, 2019 at 5:54
  • getting this Uncaught Error: Call to undefined method Magento\\Framework\\Mail\\EmailMessage::setBody() Commented Nov 22, 2019 at 5:55
  • These comments are not related to the answer Commented Nov 22, 2019 at 7:20
  • i am getting this error in magento 2.3.3 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();
answered Sep 1, 2019 at 17:24
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;
 }
answered Apr 5, 2022 at 8:24
0

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'));
}

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.