Very often, we need to send emails with attachments programmatically. Let’s add attachments to the email in this post.
We must write some code to add attachments. We require that we override the class Transportbuilder to send attachments over email.
Attachments are essential for sending PDF attachments over email and sharing receipts as PDFs. Allow customers to share their requirements via images, PDFs, or documents over email.
Let’s look at how we can add attachments to emails.
Magento 2: Add Attachments to the Email
Override the class Transportbuilder in your custom module.
Magento\Framework\Mail\Template\TransportBuilder
I want to avoid touching the original class. Hence, I have replicated TransportBuilder in my module instead of overriding it. But it depends on your requirements. How will you handle your transporter? It is simple, based on your needs and choices.
We must declare a private variable for attachments and then add the function with the name addAttachment.
/**
* Adding Attachments to the email
*
* @param mixed $content
* @param string $fileName
* @param string $fileType
* @return $this
*/
public function addAttachment($content, $fileName, $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;
}
Then, please replace the lines below inside the function prepareMessage.
$this->messageData['encoding'] = $mimePart->getCharset();
$this->messageData['body'] = $this->mimeMessageInterfaceFactory->create(
['parts' => [$mimePart]]
);
Could you replace the above line with the code below?
$parts = count($this->attachments) ? array_merge([$mimePart], $this->attachments) : [$mimePart];
$this->messageData['body'] = $this->mimeMessageInterfaceFactory->create(
['parts' =>$parts]
);
That’s it. Now we can add attachments by calling the function addAttachment of the transportBuilder class.
foreach ($attachments as $attachment) {
$fileInfo = $this->file->getPathInfo($attachment);
$this->transportBuilder->addAttachment(
$this->driver->fileGetContents($attachment),
$fileInfo['basename'],
mime_content_type($attachment)
);
}
Let’s check the transportBuilder class and service class codes from which we send emails programmatically.
Transport Builder Class
<?php
namespace SbDevBlog\Email\Mail\Template;
use Magento\Framework\App\TemplateTypesInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Mail\AddressConverter;
use Magento\Framework\Mail\EmailMessageInterfaceFactory;
use Magento\Framework\Mail\MessageInterface;
use Magento\Framework\Mail\MessageInterfaceFactory;
use Magento\Framework\Mail\MimeInterface;
use Magento\Framework\Mail\MimeMessageInterfaceFactory;
use Magento\Framework\Mail\MimePartInterfaceFactory;
use Magento\Framework\Mail\Template\FactoryInterface;
use Magento\Framework\Mail\Template\SenderResolverInterface;
use Magento\Framework\Mail\TransportInterfaceFactory;
use Magento\Framework\ObjectManagerInterface;
use Magento\Framework\Phrase;
use Laminas\Mime\Mime;
use Laminas\Mime\PartFactory;
/**
* TransportBuilder for Mail Templates
*/
class TransportBuilder extends \Magento\Framework\Mail\Template\TransportBuilder
{
/**
* Param that used for storing all message data until it will be used
*
* @var array
*/
private array $messageData = [];
/**
* @var EmailMessageInterfaceFactory
*/
private EmailMessageInterfaceFactory $emailMessageInterfaceFactory;
/**
* @var MimeMessageInterfaceFactory
*/
private MimeMessageInterfaceFactory $mimeMessageInterfaceFactory;
/**
* @var MimePartInterfaceFactory
*/
private MimePartInterfaceFactory $mimePartInterfaceFactory;
/**
* @var PartFactory
*/
private PartFactory $partFactory;
/**
* @var array
*/
private array $attachments = [];
/**
* Constructor
*
* @param FactoryInterface $templateFactory
* @param MessageInterface $message
* @param SenderResolverInterface $senderResolver
* @param ObjectManagerInterface $objectManager
* @param TransportInterfaceFactory $mailTransportFactory
* @param PartFactory $partFactory
* @param MessageInterfaceFactory|null $messageFactory
* @param EmailMessageInterfaceFactory|null $emailMessageInterfaceFactory
* @param MimeMessageInterfaceFactory|null $mimeMessageInterfaceFactory
* @param MimePartInterfaceFactory|null $mimePartInterfaceFactory
* @param AddressConverter|null $addressConverter
*/
public function __construct(
FactoryInterface $templateFactory,
MessageInterface $message,
SenderResolverInterface $senderResolver,
ObjectManagerInterface $objectManager,
TransportInterfaceFactory $mailTransportFactory,
PartFactory $partFactory,
MessageInterfaceFactory $messageFactory = null,
EmailMessageInterfaceFactory $emailMessageInterfaceFactory = null,
MimeMessageInterfaceFactory $mimeMessageInterfaceFactory = null,
MimePartInterfaceFactory $mimePartInterfaceFactory = null,
AddressConverter $addressConverter = null
) {
$this->partFactory = $partFactory;
$this->emailMessageInterfaceFactory = $emailMessageInterfaceFactory ?: $this->objectManager
->get(EmailMessageInterfaceFactory::class);
$this->mimeMessageInterfaceFactory = $mimeMessageInterfaceFactory ?: $this->objectManager
->get(MimeMessageInterfaceFactory::class);
$this->mimePartInterfaceFactory = $mimePartInterfaceFactory ?: $this->objectManager
->get(MimePartInterfaceFactory::class);
parent::__construct(
$templateFactory,
$message,
$senderResolver,
$objectManager,
$mailTransportFactory,
$messageFactory,
$emailMessageInterfaceFactory,
$mimeMessageInterfaceFactory,
$mimePartInterfaceFactory,
$addressConverter
);
}
/**
* Prepare message.
*
* @return $this
* @throws LocalizedException if template type is unknown
*/
protected function prepareMessage()
{
$template = $this->getTemplate();
$content = $template->processTemplate();
switch ($template->getType()) {
case TemplateTypesInterface::TYPE_TEXT:
$partType = MimeInterface::TYPE_TEXT;
break;
case TemplateTypesInterface::TYPE_HTML:
$partType = MimeInterface::TYPE_HTML;
break;
default:
throw new LocalizedException(
new Phrase('Unknown template type')
);
}
/** @var \Magento\Framework\Mail\MimePartInterface $mimePart */
$mimePart = $this->mimePartInterfaceFactory->create(
[
'content' => $content,
'type' => $partType
]
);
$this->messageData['encoding'] = $mimePart->getCharset();
$parts = count($this->attachments) ? array_merge([$mimePart], $this->attachments) : [$mimePart];
$this->messageData['body'] = $this->mimeMessageInterfaceFactory->create(
['parts' =>$parts]
);
// phpcs:disable Magento2.Functions.DiscouragedFunction
$this->messageData['subject'] = html_entity_decode(
(string)$template->getSubject(),
ENT_QUOTES
);
$this->message = $this->emailMessageInterfaceFactory->create($this->messageData);
return $this;
}
/**
* Adding Attachments to the email
*
* @param mixed $content
* @param string $fileName
* @param string $fileType
* @return $this
*/
public function addAttachment($content, $fileName, $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;
}
}
Email Service Classes
<?php
namespace SbDevBlog\Email\Services;
use Magento\Framework\App\Area;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\MailException;
use Magento\Framework\Translate\Inline\StateInterface;
use SbDevBlog\Email\Mail\Template\TransportBuilder;
use Magento\Store\Model\Store;
use Magento\Framework\Filesystem\Io\File;
use Magento\Framework\Filesystem\Driver\File as Driver;
use Psr\Log\LoggerInterface;
class EmailService
{
/**
* @var EmailConfigService
*/
private EmailConfigService $emailConfigService;
/**
* @var StateInterface
*/
private StateInterface $state;
/**
* @var TransportBuilder
*/
private TransportBuilder $transportBuilder;
/**
* @var LoggerInterface
*/
private LoggerInterface $logger;
/**
* @var File
*/
private File $file;
/**
* @var Driver
*/
private Driver $driver;
/**
* Constructor
*
* @param EmailConfigService $emailConfigService
* @param StateInterface $state
* @param TransportBuilder $transportBuilder
* @param File $file
* @param Driver $driver
* @param LoggerInterface $logger
*/
public function __construct(
EmailConfigService $emailConfigService,
StateInterface $state,
TransportBuilder $transportBuilder,
File $file,
Driver $driver,
LoggerInterface $logger
) {
$this->emailConfigService = $emailConfigService;
$this->state = $state;
$this->transportBuilder = $transportBuilder;
$this->file = $file;
$this->driver = $driver;
$this->logger = $logger;
}
/**
* Send Email
*
* @param mixed $templateId
* @param string|null $recipient
* @param array $sender
* @param array $emailVariables
* @param array $ccEmail
* @param array $bccEmail
* @param array $attachments
* @return bool
*/
public function sendEmail(
$templateId,
string $recipient = null,
array $sender = [],
array $emailVariables = [],
array $ccEmail = [],
array $bccEmail = [],
array $attachments = []
): bool {
$this->state->suspend();
$recipient = $recipient ? $recipient : $this->emailConfigService->getRecipientEmail();
$sender = !empty($sender) ? $sender : $this->emailConfigService->getSender();
try {
$this->transportBuilder
->setTemplateIdentifier($templateId)
->setTemplateOptions(
[
'area' => Area::AREA_FRONTEND,
'store' => Store::DEFAULT_STORE_ID,
]
)
->setTemplateVars($emailVariables)
->setFromByScope($sender)
->addTo($recipient);
foreach ($attachments as $attachment) {
$fileInfo = $this->file->getPathInfo($attachment);
$this->transportBuilder->addAttachment(
$this->driver->fileGetContents($attachment),
$fileInfo['basename'],
mime_content_type($attachment)
);
}
if (!empty($ccEmail)) {
$this->transportBuilder->addCc($ccEmail);
}
if (!empty($bccEmail)) {
$this->transportBuilder->addBcc($bccEmail);
}
$transport = $this->transportBuilder->getTransport();
$transport->sendMessage();
$this->state->resume();
return true;
} catch (MailException|LocalizedException|\Exception $e) {
$this->logger->info($e->getMessage());
}
$this->state->resume();
return false;
}
}
<?php
namespace SbDevBlog\Email\Services;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\ScopeInterface;
class EmailConfigService
{
private const ADMIN_CONTACT_RECIPIENT_EMAIL_XML_PATH = "contact/email/recipient_email";
private const ADMIN_CONTACT_SENDER_EMAIL_XML_PATH = "contact/email/sender_email_identity";
/**
* @var ScopeConfigInterface
*/
private ScopeConfigInterface $scopeConfig;
/**
* Constructor
*
* @param ScopeConfigInterface $scopeConfig
*/
public function __construct(
ScopeConfigInterface $scopeConfig
) {
$this->scopeConfig = $scopeConfig;
}
/**
* Get Recipient EMail
*
* @return string
*/
public function getRecipientEmail(): string
{
return $this->getConfig(self::ADMIN_CONTACT_RECIPIENT_EMAIL_XML_PATH);
}
/**
* Get Configuration
*
* @param string $path
* @return mixed
*/
private function getConfig(string $path)
{
return $this->scopeConfig->getValue($path, ScopeInterface::SCOPE_STORE);
}
/**
* Get Sender Details
*
* @return array
*/
public function getSender(): array
{
return $this->getSenderDetails($this->getConfig(self::ADMIN_CONTACT_SENDER_EMAIL_XML_PATH));
}
/**
* Get sender details
*
* @param string|null $senderType
* @return array
*/
private function getSenderDetails(string $senderType = null): array
{
$identity = $senderType ? "trans_email/ident_" . $senderType . "/" : "trans_email/ident_general/";
return [
"email" => $this->getConfig($identity . "email"),
"name" => $this->getConfig($identity."name")
];
}
}
That’s it. Thanks for reading SbDevBlog.
Click to send an email programmatically. However, To add attachments, we first need to upload files on the server, and then we can use them as attachments.
Click here to upload files programmatically.
Note: Please verify the code of this blog and the relevant git repository before using it in production.
🙂 HAPPY CODING 🙂