WooCommerce has the option to defer/background-send its emails instead of sending them while processing the action the visitor has taken. This can have a few benefits for customers visiting your store:
- If their actions on the store are crashing when sending emails (such as the “new account” email), deferring the emails can let the customer continue with their order.
- The site may be more responsive because less work is being performed for each action the customer takes.
However, the background email functionality is newer and less-tested.
The current default (as of this writing in July of 2019) is to send the emails immediately. You may come across other pages mentioning the deferred emails as being the default, but that’s because the default has been changed twice.
Enabling deferred emails
Adding this code to your functions.php (or a plugin file) will cause emails to be sent in a background task:
add_filter( 'woocommerce_defer_transactional_emails', '__return_true' );
Or use this code to ensure the default behavior (sending emails during the processing of the user’s request) in case it’s been changed by other code:
add_filter( 'woocommerce_defer_transactional_emails', '__return_false', PHP_INT_MAX );
Disabling sending of the deferred/background email
I originally thought the woocommerce_allow_send_queued_transactional_email
hook would be useful for temporarily disabling queued mail sending while troubleshooting. But as far as I can tell from digging into the code, the email task will be considered “completed” whether or not it’s allowed to be sent by the filter. However, if that’s useful in some way, you can turn off the sending with:
add_filter( 'woocommerce_allow_send_queued_transactional_email', '__return_false' );
How it works behind the scenes
High-level
WooCommerce uses the WP Background Processing library to manage queuing and running the background emails. WC_Email acts as a middleman for the following notifications, forwarding them to their “real” versions which have a “_notification” suffix:
woocommerce_created_customer
woocommerce_low_stock
woocommerce_new_customer_note
woocommerce_no_stock
woocommerce_order_fully_refunded
woocommerce_order_partially_refunded
woocommerce_order_status_cancelled_to_completed
woocommerce_order_status_cancelled_to_on-hold
woocommerce_order_status_cancelled_to_processing
woocommerce_order_status_completed
woocommerce_order_status_failed_to_completed
woocommerce_order_status_failed_to_on-hold
woocommerce_order_status_failed_to_processing
woocommerce_order_status_on-hold_to_cancelled
woocommerce_order_status_on-hold_to_failed
woocommerce_order_status_on-hold_to_processing
woocommerce_order_status_pending_to_completed
woocommerce_order_status_pending_to_failed
woocommerce_order_status_pending_to_on-hold
woocommerce_order_status_pending_to_processing
woocommerce_order_status_processing_to_cancelled
woocommerce_product_on_backorder
If a custom email needs other notifications, the woocommerce_email_actions
filter can be used to append to the array.
The details
WC_Emails::init_transactional_emails
hooks the above email-triggering actions with queue_transactional_email
for background emailing or send_transactional_email
for immediate emailing. queue_transactional_email
uses an instance of WC_Background_Emailer
to queue the hook and its arguments for later execution by WP-Cron. When it’s later executed, it calls back into WC_Emails::send_queued_transactional_email
. If the woocommerce_allow_send_queued_transactional_email
filter (true by default) allows it, the hook appended with “_notification” is executed, allowing the email to be sent. There doesn’t appear to be any conditional logic allowing it to retry later, so I don’t think woocommerce_allow_send_queued_transactional_email
is an effective way of temporarily disabling email you want to send later.
You’ve made it to the end of the article…
…which is a stunning feat. As a reward, here are 3 randomly-generated emojis that will surely tell a compelling story:
I am trying to find a way to defer the sending of the order completed email. I stepped through the WooCommerce 4.0.1 code and found exactly what you found – the `woocommerce_allow_send_queued_transactional_email` looks like an ‘all or nothing’ filter.
Having said that, it’s arguments (the order number and status string) could allow you to look at when the order was completed and compare it to the current time. Do you think that this would work?
As a test I tried modifying the timestamp argument in wp_schedule_event() calls in WooCommerce source from 10 to 600 but it didn’t see to make any difference. Bizarre.
I’ve asked my question on wordpress.org (https://wordpress.org/support/topic/delay-emails-by-more-than-10-sec/) but your post has given me an idea. Please email me directly if you want to reply.
I experimented with the ‘woocommerce_allow_send_queued_transactional_email’ and found what you found – it only runs once. This is disappointing because I was so excited when I wrote this code to only send after 10 minutes.
https://gist.github.com/damiencarbery/b2d0889364a0fd6878024402e1697104
Another idea might be to add one’s own scheduled event after an order is completed. It would use the above hook to not send the order completed email and then add an event for 10 mins later.
Inspired by https://stackoverflow.com/a/60284673/8605943 the scheduled event could use
`WC()->mailer()->get_emails()[‘WC_Email_Customer_Completed_Order’]->trigger( $order_id );`
to send the email later.
I think I got it!!
In `woocommerce_allow_send_queued_transactional_email` I allow all emails to be sent except for `woocommerce_order_status_completed`. For it I wp_schedule_single_event() for 10 mins in the future, specifying $order_id as an argument.
The triggered schedule fuction calls: `WC()->mailer()->get_emails()[‘WC_Email_Customer_Completed_Order’]->trigger( $order_id );`
I’ll blog about it and publish my post on Monday next.
Here’s my code that will defer the order completed email for 10 minutes. It’s (partially) set up to allow for the delay of different emails for different lengths of time.
Oops, the code: https://gist.github.com/damiencarbery/dfc45a9ceb52fa336749b363a6e7dd51#file-wc-defer-order-emails-php
Thank you for posting the update! Half the time people just disappear when they figure out a solution 🙂
There’s probably some reason this wouldn’t work (I’m not familiar with Shippo), but could the email be set to manual, and then triggered from a hook for whatever method (API call?) Shippo uses to update the shipping info?