WooCommerce’s background email feature

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:

6 thoughts on “WooCommerce’s background email feature”

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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?

Leave a Comment

Your email address will not be published. Required fields are marked *