Defer WooCommerce emails for a specified time after the normal delivery time.
Update 7 March 2022: I changed the scheduling code to use Action Scheduler that is included with WooCommerce. If WP Cron is disabled this change will help as WooCommerce will run Action Scheduler automatically. To view the scheduled action you do not need to install a plugin, instead go to WooCommerce/Status/Scheduled Actions.
Update 4 May 2022: I have significantly changed the algorithm for the execution of the deferred emails. Instead of calling the email function I call action hook they were added to. This makes the code generic i.e. it will run all functions/emails attached to the deferred action hook.
After completing the code for Add Tracking Info to WooCommerce order Elizabeth reported intermittent issues where the tracking info was not in the Order Completed emails. I suspected that there was a small delay with Shippo pushing the tracking data back to her site. Shippo suggested delaying the Order Completed email to allow for the small delay (which could be a few minutes).
Stepping through the code
When WooCommerce 3.0 was released it deferred transactional emails (i.e. emails sent when the order status changes) by 5 seconds (increased to 10 seconds in later release). Deferring the emails was primarily to speed up the checkout process. In version 3.0.3 this was disabled by default but could be enabled with a filter.
I read through the WooCommerce code and later stepped through it with Visual Studio Code. The debugging confirmed what my code reading found – there was no WooCommerce filter to change the delay time from 10 seconds. I even looked at filters in the wp_schedule_event() function but there didn’t appear to be enough data to idenfity WooCommerce email events.
I found that another developer, Dan Wich, encountered the same issue. While typing a comment on his post I got an idea – set up my own scheduled event!
It happens frequently when, after extensive investigation and debugging of an issue, writing a post on a forum gives me an idea for a solution. It’s a sort of ‘a problem shared is a problem halved‘ type thing.
I had used WP Cron about 3 years ago but I needed to reread the wp_schedule_single_event() docs and look for examples. In a later version of my code I changed it to use as_schedule_single_action(), the Action Scheduler equivalent of wp_schedule_single_event().
My experiments in trying to find a filter were very helpful in finding the existing WooCommerce filters that I needed to use. The final logic is quite simple:
- Enable deferred emails
- Examine an email just before it is to be sent – disallow it and schedule a future event to send it (include the order ID and email identifier)
- The scheduled event function directly triggers the email for the specified order ID.
When an email is deferred you can see it in a list of scheduled actions (under WooCommerce/Status/Scheduled Actions).
I installed Advanced Cron Manager plugin while testing. The scheduled event is called ‘send_deferred_woocommerce_email‘. The arguments are the order number and the email identifier.
My initial code only deferred the Order Completed email but I generalised the code to allow any WooCommerce transactional email to be deferred and a different delay time for each one.