Update cookies preferences

How to add File Attachments in Email in Magento 2 (2.4.2-p1)

In this article we look at an issue we faced with our clients; how to add a file attachments in an Email in Magento 2

Email Attachment issue

We recently came across an issue while building an email notification for a client that requires file attachments. It simply failed to attach the files and after careful digging, we realised it was due to the changes in the Zend module.


To tackle this issue we resorted to creating a class we can use across the project. We created EmailService class, some could argue it could have been a helper class, however, helper classes can become bloated with other methods and I think in this case, this service class encapsulates the process of sending emails. Our class will be using the core Magento mail transport builder  Magento\Framework\Mail\Template\TransportBuilder 
to attach the file and send the email. To do this, we will add transport as a property in our class and our constructor will look like this:

namespace Develo\DailyAuctionReport\Service;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Mail\Template\TransportBuilder;
use Laminas\Mime\Mime;
use Laminas\Mime\Part;
use Laminas\Mime\Message;
use Magento\Framework\DataObject;
use Magento\Framework\Mail\TransportInterface;
use Magento\Framework\Filesystem\Io\File;
use Magento\Framework\Translate\Inline\StateInterface;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Framework\App\Area;

class EmailService
* @var TransportBuilder
protected $transportBuilder;

* @var ScopeConfigInterface
protected $scopeConfig;

* @var File
protected $file;

* @var StoreManagerInterface
protected $storeManager;

* Daily Completed Auction Report Template Id
const COMPLETED_AUCTION_TEMPLATE_ID = 'daily_completed_auction_report';

* Sender email config path
const XML_PATH_SUPPORT_SENDER_EMAIL = 'trans_email/ident_support/email';

* Sender name config path
const XML_PATH_SUPPORT_SENDER_NAME = 'trans_email/ident_support/name';

const XML_REPORT_EMAIL_RECIPIENTS = 'auction_daily_reports/general/report_email_recipients';

* @var StateInterface
protected $inlineTranslation;

* EmailService constructor
* @param TransportBuilder      $transportBuilder
* @param ScopeConfigInterface  $scopeConfig
* @param File                  $file
* @param StoreManagerInterface $storeManager
* @param StateInterface        $inlineTranslation
public function __construct(
TransportBuilder      $transportBuilder,
ScopeConfigInterface  $scopeConfig,
File                  $file,
StoreManagerInterface $storeManager,
StateInterface $inlineTranslation
) {
     $this->transportBuilder = $transportBuilder;
     $this->scopeConfig = $scopeConfig;
     $this->file = $file;
     $this->storeManager = $storeManager;
            $this->inlineTranslation = $inlineTranslation;

The responsibility of this class, is to send an email to a recipient with a file attached. So we will begin by creating a function to add a file attachment to an email. The Laminas doc has an example in their docs on how to do this.

Create File Attachment

Our method below is similar, it will take a file attachment, set its properties and return a part class i.e 


/** Create a Laminas mime part that is an attachment to attach to the email.
 * @param array $file Sanitized index from $_FILES
 * @return Part
protected function createAttachment(array $file): Part
    $attachment = new Part($this->file->read($file['tmp_name']));
    $attachment->disposition = Mime::DISPOSITION_ATTACHMENT;
    $attachment->encoding = Mime::ENCODING_BASE64;
    $attachment->filename = $file['name'];
    $attachment->type = 'text/csv';
    return $attachment;

Add File Attachment to the TransportBuilder

Next, we will add the file attachment to the transport interface. Think of it more like appending more content to an email body, only this time it's a file. Our method will take Laminas\Mime\Part and Magento\Framework\Mail\TransportInterface as an argument. 

* Add an attachment to the message inside the transport builder.
* @param TransportInterface $transport
* @param array $file Sanitized index from $_FILES
* @return TransportInterface

protected function addAttachment(TransportInterface $transport, array $file): TransportInterface
 $part = $this->createAttachment($file);
 $html = $transport->getMessage()->getBody()->generateMessage();
 $bodyMessage = new Part($html);
 $bodyMessage->type = 'text/html';
 $bodyPart = new Message();
 $contentTypeHeader = $transport->getMessage()->getZendMessage()->getHeaders()->get('Content-Type');
 return $transport;

So what we have down here is to break the example in the Laminas doc into two separate parts.

The final step is to create a method that will send an email to the recipient while attaching the file. 

* Send the email for a Help Center submission.
* @param DataObject $templateParams
* @param array      $attachments
* @throws \Magento\Framework\Exception\LocalizedException
* @throws \Magento\Framework\Exception\MailException
* @throws \Magento\Framework\Exception\NoSuchEntityException
public function send(DataObject $templateParams, array $attachments = []): void
$storeId = $this->storeManager->getStore()->getId();
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$logger = $objectManager->create(\Psr\Log\LoggerInterface::class);
foreach ($this->getConfigReportRecipients() as $recipient) {

   // Build transport
   /** @var \Magento\Framework\Mail\TransportInterface $transport */
   $transport = $this->transportBuilder
                    ->setTemplateOptions(['area' => Area::AREA_FRONTEND, 'store' => $storeId])
                    ->setFromByScope('support', 1)
                    ->addTo($recipient['email'], $recipient['name'])

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

                // Send transport
      } catch (\Exception $exception){
      $logger->debug(" Email Service Exception.". $exception->getMessage());


The method takes an array of files as an argument and template variables. These files will be attached to the transport class and each recipient is notified. The full content of the file is thus


To use our email service, in our case in a cron job we simply instantiate as below:

class CsvReportNotification
 * @var \Psr\Log\LoggerInterface
 protected $logger;

* @var ReportGeneratorHandler
protected $reportGenerator;

* @var EmailService
protected $emailService;

* Constructor
* @param \Psr\Log\LoggerInterface $logger
public function __construct(
\Psr\Log\LoggerInterface $logger,
ReportGeneratorHandler $reportGeneratorHandler,
EmailService $emailService
 $this->logger = $logger;
 $this->reportGenerator = $reportGeneratorHandler;
 $this->emailService = $emailService;

Passing the email service class via dependency injection in our CsvReportNotification job, we can initiate it in the execute function as below:

$dateRange = $this->reportGenerator->getDateRange('Last24h');
$csvData = $this->reportGenerator->fetchLast24HoursAuctions($dateRange);
$csvFile[] = $this->reportGenerator->createCsvReportFile($csvData);
$templateData = new \Magento\Framework\DataObject();
$templateData->setData(['dateAndTime' => date("m/d/Y")]);
  try {
    $this->emailService->send($templateData, $csvFile);
  catch (MailException | NoSuchEntityException | LocalizedException $e) {
   $this->logger->debug("CsvReport Notification Exception.". $e->getMessage());
$this->logger->info("Cronjob CsvReport Notification is executed.");

We are passing a CSV file generated from the report and the template variable to our email service. 

I hope this can set the base for spinning new ideas for improving on this approach.

Want to learn more about how to attach files to emails in Magento 2?



Ready to start your next project?

See how we can help you accelerate your business