How to send emails from Shopware 6 programmatically
Lately, I have needed to send an email notification from one Shopware 6 store, if something happened in one of the background scripts. Of course, I could have just used the standard PHP mail function, but I thought, that Shopware certainly must have something of its own. And it has! There is a class Shopware\Core\Content\Mail\Service\AbstractMailService, that contains the “send” method, that we can use to send emails. So now you know, how to send emails in Shopware 6. You are welcome, end of the article.
Of course not, just kidding. 😉 As usual, we will take a look at some examples and talk a bit about the code. In the end, our goal here will be to create a standalone email service, that you can copy to your plugin, register in your services.xml file and then inject it to any class, from which you need it to send some emails for you.
This presumes, that you already have a plugin with a class, to which you could inject our new email service. If you don’t have a plugin yet, then you can for example download and use the sample “skeleton plugin”, available in my Shopware 6 plugin programming tutorial. In the following examples, I will use the name “TestPlugin”, so do not forget to replace it by the name of your plugin, if you want to copy and use my code.
A simple email service for Shopware 6
Without further introduction, here is the code of the email service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
<?php namespace TestPlugin\Service; use Shopware\Core\Content\Mail\Service\AbstractMailService; use Shopware\Core\Framework\Validation\DataBag\DataBag; use Shopware\Core\System\SalesChannel\SalesChannelContext; class EmailService { private $mailService; public function __construct( AbstractMailService $mailService ) { $this->mailService = $mailService; } /** * Method for sending an email notification * * @param array $recipients * @param string $senderName * @param string $subject * @param string $messageHtml * @param SalesChannelContext $salesChannelContext */ public function sendMail( array $recipients, string $senderName, string $subject, string $messageHtml, SalesChannelContext $salesChannelContext ) { $data = new DataBag(); //basic e-mail data $data->set('recipients', $recipients); //format: ['email address' => 'recipient name'] $data->set('senderName', $senderName); $data->set('subject', $subject); $data->set('contentHtml', $messageHtml); $data->set('contentPlain', strip_tags($messageHtml)); //set sales channel context $data->set('salesChannelId', $salesChannelContext->getSalesChannel()->getId()); //send the e-mail $this->mailService->send($data->all(), $salesChannelContext->getContext(), []); } } |
I think it is quite obvious, that the “EmailService” class contains just one method – “sendMail”. To this method, you just have to pass the mandatory parameters. First of all, you need to prepare the email recipients as an array, where the email addresses are keys and the recipient names are values. Then you need a sender name, that you want to display to the person, who receives the email in their favorite email program. And obviously, you need to specify the subject and the content of the email message. The content could be an HTML. The class strips the HTML tags for the plaintext version of the email automatically. Last, but not least, you have to specify the sales channel context – more on that later..
So, now we have our mail service wrapper. Here is how we register it for Shopware in the services.xml file (located in the src/Resources/config in your plugin’s main directory):
1 2 3 4 5 6 7 8 9 10 11 12 |
<?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="TestPlugin\Service\EmailService"> <argument type="service" id="Shopware\Core\Content\Mail\Service\MailService"/> </service> </services> </container> |
You may have expected the fore mentioned Shopware\Core\Content\Mail\Service\AbstractMailService class to be added as an argument to the services.xml file. However, there is another service instead. That is, because the AbstractMailService is an abstract class, which means it can not be instantiated. Therefore, we specify the Shopware\Core\Content\Mail\Service\MailService, which extends this abstract class, to be the one, that gets used in the end. Like this, we could use any other class, that would extend the AbstractMailService class.
And finally, here is how we could inject the email service to some other class and send an email from it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?php namespace TestPlugin\Service; use Shopware\Core\System\SalesChannel\SalesChannelContext; class TestService { public function testMethod (SalesChannelContext $salesChannelContext) { $email = 'test@email.xx'; $message = ' <p>This is a testing email</p> <p>TEST TEST TEST</p> '; $this->emailService->sendMail([$email => 'Test recipient'], 'Test sender', 'Test subject', $message, $salesChannelContext); } } |
This was tested and works on Shopware 6.4. It should be all you need to be able to send emails from Shopware 6. The article could end here now with a clear conscience, but let’s take it one step further and make some enhancements.
An advanced email service for Shopware 6
The simple email service is all nice and good for explaining things, but for real world usage, we should add some more perks and features to make our standalone service more robust and practical. So here is what I have added:
- specify the plaintext and HTML version of the message separately
- be able to use any version of the message for both cases
- create sales channel context automatically
- choose the template for the email
- return success or failure
Here is the code for my advanced Shopware 6 email service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
<?php namespace TestPlugin\Service; use Shopware\Core\Content\Mail\Service\AbstractMailService; use Shopware\Core\Content\MailTemplate\MailTemplateEntity; use Shopware\Core\Framework\Context; use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface; use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter; use Shopware\Core\Framework\Validation\DataBag\DataBag; use Shopware\Core\System\SalesChannel\SalesChannelContext; use Shopware\Core\Content\Mail\Service\MailService; use TestPlugin\Service\SalesChannelService; class EmailService { private $mailService; private $mailTemplateRepository; private $salesChannelService; public function __construct( AbstractMailService $mailService, EntityRepositoryInterface $mailTemplateRepository, SalesChannelService $salesChannelService ) { $this->mailService = $mailService; $this->mailTemplateRepository = $mailTemplateRepository; $this->salesChannelService = $salesChannelService; } /** * Method for sending an email notification * * @param array $recipients * @param string $senderName * @param string $subject * @param string $messageHtml * @param string $messagePlain * @param SalesChannelContext|null $salesChannelContext * @param array $templateData * @return bool */ public function sendMail( array $recipients, string $senderName, string $subject, string $messageHtml = '', string $messagePlain = '', SalesChannelContext $salesChannelContext = null, array $templateData = [] ) : bool { $data = new DataBag(); //basic e-mail data $data->set('recipients', $recipients); //format: ['email address' => 'recipient name'] $data->set('senderName', $senderName); $data->set('subject', $subject); $data->set('contentHtml', $messageHtml); $data->set('contentPlain', $messagePlain); //fill HTML and PlainText version with the other of the two, if empty if (!$messagePlain && $messageHtml) { $data->set('contentPlain', strip_tags($messageHtml)); } elseif ($messagePlain && !$messageHtml) { $data->set('contentHtml', $messagePlain); } //get the sales channel context, if not already present if (!isset($salesChannelContext)) { $salesChannelContext = $this->salesChannelService->createSalesChannelContext(); } //set sales channel context $data->set('salesChannelId', $salesChannelContext->getSalesChannel()->getId()); //set the template (not mandatory) $mailTemplate = $this->getMailTemplate(); $data->set('templateId', $mailTemplate->getId()); //send the e-mail $this->mailService->send($data->all(), $salesChannelContext->getContext(), []); $result = $this->mailService->send($data->all(), $salesChannelContext->getContext(), $templateData); if ($result) { return true; } else { return false; } } /** * Method for getting an email template by its ID or the first one available, if no ID is supplied * * @param string $id * @param Context $context * @return MailTemplateEntity|null */ private function getMailTemplate(string $id = null, Context $context = null): ?MailTemplateEntity { //get the sales channel context, if not already present if (!isset($context)) { $salesChannelContext = $this->salesChannelService->createSalesChannelContext(); $context = $salesChannelContext->getContext(); } //set the criteria for searching in the mail template repository $criteria = new Criteria(); $criteria->addAssociation('media.media'); $criteria->setLimit(1); //if a template ID was passed, we will get that template, otherwise just the first one the repository returns if (isset($id)) { $criteria->addFilter(new EqualsFilter('id', $id)); } //get and return one template return $this->mailTemplateRepository->search($criteria, $context)->first(); } } |
As you can see, quite a lot of code was added..
The newly added getMailTemplate method selects a specified (or default) template to be used from the template repository. You can find this repository as a table named “mail_template”, although for better orientation, I recommend the table “mail_template_translation”.
You can also take a look at the email templates in the Shopware 6 Administration. Just go to Settings – Shop – Email templates. The list looks like this:
To get the UUID of the template, that you want to use as a parameter for the getMailTemplate method, you can click on “Edit” and you will then see this ID in an address bar.
For the automatic creation of the sales channel context, I have used an extended version of the class from my article on the topic of Shopware 6 sales channel context creation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
<?php namespace TestPlugin\Service; use Shopware\Core\Framework\Context; use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface; use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter; use Shopware\Core\System\SalesChannel\Context\AbstractSalesChannelContextFactory; use Shopware\Core\System\SalesChannel\Context\SalesChannelContextService; use Shopware\Core\System\SalesChannel\SalesChannelContext; use Shopware\Core\System\SalesChannel\SalesChannelEntity; class SalesChannelService { private $salesChannelRepository; private $salesChannelContextFactory; public function __construct( EntityRepositoryInterface $salesChannelRepository, AbstractSalesChannelContextFactory $salesChannelContextFactory ) { $this->salesChannelRepository = $salesChannelRepository; $this->salesChannelContextFactory = $salesChannelContextFactory; } /** * Method for creating a sales channel context * * @param string|null $salesChannelId * @param string|null $languageId * @return SalesChannelContext */ public function createSalesChannelContext(string $salesChannelId = null, string $languageId = null) : SalesChannelContext { //get the sales channel ID and language ID, if they are missing if (!isset($salesChannelId) || !isset($languageId)) { $criteria = new Criteria(); if (isset($salesChannelId)) { $criteria->addFilter(new EqualsFilter('salesChannelId', $salesChannelId)); } if (isset($languageId)) { $criteria->addFilter(new EqualsFilter('languageId', $languageId)); } /** @var SalesChannelEntity $salesChannel */ $salesChannel = $this->salesChannelRepository->search($criteria, Context::createDefaultContext())->first(); if ($salesChannel) { $salesChannelId = $salesChannel->getId(); $languageId = $salesChannel->getLanguageId(); } } return $this->salesChannelContextFactory->create('', $salesChannelId, [SalesChannelContextService::LANGUAGE_ID => $languageId]); } } |
Of course, the services.xml file has to be adjusted accordingly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="TestPlugin\Service\EmailService"> <argument type="service" id="Shopware\Core\Content\Mail\Service\MailService"/> <argument type="service" id="mail_template.repository"/> <argument type="service" id="TestPlugin\Service\SalesChannelService"/> </service> <service id="TestPlugin\Service\SalesChannelService"> <argument type="service" id="sales_channel.repository"/> <argument type="service" id="Shopware\Core\System\SalesChannel\Context\SalesChannelContextFactory" /> </service> </services> </container> |
And here is an example of the advanced email service call, that you can use from your own plugin and class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<?php namespace TestPlugin\Service; class TestService { public function testMethod () { $email = 'test@email.xx'; $message = ' <p>This is a testing email</p> <p>TEST TEST TEST</p> '; $result = $this->emailService->sendMail([$email => 'Test recipient'], 'Test sender', 'Test subject', $message); if (!$result) { //do something, if the email was not sent successfully } } } |
That is it for now. I am satisfied with this version of my email service for Shopware 6 for the time being. It is good enough for my current purposes, but I am sure it could be taken even further. If you have some ideas on how to enhance it even further or have already added some features, you can share them with others in the comments below.
Heyo! Thanks a lot for your work and detailed explaination! I was struggling between Symfony, php, GuzzleMail and other dependencies, but your solution is way better and works like butter in a pancake XD
Im always following your work and time to time check if u have news things. Keep up the work! I’m pretty sure you’ve helped a lot of ppl with they problems already 😀
Did it like this now.
$data = new DataBag();
$mailTemplate = $this->getMailTemplate($salesChannelContext->getContext());
$data->set('recipients', [$recipientMail => $recipient->getFullName()]);
$data->set('senderName', $mailTemplate->getTranslation('senderName'));
$data->set('subject', $mailTemplate->getTranslation('subject'));
$data->set('contentPlain', $mailTemplate->getTranslation('contentPlain'));
$data->set('contentHtml', $mailTemplate->getTranslation('contentHtml'));
$data->set('templateId', $mailTemplate->getId());
//set sales channel context
$data->set('salesChannelId', $salesChannelContext->getSalesChannel()->getId());
//send the e-mail
$defaultData = $this->getTemplateDefaultData();
$templateData = array_merge($defaultData, $formData->all());
$this->mailService->send($data->all(), $salesChannelContext->getContext(), $templateData);
}
/**
* @param Context $context
* @return MailTemplateEntity|null
*/
private function getMailTemplate(Context $context): ?MailTemplateEntity
{
$id = $this->configService->get('xyz.config.emailTemplate');
$criteria = new Criteria([$id]);
$criteria->addAssociation('media.media');
return $this->mailTemplateRepository->search($criteria, $context)->first();
}
You are a lifesaver, man! I have all your articles saved in Favorites, and I am learning a lot from you. Thank you!