Magento 2 – How Asynchronous Email Sending Works

emailmagento2

In Magento 1.x if you set this config to enabled Store > Configration > Sales > Sales Emails > General Settings > Asynchronous sending, Magento, instead of sending the email when the order is placed, adds an entry in a queue table (core_email_queue), and then a cron job comes, reads all the entries from the queue and sends the emails.

I don't find core_email_queue table in Magento 2. How does this feature work in Magento 2?

Best Answer

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.

Related Topic