I had no idea how emails are sent in Magento 2, but your question took my interest so I investigated a little.
I have started my investigation by searching email keyword on cronjobs, and got the following output.
$ n98-magerun2 sys:cron:list | grep mail
| sales_send_order_creditmemo_emails | default | */1 | * | * | * | * |
| sales_send_order_emails | default | */1 | * | * | * | * |
| sales_send_order_invoice_emails | default | */1 | * | * | * | * |
| sales_send_order_shipment_emails | default | */1 | * | * | * | * |
So, there are some cronjobs which are responsible for sending emails. This shows us that they are async.
I have selected sales_send_order_emails
cronjob to investigate because I can easily create a new order to test it. Then I have searched for sales_send_order_emails
in the whole project, and found the following file vendor/magento/module-sales/etc/crontab.xml
. It has the following cron-job entry.
<job name="sales_send_order_emails" instance="SalesOrderSendEmailsCron" method="execute">
<schedule>*/1 * * * *</schedule>
</job>
SalesOrderSendEmailsCron
is clearly a virtual type otherwise it should have a namespaces. This virtual type is defined in vendor/magento/module-sales/etc/di.xml
.
<virtualType name="SalesOrderSendEmailsCron" type="Magento\Sales\Cron\SendEmails">
<arguments>
<argument name="emailSenderHandler" xsi:type="object">SalesOrderSendEmails</argument>
</arguments>
</virtualType>
So the virtual type SalesOrderSendEmailsCron
is actually the class Magento\Sales\Cron\SendEmails
. As you can see this class has a constructor with only one argument, and it is given as SalesOrderSendEmails
virtual type. This virtual type is also defined in the vendor/magento/module-sales/etc/di.xml
. Here is the type definition:
<virtualType name="SalesOrderSendEmails" type="Magento\Sales\Model\EmailSenderHandler">
<arguments>
<argument name="emailSender" xsi:type="object">Magento\Sales\Model\Order\Email\Sender\OrderSender</argument>
<argument name="entityResource" xsi:type="object">Magento\Sales\Model\ResourceModel\Order</argument>
<argument name="entityCollection" xsi:type="object" shared="false">Magento\Sales\Model\ResourceModel\Order\Collection</argument>
</arguments>
</virtualType>
So the real mail sender is Magento\Sales\Model\EmailSenderHandler
. If you open this file, you will see the following method:
public function sendEmails()
{
if ($this->globalConfig->getValue('sales_email/general/async_sending')) {
$this->entityCollection->addFieldToFilter('send_email', ['eq' => 1]);
$this->entityCollection->addFieldToFilter('email_sent', ['null' => true]);
/** @var \Magento\Sales\Model\AbstractModel $item */
foreach ($this->entityCollection->getItems() as $item) {
if ($this->emailSender->send($item, true)) {
$this->entityResource->save(
$item->setEmailSent(true)
);
}
}
}
}
If you set sales_email/general/async_sending
config as true
, then this cronjob will be sending mails. It also has the following filters to fetch orders.
$this->entityCollection->addFieldToFilter('send_email', ['eq' => 1]);
$this->entityCollection->addFieldToFilter('email_sent', ['null' => true]);
As a result, you have send_email
and email_sent
attributes in your sales_order
table. If send_email
is set to 1
for an order, this cronjob should send an email for the order. If email_sent
is not NULL
, then it has already done.
core_email_queue
table, Magento simply queries the order table itself for orders with theemail_sent
flag set to null. (The order is the "entity" referenced in the code snippet he shows.)