Skip to content

Instantly share code, notes, and snippets.

@damiencarbery
Last active January 9, 2024 15:54
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.8
*/
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;
// 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->default_defer_time = 600; // Defer for 600 seconds (10 minutes).
// 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_new_customer_note' => $this->default_defer_time,
'woocommerce_order_status_completed' => $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_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.
);
$this->init();
}
// Set up WordPress specfic actions.
public function init() {
// 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 );
// DEBUG: Add the order modification time and current time to prove that the email was intentionally delayed.
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 ) {
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 ) {
//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 ) );
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;
}
//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() ) {
//error_log( 'send_deferred_woocommerce_email for order: ' . $order_id );
// 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 );
}
// 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 ( '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();
@djm56
Copy link

djm56 commented May 19, 2020

Thanks for this code I was desperately looking for something like this you saved my life. I needed it for on-hold mails. Here is the code I used so it pulls the correct email.
'woocommerce_order_status_on-hold' => array('WC_Email_Customer_On_Hold_Order', $this->default_defer_time), 'woocommerce_order_status_pending_to_on-hold' => array('WC_Email_Customer_On_Hold_Order', $this->default_defer_time), // New order 'woocommerce_order_status_cancelled_to_on-hold' => array('WC_Email_Customer_On_Hold_Order', $this->default_defer_time), // New order 'woocommerce_order_status_pending_to_on-hold' => array('WC_Email_Customer_On_Hold_Order', $this->default_defer_time), // New order

@damiencarbery
Copy link
Author

@djm56 - Thanks for the additional code. I have updated the gist to include these. I'm delighted that you found it useful - it took me a long while to figure it all out.

@KitKatCarson
Copy link

Thanks for sharing your gist! This helped me a ton!

@wuspum
Copy link

wuspum commented Oct 28, 2021

Has anyone experienced "random" execution success of this script? Sometimes event is scheduled and sometimes not. Any ideas?

@damiencarbery
Copy link
Author

damiencarbery commented Oct 28, 2021

Has anyone experienced "random" execution success of this script? Sometimes event is scheduled and sometimes not. Any ideas?

I suggest uncommenting the error_log calls so that you can see whether the script is running as expected.

To see whether the event is scheduled I use Advanced Cron Manager.

@damiencarbery
Copy link
Author

Has anyone experienced "random" execution success of this script? Sometimes event is scheduled and sometimes not. Any ideas?

Maybe I should look into using Action Scheduler. WooCommerce uses it extensively and it can log events.

@eduardgermoniy
Copy link

Will this work for registration notification?

@damiencarbery
Copy link
Author

Will this work for registration notification?
If that plugin uses the WooCommerce email system then it could be defer those emails by adding the email ID to the $this->email_id_to_defer array.
Where could I download that plugin to experiment?

@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?

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