How to automatically send renewal reminder emails for WooCommerce Subscriptions with PHP

If you use the WooCommerce Subscriptions plugin, you’ve probably noticed that no emails are sent to customers before automatic renewals. Let’s say we want to notify our customers about an upcoming renewal a week before it happens.

Main approach

We are going to:

  • get all active subscriptions,
  • select subscriptions that expire in a week,
  • mark these subscriptions (so we’d know that we already sent a reminder for them),
  • and finally, send a reminder email.

Also, to make all this work automatically, we will schedule a recurring action to run at midnight each day (with the Action Scheduler).

/**
 * Sends renewal reminder emails for WooCommerce Subscriptions. Fired on the `wpcodebook_process_all_subscriptions` action.
 *
 * @see https://wpcodebook.com/woocommerce-subscription-renewal-reminder/
 */
add_action( 'wpcodebook_process_all_subscriptions', function () {

	// Check for the "WooCommerce Subscriptions" plugin
	if ( ! function_exists( 'wcs_get_subscriptions' ) ) {
		return;
	}

	// User-defined vars
	$number_of_days  = 7;
	$meta_key_prefix = '_wpcodebook_subscription_renewal_reminder_';

	// Get all active subscriptions
	$subscriptions = wcs_get_subscriptions( array(
		'subscriptions_per_page' => -1,
		'subscription_status'    => 'wc-active',
	) );

	// Subscription loop
	foreach ( $subscriptions as $subscription ) {

		// Calculate renewal remaining time
		$time_next_payment = $subscription->get_time( 'next_payment' );
		$time_remaining    = ( $time_next_payment - time() );

		// Check subscription
		if (
			( $time_remaining > 0 && $time_remaining <= $number_of_days * DAY_IN_SECONDS ) &&
			! $subscription->get_meta( "{$meta_key_prefix}{$time_next_payment}" )
		) {

			// Update meta
			$subscription->update_meta_data( "{$meta_key_prefix}{$time_next_payment}", true );
			$subscription->save();

			// Send email
			$to      = $subscription->get_billing_email();
			$subject = sprintf( esc_html__( 'Subscription #%d Renewal Reminder' ),
				$subscription->get_id() );
			$message = sprintf( esc_html__( 'Renewal reminder for your subscription #%d: the subscription expires on %s.' ),
				$subscription->get_id(), date( 'Y-m-d', $time_next_payment ) );
			wc_mail( $to, $subject, $message );

		}

	}

} );

/**
 * Schedules an action with the hook `wpcodebook_process_all_subscriptions` to run at midnight each day.
 *
 * @see https://actionscheduler.org/usage/
 */
add_action( 'init', function () {
	if (
		function_exists( 'as_has_scheduled_action' ) &&
		false === as_has_scheduled_action( 'wpcodebook_process_all_subscriptions' )
	) {
		as_schedule_recurring_action(
			strtotime( 'tomorrow' ),
			DAY_IN_SECONDS,
			'wpcodebook_process_all_subscriptions',
			array(),
			'',
			true
		);
	}
} );

Optimization

If you have a lot of subscriptions in your shop, you may want to optimize the algorithm by sending the emails in the background.

First, we have to replace this line in our snippet:

wc_mail( $to, $subject, $message );

with this:

as_enqueue_async_action( 'wpcodebook_send_email', array( $to, $subject, $message ) );

And then add the wc_mail() callback function to our wpcodebook_send_email action hook:

/**
 * Adds the `wc_mail()` callback function to the `wpcodebook_send_email` action hook.
 */
add_action( 'wpcodebook_send_email', 'wc_mail', 10, 3 );

Another approach

Instead of firing an action directly, we can pre-schedule it:

/**
 * Pre-schedules renewal reminder emails for WooCommerce Subscriptions. Fired on the `wpcodebook_process_all_subscriptions` action.
 *
 * @see https://wpcodebook.com/woocommerce-subscription-renewal-reminder/
 *
 * @todo check if `( $subscription->get_time( 'next_payment' ) - $number_of_days * DAY_IN_SECONDS ) > time()`
 */
add_action( 'wpcodebook_process_all_subscriptions', function () {

	// Check for the "WooCommerce Subscriptions" plugin
	if ( ! function_exists( 'wcs_get_subscriptions' ) ) {
		return;
	}

	// User-defined vars
	$number_of_days = 7;

	// Get all active subscriptions
	$subscriptions = wcs_get_subscriptions( array(
		'subscriptions_per_page' => -1,
		'subscription_status'    => 'wc-active',
	) );

	// Subscription loop
	foreach ( $subscriptions as $subscription ) {

		// Schedule the email reminder using Action Scheduler
		if ( ! as_has_scheduled_action( 'wpcodebook_send_renewal_reminder_email', array( $subscription->get_id() ) ) ) {
			as_schedule_single_action( 
				( $subscription->get_time( 'next_payment' ) - $number_of_days * DAY_IN_SECONDS ),
				'wpcodebook_send_renewal_reminder_email', 
				array( $subscription->get_id() ) 
			);
		}

	}

} );

/**
 * Sends subscription renewal reminder email.
 *
 * @see https://wpcodebook.com/woocommerce-subscription-renewal-reminder/
 *
 * @todo check for `$subscription->has_status( 'active' )`
 */
add_action( 'wpcodebook_send_renewal_reminder_email', function ( $subscription_id ) {
	if ( ( $subscription = new WC_Subscription( $subscription_id ) ) ) {
		$to      = $subscription->get_billing_email();
		$subject = sprintf( esc_html__( 'Subscription #%d Renewal Reminder' ),
			$subscription->get_id() );
		$message = sprintf( esc_html__( 'Renewal reminder for your subscription #%d: the subscription expires on %s.' ),
			$subscription->get_id(), date( 'Y-m-d', $subscription->get_time( 'next_payment' ) ) );
		wc_mail( $to, $subject, $message );
	}
} );

Or even better – pre-schedule the action for a specific subscription when its status changes to “active”. As the subscription is temporarily put “on hold”, and then back to “active” on each renewal, the algorithm will work for all subsequent renewals as well:

/**
 * Schedule the email reminder (on subscription status updated to active) using Action Scheduler.
 *
 * @see https://wpcodebook.com/woocommerce-subscription-renewal-reminder/
 *
 * @todo check if `( $subscription->get_time( 'next_payment' ) - $number_of_days * DAY_IN_SECONDS ) > time()`
 */
add_action( 'woocommerce_subscription_status_active', function ( $subscription ) {
	if ( ! as_has_scheduled_action( 'wpcodebook_send_renewal_reminder_email', array( $subscription->get_id() ) ) ) {

		// User-defined vars
		$number_of_days = 7;

		// Schedule the email
		as_schedule_single_action(
			( $subscription->get_time( 'next_payment' ) - $number_of_days * DAY_IN_SECONDS ),
			'wpcodebook_send_renewal_reminder_email',
			array( $subscription->get_id() )
		);

	}
} );

Please note that this will not work for your old subscriptions.

Leave a Comment