HowTo: Send Emails Programmatically in Magento 2
On this page
Sooner or later every project needs to send an email that Magento does not send out of the box, a notification to an
admin, a reminder to a customer, a report to the warehouse. The good news is that Magento gives us a clean service to
do it, the TransportBuilder. Let me show you the setup I use.
This post assumes that there is already a custom module created in
app/code/Vendor/Module and that you have registered an email template. If you need the template part, look at HowTo: Create Custom Email Templates in Magento 2.The service
I like to wrap the whole thing in a small service so the calling code stays clean and testable.
<?php
/** app/code/Vendor/Module/Model/EmailSender.php */
namespace Vendor\Module\Model;
use Magento\Framework\Mail\Template\TransportBuilder;
use Magento\Framework\Translate\Inline\StateInterface;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Framework\App\Area;
class EmailSender
{
/**
* @var TransportBuilder
*/
private $transportBuilder;
/**
* @var StateInterface
*/
private $inlineTranslation;
/**
* @var StoreManagerInterface
*/
private $storeManager;
public function __construct(
TransportBuilder $transportBuilder,
StateInterface $inlineTranslation,
StoreManagerInterface $storeManager
) {
$this->transportBuilder = $transportBuilder;
$this->inlineTranslation = $inlineTranslation;
$this->storeManager = $storeManager;
}
/**
* @param string $toEmail
* @param array $templateVars
*/
public function send($toEmail, array $templateVars = [])
{
$storeId = $this->storeManager->getStore()->getId();
$this->inlineTranslation->suspend();
try {
$transport = $this->transportBuilder
->setTemplateIdentifier('vendor_module_email_template')
->setTemplateOptions(['area' => Area::AREA_FRONTEND, 'store' => $storeId])
->setTemplateVars($templateVars)
->setFromByScope('general')
->addTo($toEmail)
->getTransport();
$transport->sendMessage();
} finally {
$this->inlineTranslation->resume();
}
}
}A few things worth pointing out:
setTemplateIdentifieris theidyou gave the template inemail_templates.xml.- Always
suspend()the inline translation before sending andresume()after, otherwise you can get the translation markup leaking into the email. Doing it in afinallyblock guarantees it resumes even if sending throws. setFromByScope('general')uses the store’s general contact, but you can pass your own['email' => ..., 'name' => ...].
Using it
From a controller, an observer or another service, it is now a one liner:
$this->emailSender->send('customer@example.com', ['customer_name' => 'John', 'order_id' => '000000123']);Those variables are the ones you reference in the template with {{var customer_name}}.
Hope this helps.
If you have any question or know a better way to achieve this, feel free to share in the comments below.
Related links: