How to Automatically Send Monthly Coupons to WooCommerce Members

Recurring perks keep members coming back. With a short snippet, Loyalty Program for WooCommerce can issue a fresh coupon to selected tiers on the first day of every month: VIPs receive a NT$100 coupon, VVIPs a NT$300 coupon, each valid for 30 days. Coupons are created through the plugin’s own coupon service so they tie cleanly into the existing loyalty coupon system.

Find Your Rank IDs

The snippet below references ranks by numeric ID rather than name, so the same code keeps working if you rename a rank later. To find a rank’s ID, go to Loyalty Program > Ranks in WordPress admin, click Edit Rank on the row you want, and read the ID from the modal header: it appears as Edit Rank (#2). Use that number wherever the snippet shows a comment like // VIP or // VVIP.

How the Snippet Works

The example hooks into wpgens_loyalty_daily_cron and self-throttles to one run per month by checking current_time('j') !== '1'. Each user gets a single coupon per calendar month thanks to a _wpgl_monthly_coupon_issued marker keyed by year and month, so the snippet is safe to run repeatedly without issuing duplicates.

Users on a given tier are found by reading the cached _wpgens_loyalty_rank_id user meta the plugin maintains. An optional email handler at the bottom of the file delivers the coupon to the customer immediately; remove it if you only want the coupon to appear inside their account.

The Snippet

<?php
/**
 * Example: Monthly automatic coupon issuance for specific membership tiers
 *
 * Automatically issue a coupon every month to users in selected tiers. For
 * example:
 *  - VIP members  -> NT$100 coupon each month
 *  - VVIP members -> NT$300 coupon each month
 *
 * We piggyback on the plugin's existing `wpgens_loyalty_daily_cron` hook,
 * and run our logic only on the first day of each month. Each user gets one
 * coupon per month thanks to a per-user "last issued" marker.
 *
 * Rank IDs are visible in the admin: Loyalty Program -> Ranks -> Edit Rank.
 * The modal header shows the rank ID, e.g. "Edit Rank (#2)".
 *
 * Copy this snippet to your theme's functions.php or a small custom plugin.
 * Requires: Loyalty Program plugin (Points + Ranks modules active), WooCommerce.
 */

if (!defined('ABSPATH')) {
	exit;
}

add_action('wpgens_loyalty_daily_cron', 'wpgl_issue_monthly_tier_coupons');

function wpgl_issue_monthly_tier_coupons()
{
	// Run only on the first day of the month.
	if (current_time('j') !== '1') {
		return;
	}

	if (!class_exists('WPGL_Ranks_Core') || !class_exists('WPGL_Coupon_Service')) {
		return;
	}

	// Map of rank ID => monthly coupon definition. Edit IDs to match your setup.
	$tier_monthly_coupons = [
		2 => [ // VIP
			'amount'        => 100,
			'discount_type' => 'fixed_cart',
			'expires_days'  => 30,
		],
		3 => [ // VVIP
			'amount'        => 300,
			'discount_type' => 'fixed_cart',
			'expires_days'  => 30,
		],
	];

	$year_month = current_time('Y-m');
	$meta_key   = '_wpgl_monthly_coupon_issued';

	foreach ($tier_monthly_coupons as $rank_id => $coupon_args) {
		$users = wpgl_get_users_by_rank_id((int) $rank_id);

		foreach ($users as $user_id) {
			// One coupon per user per calendar month.
			$last_issued = get_user_meta($user_id, $meta_key, true);
			if ($last_issued === $year_month) {
				continue;
			}

			$args = wp_parse_args($coupon_args, [
				'amount'        => 0,
				'discount_type' => 'fixed_cart',
				'expires_days'  => 30,
			]);

			$expires = strtotime('+' . (int) $args['expires_days'] . ' days');

			$coupon = WPGL_Coupon_Service::create_coupon([
				'user_id'              => (int) $user_id,
				'amount'               => (float) $args['amount'],
				'discount_type'        => $args['discount_type'],
				'description'          => sprintf('Monthly reward for rank #%d (%s)', $rank_id, $year_month),
				'usage_limit'          => 1,
				'usage_limit_per_user' => 1,
				'individual_use'       => false,
				'date_expires'         => $expires ? gmdate('Y-m-d H:i:s', $expires) : null,
				'prefix'               => 'monthly',
				'loyalty_meta'         => [
					'_wpgl_monthly_reward_period'  => $year_month,
					'_wpgl_monthly_reward_rank_id' => (int) $rank_id,
				],
			]);

			if (is_wp_error($coupon)) {
				continue;
			}

			update_user_meta($user_id, $meta_key, $year_month);

			/**
			 * Fires after a monthly tier coupon is issued.
			 *
			 * @param int       $user_id
			 * @param int       $rank_id
			 * @param WC_Coupon $coupon
			 * @param string    $year_month
			 */
			do_action('wpgl_monthly_tier_coupon_issued', $user_id, (int) $rank_id, $coupon, $year_month);
		}
	}
}

/**
 * Look up all user IDs that currently sit on a given rank ID.
 *
 * Uses the cached `_wpgens_loyalty_rank_id` meta the plugin writes when
 * computing ranks. New users without cached meta will pick up the coupon next
 * month after their first rank evaluation.
 *
 * @param int $rank_id
 * @return int[]
 */
function wpgl_get_users_by_rank_id($rank_id)
{
	global $wpdb;

	// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- monthly cron, no cache benefit
	$user_ids = $wpdb->get_col($wpdb->prepare(
		"SELECT user_id FROM {$wpdb->usermeta}
		 WHERE meta_key = '_wpgens_loyalty_rank_id' AND meta_value = %d",
		$rank_id
	));

	return array_map('intval', $user_ids);
}

/**
 * Optional: also email the coupon to the customer immediately.
 * Remove this block if you only want the coupon to appear in their account.
 */
add_action('wpgl_monthly_tier_coupon_issued', 'wpgl_email_monthly_tier_coupon', 10, 4);

function wpgl_email_monthly_tier_coupon($user_id, $rank_id, $coupon, $year_month)
{
	$user = get_userdata($user_id);
	if (!$user || empty($user->user_email)) {
		return;
	}

	$subject = sprintf(
		/* translators: 1: site name */
		__('Your monthly %s reward is here', 'wpgens-loyalty-program'),
		get_bloginfo('name')
	);

	$message = sprintf(
		/* translators: 1: first name or display name, 2: coupon code, 3: amount, 4: expiry date */
		__("Hi %1\$s,\n\nAs a thank you for being a valued member, here's your monthly coupon: %2\$s\n\nValue: %3\$s\nExpires: %4\$s\n\nUse it at checkout on your next order.", 'wpgens-loyalty-program'),
		$user->first_name ?: $user->display_name,
		$coupon->get_code(),
		wp_strip_all_tags(wc_price($coupon->get_amount())),
		$coupon->get_date_expires() ? $coupon->get_date_expires()->date_i18n(get_option('date_format')) : __('No expiry', 'wpgens-loyalty-program')
	);

	wp_mail($user->user_email, $subject, $message);
}

Where to Put This Snippet

Drop the snippet into a small site-specific plugin under wp-content/mu-plugins/ so it survives theme changes, or paste it into your child theme’s functions.php. It is also available in the plugin folder under examples/.

To verify, temporarily change the current_time('j') !== '1' check to !== '0' (or comment it out) and run wp cron event run wpgens_loyalty_daily_cron. Confirm coupons appear in test users’ accounts and that no duplicates are issued on a second run.

Browse our plugins

Lightweight WooCommerce plugins built for speed. No bloat, no frameworks -- just clean code that works.

View all plugins
Stay in the loop

Get notified when we launch new plugins. No spam, just product updates.