WooCommerce sends a well-defined set of transactional emails out of the box — order confirmations, shipping notifications, password resets — but almost every store eventually needs a custom email trigger. Maybe you want to notify the store owner when a customer leaves a review, send a coupon code seven days after a first purchase, or alert the warehouse team when a specific product goes on back-order. WooCommerce’s email system is built on top of WordPress’s action hook architecture, which means you can trigger, extend, or replace any email with a few lines of code. The cleanest approach is to extend the WC_Email class and register your email in the woocommerce_email_classes filter — this integrates fully with the WooCommerce email settings page where administrators can enable, disable, and customise the template. For simpler cases where you just want to send a plain notification on a specific event, wc_mail() or WordPress’s own wp_mail() called inside a hook callback is all you need. Pair this with WP-Cron for delayed or scheduled emails. When customising the email template HTML, always test across multiple email clients — Outlook, Gmail, and Apple Mail each render CSS differently. The example below shows a minimal custom WooCommerce email that fires when an order status changes to a custom status, along with a simpler wp_mail() version for quick notifications.
Problem: You need WooCommerce to send a custom email notification on a specific event (order status change, new review, stock alert) that is not covered by the default emails.
Solution: Add the following code to your functions.php file or a custom plugin:
/**
* Simple approach: send a plain email when an order is marked "on-hold".
* Hook: woocommerce_order_status_on-hold
*/
add_action( 'woocommerce_order_status_on-hold', 'helloadmin_notify_on_hold', 10, 1 );
function helloadmin_notify_on_hold( $order_id ) {
$order = wc_get_order( $order_id );
if ( ! $order ) {
return;
}
$to = get_option( 'admin_email' );
$subject = sprintf( 'Order #%d is on hold', $order->get_id() );
$message = sprintf(
"Order #%d from %s %s is waiting for payment confirmation.
Total: %s
Edit: %s",
$order->get_id(),
$order->get_billing_first_name(),
$order->get_billing_last_name(),
$order->get_formatted_order_total(),
admin_url( 'post.php?post=' . $order->get_id() . '&action=edit' )
);
$headers = [ 'Content-Type: text/plain; charset=UTF-8' ];
wp_mail( $to, $subject, $message, $headers );
}
/**
* Send a customer thank-you email 7 days after order completion using WP-Cron.
*/
add_action( 'woocommerce_order_status_completed', 'helloadmin_schedule_followup', 10, 1 );
function helloadmin_schedule_followup( $order_id ) {
if ( ! wp_next_scheduled( 'helloadmin_send_followup', [ $order_id ] ) ) {
wp_schedule_single_event( time() + 7 * DAY_IN_SECONDS, 'helloadmin_send_followup', [ $order_id ] );
}
}
add_action( 'helloadmin_send_followup', 'helloadmin_send_followup_email' );
function helloadmin_send_followup_email( $order_id ) {
$order = wc_get_order( $order_id );
if ( ! $order ) {
return;
}
$to = $order->get_billing_email();
$subject = 'Thank you for your purchase!';
$message = 'Hi ' . $order->get_billing_first_name() . ', we hope you are enjoying your order. Leave a review and get 10% off your next purchase!';
wp_mail( $to, $subject, $message );
}
NOTE: Always use wp_mail() rather than PHP’s native mail() function in WordPress — it respects the wp_mail filter, works with SMTP plugins like WP Mail SMTP, and handles character encoding correctly. When sending HTML emails, add a Content-Type: text/html; charset=UTF-8 header and wrap your content in a complete HTML document with inline CSS, since most email clients strip <style> blocks.