Send custom WooCommerce order emails with a transactional template

WooCommerce includes a complete email notification system built on top of the WC_Email base class. Every transactional email — new order notification, order processing, order complete, customer invoice — is a PHP class that extends WC_Email and defines the trigger, subject, heading, and template. Creating a custom email means extending the same class, which gives you the full WooCommerce email infrastructure: the branded HTML wrapper template, variable substitution, the plain-text version, and the admin customisation panel in WooCommerce → Settings → Emails where store owners can customise subject and heading without code. The most common use case is sending a custom notification when an order reaches a specific custom status, or including additional information in an existing email. To add a custom email to WooCommerce, you filter woocommerce_email_classes to register your class and hook your trigger method onto the relevant action — typically an woocommerce_order_status_{old}_to_{new} hook. The email template is an HTML file placed in woocommerce/emails/ inside your theme or plugin, following the WooCommerce template structure. The WC_Email base class handles sending via wp_mail(), applies the store’s email header and footer templates, and provides helper methods like format_string() for placeholder substitution. Combine this with the custom order status guide and the checkout fields guide to build a complete custom order workflow.

Problem: You need to send a custom styled transactional email to the customer when an order reaches a specific status, using the WooCommerce email template system rather than raw wp_mail().

Solution: Extend WC_Email, register the class via woocommerce_email_classes, and trigger it on the target order status hook:

// Register custom email class
add_filter( 'woocommerce_email_classes', 'ha_register_custom_email' );

function ha_register_custom_email( $email_classes ) {
    require_once get_stylesheet_directory() . '/inc/class-ha-ready-to-ship-email.php';
    $email_classes['HA_Ready_To_Ship_Email'] = new HA_Ready_To_Ship_Email();
    return $email_classes;
}

// inc/class-ha-ready-to-ship-email.php
if ( ! defined( 'ABSPATH' ) ) exit;

class HA_Ready_To_Ship_Email extends WC_Email {

    public function __construct() {
        $this->id             = 'ha_ready_to_ship';
        $this->customer_email = true;             // Send to customer
        $this->title          = 'Ready to Ship';
        $this->description    = 'Sent when an order moves to the "Ready to Ship" status.';
        $this->subject        = 'Your order #{order_number} is ready to ship!';
        $this->heading        = 'Great news — your order is ready!';

        // Template path inside theme: woocommerce/emails/ha-ready-to-ship.php
        $this->template_html  = 'emails/ha-ready-to-ship.php';
        $this->template_plain = 'emails/plain/ha-ready-to-ship.php';

        // Trigger on order status change to 'ready-to-ship'
        add_action( 'woocommerce_order_status_processing_to_ready-to-ship_notification', [ $this, 'trigger' ], 10, 2 );

        parent::__construct();
    }

    public function trigger( $order_id, $order = false ) {
        $this->setup_locale();

        $order = $order instanceof WC_Order ? $order : wc_get_order( $order_id );
        if ( ! $order ) return;

        $this->object                  = $order;
        $this->recipient               = $this->object->get_billing_email();
        $this->placeholders['{order_number}'] = $order->get_order_number();
        $this->placeholders['{order_date}']   = wc_format_datetime( $order->get_date_created() );

        if ( $this->is_enabled() && $this->get_recipient() ) {
            $this->send(
                $this->get_recipient(),
                $this->get_subject(),
                $this->get_content(),
                $this->get_headers(),
                $this->get_attachments()
            );
        }

        $this->restore_locale();
    }

    public function get_content_html() {
        return wc_get_template_html(
            $this->template_html,
            [ 'order' => $this->object, 'email_heading' => $this->get_heading(), 'email' => $this ],
            '',
            $this->template_base
        );
    }

    public function get_content_plain() {
        return wc_get_template_html(
            $this->template_plain,
            [ 'order' => $this->object, 'email_heading' => $this->get_heading(), 'email' => $this ],
            '',
            $this->template_base
        );
    }
}

NOTE: The email template file at woocommerce/emails/ha-ready-to-ship.php in your theme must start with <?php do_action( ‘woocommerce_email_header’, $email_heading, $email ); ?> and end with <?php do_action( ‘woocommerce_email_footer’, $email ); ?> to include the WooCommerce branded header and footer. After registering the class, go to WooCommerce → Settings → Emails to confirm the email appears in the list — you can preview and customise the subject and heading there without touching code.