Skip to content

Instantly share code, notes, and snippets.

@damiencarbery
Last active May 28, 2025 21:04
Show Gist options
  • Save damiencarbery/dfc45a9ceb52fa336749b363a6e7dd51 to your computer and use it in GitHub Desktop.
Save damiencarbery/dfc45a9ceb52fa336749b363a6e7dd51 to your computer and use it in GitHub Desktop.
Defer WooCommerce emails for a few minutes - Defer WooCommerce emails for a specified time after the normal delivery time. https://www.damiencarbery.com/2020/04/defer-woocommerce-emails-for-a-few-minutes/
<?php
/*
Plugin Name: Defer WooCommerce emails for a few minutes (use Action Scheduler)
Plugin URI: https://www.damiencarbery.com/2020/04/defer-woocommerce-emails-for-a-few-minutes/
Description: Defer WooCommerce emails for a specified time after the normal delivery time. Use Action Scheduler instead of WP Cron. Call do_action() instead of remove_action() and calling trigger.
Author: Damien Carbery
Author URI: https://www.damiencarbery.com
Version: 0.11.20250528
*/
class DeferSendingWooCommerceEmails {
private static $instance;
private $default_defer_time;
// An associative array to match $email_id with email class, to allow for the deferring of different emails.
private $email_id_to_defer;
private $debug_mode;
// Returns an instance of this class.
public static function get_instance() {
if ( null == self::$instance ) {
self::$instance = new DeferSendingWooCommerceEmails();
}
return self::$instance;
}
// Initialize the plugin variables.
public function __construct() {
$this->debug_mode = false;
// Uncomment this to enable debug mode.
//$this->debug_mode = true;
$this->default_defer_time = 600; // Defer for 600 seconds (10 minutes).
if ( $this->debug_mode ) {
$this->default_defer_time = 60; // Defer for 1 minute when debugging.
}
// Can add other email IDs and their defer times.
// You can uncomment the add_action() with 'order_status_changed' function to find out the before and after status.
// This is also the list of emails that will be deferred.
$this->email_id_to_defer = array(
'woocommerce_order_status_completed' => $this->default_defer_time,
//'woocommerce_order_status_processing' => $this->default_defer_time,
// Probably don't want to defer these emails but they are shown
// here as a demo of a different defer time.
//'woocommerce_new_customer_note' => $this->default_defer_time,
//'woocommerce_order_status_pending_to_on-hold' => $this->default_defer_time, // New order
//'woocommerce_order_status_pending_to_processing' => $this->default_defer_time,
// Additional transitition-to-email class info from @djm56
//'woocommerce_order_status_on-hold' => $this->default_defer_time, // Order on hold.
//'woocommerce_order_status_pending_to_on-hold' => $this->default_defer_time, // Order on hold.
//'woocommerce_order_status_cancelled_to_on-hold' => $this->default_defer_time, // Order on hold.
//'woocommerce_order_status_pending_to_on-hold' => $this->default_defer_time, // Order on hold.
);
// Wait until other plugins loaded so that we can check for some specific plugins.
add_action( 'plugins_loaded', array( $this, 'init' ) );
}
// Set up WordPress specfic actions.
public function init() {
// Use a filter to enable (or disable) debug mode.
$this->debug_mode = apply_filters( 'DeferSendingWooCommerceEmails_debug_mode', $this->debug_mode );
// Set all WooCommerce emails to be deferred.
add_filter( 'woocommerce_defer_transactional_emails', '__return_true' );
// Allow most emails to be sent as normal but prevent emails listed in $this->email_id_to_defer. Schedule them for a time in the future.
add_filter( 'woocommerce_allow_send_queued_transactional_email', array( $this, 'whether_send_queued_wc_email' ), 10, 3 );
// This is the scheduled function that will send the email.
add_action( 'send_deferred_woocommerce_email', array( $this, 'send_deferred_woocommerce_email' ), 10, 2 );
// If Custom Order Statuses for WooCommerce by Nuggethon is active (https://wordpress.org/plugins/custom-order-statuses-for-woocommerce/)
// attached to some additional hooks to defer new order status emails.
if ( class_exists( 'WOOCOS_Email_Manager' ) ) {
add_action( 'woocommerce_order_status_changed', array( $this, 'defer_woocos_emails' ), 1, 3 );
//add_action( 'woocommerce_order_status_changed_to_woocos', array( $this, 'woocos_email_trigger' ), 5, 4 );
// This is the scheduled function that will send the WooCOS emails.
add_action( 'send_deferred_woocos_mails', array( $this, 'send_deferred_woocos_mails' ), 10, 3 );
}
// DEBUG: Add the order modification time and current time to prove that the email was intentionally delayed.
if ( $this->debug_mode ) {
add_action( 'woocommerce_email_order_details', array( $this, 'add_defer_length_info_to_order_email' ), 5, 4 );
}
// Uncomment this to log the before and after statuses so you know what ones
// to add to the $this->email_id_to_defer array.
//add_action('woocommerce_order_status_changed', array( $this, 'order_status_changed' ), 10, 4);
}
// Log the before and after status to discover which one to add to $this->email_id_to_defer array.
public function order_status_changed( $id, $from, $to, $order ) {
if ( $this->debug_mode ) {
error_log( 'Order ID: ' . $id );
error_log( 'Status from: ' . $from );
error_log( 'Status to: ' . $to );
}
}
private function get_email_defer_time( $filter ) {
if ( array_key_exists( $filter, $this->email_id_to_defer ) ) {
return $this->email_id_to_defer[ $filter ];
}
return $this->default_defer_time;
}
public function whether_send_queued_wc_email( $true, $filter, $args ) {
if ( $this->debug_mode ) {
error_log( 'woocommerce_allow_send_queued_transactional_email $filter: ' . var_export( $filter, true ) );
error_log( 'woocommerce_allow_send_queued_transactional_email order_number: ' . var_export( $args[ 0 ], true ) );
error_log( 'woocommerce_allow_send_queued_transactional_email defer this email? ' . array_key_exists( $filter, $this->email_id_to_defer ) ? 'Yes' : 'No' );
}
if ( array_key_exists( $filter, $this->email_id_to_defer ) ) {
// TODO: Consider verifying that $args[0] is a valid order number.
//$order = wc_get_order( $args[ 0 ] );
//$action_args = array( 'filter' => $filter, 'args' => $args );
// Call Action Scheduler instead of WP Cron. Args will be filter and $args.
$event_id = as_schedule_single_action( time() + $this->get_email_defer_time( $filter ), 'send_deferred_woocommerce_email', array( $filter, $args ) );
//$order_num = $args[ 0 ];
//error_log( sprintf( 'woocommerce_allow_send_queued_transactional_email: Defer a %s email for order: %s for %d seconds (event ID: %d).', $filter, $order_num, $this->get_email_defer_time( $filter ), $event_id ) );
return false;
}
if ( $this->debug_mode ) {
error_log( 'woocommerce_allow_send_queued_transactional_email: Ok to send email.' );
}
return $true;
}
// Send the deferred email for order $order_id.
public function send_deferred_woocommerce_email( $filter, $args = array() ) {
if ( $this->debug_mode ) {
error_log( 'send_deferred_woocommerce_email for $filter: ' . $filter );
error_log( 'send_deferred_woocommerce_email for $args: ' . var_export( $args, true ) );
}
// Use same code as WC_Emails::send_queued_transactional_email() in woocommerce/includes/class-wc-emails.php
WC_Emails::instance();
// Ensure gateways are loaded in case they need to insert data into the emails.
WC()->payment_gateways();
WC()->shipping();
do_action_ref_array( $filter . '_notification', $args );
}
public function woocos_email_trigger( $order_id, $custom_order_status, $new_status, $posted ) {
if ( $this->debug_mode ) {
error_log( '$order_id:' . var_export( $order_id, true ) );
error_log( '$custom_order_status:' . var_export( $custom_order_status, true ) );
error_log( '$new_status:' . var_export( $new_status, true ) );
error_log( '$posted:' . var_export( $posted, true ) );
}
}
// Remove the WooCOS function and schedule it to be called it later.
public function defer_woocos_emails( $order_id, $old_status, $new_status ) {
remove_action('woocommerce_order_status_changed', 'woocos_add_custom_order_status_actions', 10, 3);
$event_id = as_schedule_single_action( time() + $this->get_email_defer_time( 'woocos' ), 'send_deferred_woocos_mails', array( $order_id, $old_status, $new_status ) );
}
public function send_deferred_woocos_mails( $order_id, $old_status, $new_status ) {
woocos_add_custom_order_status_actions( $order_id, $old_status, $new_status );
}
// This is an experimental function to add the date/time the order was modified and
// the date/time the email was sent into email - to demonstrate that the deferring code worked.
public function add_defer_length_info_to_order_email( $order, $sent_to_admin, $plain_text, $email ) {
if ( !is_object( $email ) ) { return; } // Ensure $email is an object before testing $email->id.
if ( 'customer_completed_order' == $email->id ) {
if ( $plain_text ) {
printf( '%sThe order was modified at %s and email sent at %s.', "\n", $order->get_date_modified(), current_time( 'mysql' ) );
}
else {
printf( '<p>The order was modified at <strong>%s</strong> and email sent at <strong>%s</strong>.</p>', $order->get_date_modified(), current_time( 'mysql' ) );
}
}
}
}
$DeferSendingWooCommerceEmails = new DeferSendingWooCommerceEmails();
@eduardgermoniy
Copy link

I have solved mine the problem! I commented out the hooks for debugging, because these actions are missing in the desired email. Message ID is"woocommerce_created_customer".

@damiencarbery
Copy link
Author

I have solved mine the problem! I commented out the hooks for debugging, because these actions are missing in the desired email. Message ID is"woocommerce_created_customer".
Did you add 'woocommerce_created_customer' to the $this->email_id_to_defer array?

@esachs4
Copy link

esachs4 commented Dec 12, 2024

Where should this code be placed?

@damiencarbery
Copy link
Author

Where should this code be placed?

@esachs4 : The code is a standalone plugin. You can download the raw file and upload it to wp-content/plugins and then activate it. I made a demo video at: https://www.damiencarbery.com/2018/10/how-to-use-my-code-snippets/

@Fattoresaimon
Copy link

Hello,
I have installed the plugin and i see on the log that the plugin start.
I also see the print of the function order_status_changed regarding the order detail
but i don't see any scheduled action and there is no delay on the mail.
I also installed the plugin action-scheduler.
Any idea?
Thank you!

@damiencarbery
Copy link
Author

Hello, I have installed the plugin and i see on the log that the plugin start. I also see the print of the function order_status_changed regarding the order detail but i don't see any scheduled action and there is no delay on the mail. I also installed the plugin action-scheduler. Any idea? Thank you!

