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.

Resolution

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 

Laminas\Mime\Part

/** 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();
 $bodyPart->setParts(array($bodyMessage,$part));
 $transport->getMessage()->setBody($bodyPart);
 $contentTypeHeader = $transport->getMessage()->getZendMessage()->getHeaders()->get('Content-Type');
 $contentTypeHeader->setType('multipart/related');
 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();
$this->inlineTranslation->suspend();
$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])
                    ->setTemplateIdentifier(self::COMPLETED_AUCTION_TEMPLATE_ID)
                    ->setTemplateVars($templateParams->toArray())
                    ->setFromByScope('support', 1)
                    ->addTo($recipient['email'], $recipient['name'])
                    ->getTransport();

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

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

 }
 $this->inlineTranslation->resume();
 
}


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?


...
Raphael

share:

Ready to start your next project?

See how we can help you accelerate your business