Action Scheduler is part of WooCommerce so you do not need to install anything extra. I will test the plugin to verify that it works with the latest version of WooCommerce and then reply to you.

@Fattoresaimon
Copy link

Thank you for the quick reply.
Ok got it!
Another think, i'm using YayMail for customize the mail, can this effect?
I have tried without that plugin but no difference.

@damiencarbery
Copy link
Author

Thank you for the quick reply. Ok got it! Another think, i'm using YayMail for customize the mail, can this effect? I have tried without that plugin but no difference.

Yes, YayMail could have an effect. I will look at this.

I did try my code this morning and it works. I updated the code to 0.10.20250528 to make it easier to turn on debug mode.
dcwd-defer-email-times
This screenshot shows that the Order Complete email was delayed about 60 seconds (the defer time in debug mode is 60 secs).

@damiencarbery
Copy link
Author

Thank you for the quick reply. Ok got it! Another think, i'm using YayMail for customize the mail, can this effect? I have tried without that plugin but no difference.

I spent a lot of time today debugging the code but it actually works without having made any changes. I don't know why it wasn't working earlier today. Bizarre.

I did a little more code tidying and it is now version 0.11.20250528. Please try it out.
You can uncomment line 34 to enable debug mode and then look in your wp-content/debug.log or server error_log file for messages.
//$this->debug_mode = true;

@Fattoresaimon
Copy link

Hello,
Thank you so much for your effort.
I have tried the last version of the plugin: installed and activated.
But unfortunately still not working. YayMail is disabled
i have enabled the debug mode, but from the log i don't see any print from the plugin.
So i have added a couple of print :
image

image

and i see in the log
image

but for the rest no log.
Probably there is some other thing clashing with the plugin
Thank you

@damiencarbery
Copy link
Author

Hello, Thank you so much for your effort. I have tried the last version of the plugin: installed and activated. But unfortunately still not working. YayMail is disabled i have enabled the debug mode, but from the log i don't see any print from the plugin. So i have added a couple of print : image

image

and i see in the log image

but for the rest no log. Probably there is some other thing clashing with the plugin Thank you

Yes, I think that there must be something else clashing. When debug mode is enabled there should be a number of messages in the log.
Please email me at [email protected] so we can investigate this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment