WooCommerce comes with several email templates out of the box. It also provides you with a way to register your own emails which are going to be sent on some events. In this tutorial we will learn how to create a new custom WooCommerce Email.

The Main WooCommerce Email Class

To create a custom WooCommerce Email, you should understand the main WC_Email class. You can find it at includes/emails/class-wc-email.php or read it online on GitHub. This class is extending the WC_Settings_API class which is used for any settings related things in WooCommerce.

I won’t go into every possible detail of the WC_Email class (I leave that for the ambitious ones), but there are some of the attributes and methods you might want to know:

  • $title – Email title, used in admin,
  • $description – Email description, used in admin,
  • $heading – Email heading, used in templates,
  • $subject – Email subject,
  • $template_base – the absolute path to the folder where the templates are located,
  • $template_html – the relative path to the HTML email template,
  • $template_plain – the relative path to the plain email template,
  • $recipient – a string of recipient emails,
  • $object – The object for which is the email. This can be a WC_Product, WC_Customer or something else (even our own),
  • $customer_email – if this email is sent to customers.
  • send() – this method is used for sending the email (check the parameters),
  • init_form_fields() – this method is used to register form fields.

Creating a Custom WooCommerce Email

Let’s now jump right into creating a custom WooCommerce Email. If you’re looking to implement this into your own plugin, just replace the paths for your own plugin. I’ll create a new plugin folder custom-wc-email and also a file inside of it custom-wc-email.php.

WooCommerce provides a lot of Customer Emails but your site only sends an email to the administrator or the provided recipient when an order is canceled. Such email is not sent to customers. We will create a custom WooCommerce email that will also notify the customer when an order was canceled.

Let’s create additional folders in our plugin. Create two folders emails and templates. We will also go one more step further and inside of the folder templates create a new folder emails. Then inside of that folder, create another one plain.

The folder templates will hold email templates inside of the subfolder emails. Plain templates will be located under plain.

Registering a Custom WooCommerce Email

To register a custom WooCommerce Email, we will create the main plugin class where we will hook into the WooCommerce emails and define the absolute path to the plugin folder.

For now, your site will not work if you have activated this plugin. That’s because we have yet to create the custom email.

Define the Custom WooCommerce Email

Create a file class-wc-customer-cancel-order.php. This file will contain all the code we will create here. Let’s define the __construct method first.

In the constructor method, we define the default values to attributes. We then hook on two actions where the order can be cancelled. When the order is cancelled, we will call the method trigger.

We are also setting the relative and absolute paths to templates.

Trigger Method

The trigger method is not an inherited method and we need to define it ourselves. This method will accept the Order ID which is passed through the action hooks. We will then get the order and use the billing email as a recipient email.

Overriding the Content Methods

We also need to override the content methods which are used to get the HTML and plain templates.

We are using the function wc_get_template_html where we pass the relative path to the email templates. We also pass the absolute path to the folder where the path to email templates is located. Since the email templates that we are using are copied from the admin cancelled email, we are also passing the required parameters.


The last thing is to define the templates that are going to be used for creating the email content. The first template will be an HTML template. Create a file wc-customer-cancelled-order.php inside templates/emails/.

With this hooks, WooCommerce will fill the required data since the hooks are the same as for the WooCommerce Admin Cancelled email. The plain template will go into templates/emails/plain. We will use the same file name wc-customer-cancelled-order.php.

The Code

Here is the complete code which you can download and test it on your own.

This part is available only to the members. If you want to become a member and support my work go to this link and subscribe: Become a Member


With one custom WooCommerce email (or more) you can enhance the user experience of your plugin. This can be emails on subscriptions, bookings, rentals or anything else. Some solutions won’t require such emails, but with this knowledge you can now enhance the experience of your own clients also or even suggest such custom emails where needed.

Have you ever tried working with custom WooCommerce emails? Do you maybe have an idea where such emails could be also used?

Become a Sponsor

Posted by Igor Benic

Web Developer who mainly uses WordPress for projects. Working on various project through Codeable & Toptal. Author of several ebooks at https://leanpub.com/u/igorbenic.


  1. Hello, I followed this guide exactly but cannot get it to work. I didn’t receive an email notice when my order is cancelled. I can see and edit “Cancelled Order to Customer” from WP Admin > WooCommerce > Settings > Emails. I tried disabling the default Cancelled Order notice sent to admins but nothing.

    Has updates to WooCommerce caused a change in how a custom email could be made?


    1. It should work without issues. This might happen if you’re sending to and receiving from the same email (SMTP). Could that be the case?


  2. What if I need to send the email only to the recipients which are added in the backend.

    Because I created a new custom email which needs to be triggered on a form submission. So, I need to send an email notification to the shop manager with form data.


    1. You will retrieve the recipients in the trigger method.


  3. Hi. i suppose it’s “add_filter” in the constructor instead of “add_action” since its a filter and not a hook i.e. add_filter( ‘woocommerce_email_classes’, array( $this, ‘register_email’ ), 90, 1 );


  4. Thanks for the guide! I liked the idea of creating the Custom_WC_Email class to register custom emails but then letting those emails define their own spec inside the WC_Email extension methods.

    One thing to note is that the “woocommerce_email_classes” hook will not run without the core WC_Emails classes loading. If you aren’t doing something that isn’t already a usecase for transactional emails in WC, the class in this example that extends WC_Email won’t load either as WC_Emails is often lazy-loaded. (See https://github.com/woocommerce/woocommerce/issues/19572)

    For example, in our case we are sending a custom email leveraging WC_Email that isn’t even tied to an order or transaction, just a UX step in our site.
    We are able to load WC_Emails by calling WC()->mailer() which will then run the “woocommerce_email_classes” hook.

    Then we can immediately trigger the custom email by running:
    do_action(‘some_custom_email_hook’, $args);

    This could be may more robust by some autoloading helper that would call WC()->mailer() if needed and only when needed by checking if the necessary classes exist at runtime.


    1. Thanks for your answer, the addition of WC()->mailer() was very important in my script, without this call, the do_action() remained empty during the execution of my email


  5. Hello,

    The snippet labelled as “class.php” – does this get placed in plugin directory as class.php? or is this to be placed in funtions.php?

    I see the first snippet which is supposed to be placed in file “custom-wc-email.php” is labelled as “plugin.php” in github snippet, so this futher adds to the confusion. Can you please clarify?


    1. Hi Michael, don’t mind the snippet names. Those are just to keep me organized when writing the tutorials. You can put the whole code in the custom-wc-email.php file.


      1. Hi,

        Does the code mentioned in email-trigger.php ALSO go into custom-wc-email.php file?

        Following this article is very confusing!


  6. Hi, thank you for the guide. However, I’ve the following error :
    ERROR Please set some HTML first

    I don’t understand because I’ve the same files as you.

    Do you see an issu for that ?

    Thank you


  7. Hi,

    add_action( ‘woocommerce_email_classes’, array( $this, ‘register_email’ ), 90, 1 );

    …. I assume this should be an add_filter instead?


    1. Yes, that should be an add_fitler. Thanks!


  8. Hi,

    While the effort put here is appreciated, it would have been better if this article was written in a manner where newbies could ACTUALLY use it!!

    Some observations:
    1.People have pointed out about the add_action to be renamed to add_filter. While you have acknowledged it, the gist has not been updated.
    2. Create two folders emails and templates. We will also go one more step further and inside of the folder templates create a new folder emails. Then inside of that folder, create another one plain. -> This does not make sense. When I follow this EXACTLY, I have an EMPTY emails folder alongside templates folder
    3. Where do email-content.php & email-trigger.php codes go? In which file? I have currently pasted them into custom-wc-email.php

    Now when I try to activate the plugin, I get:
    Fatal error: Uncaught Error: Class ‘WC_Email’ not found in [path]\wp-content\plugins\custom-wc-email\custom-wc-email.php:36 Stack trace: #0 [path]\wp-admin\includes\plugin.php(2223): include() #1 [path]\wp-admin\plugins.php(175): plugin_sandbox_scrape(‘custom-wc-email…’) #2 {main} thrown in [path]\wp-content\plugins\custom-wc-email\custom-wc-email.php on line 36

    I do not even know where to begin troubleshooting this.

    I would request you to fix your article to make it usable for newbies!

    Thanks a lot!


    1. Hi KoolPal, sorry if reading this tutorial was confusing to you.

      1. Fixed it. I thought I had it done before but I guess I missed one line.
      2. By the end of the tutorial we put our email templates inside of those folders as I have stated in the tutorial. The sentence after the one you have quoted here explains that.
      3. At the beginning of that chapter, I’ve stated “Create a file class-wc-customer-cancel-order.php. This file will contain all the code we will create here.”. Those names are just for me so I can follow the code I write in the tutorial (because the gist won’t be created without a name).

      In case the code went into a different file/folder I would say so.

      WC_Email is a class provided by WooCommerce. Since we are defining the email inside of the WooCommerce filter, it should be there. In case the error does persist even though WooCommerce is active, try changing the plugin name/folder to woocommerce-custom-email. That might load it after WooCommerce and mitigate the error you have.

      I try to make the tutorials easy to follow by providing the same class wrappers and such with the details such as “….other code here” and similar so you know where to put the next code.

      I’ll try to make such tutorials even easier to follow in the future. Thanks for the feedback.


  9. Hi! Thanks for this amazing tutorial. I’ve one question. My product has attribute named “Starting date” and i need to send custom email (reminder email) e.g. one day before the attribute value. Is it possible to do with this script? How can I run this script exactly one day before attribute “starting date” value?


    1. Hi Matthew, this Custom Email can be used for such scenario. You would need to create a CRON job (you can use WordPress functions, most likely: https://developer.wordpress.org/reference/functions/wp_schedule_single_event/).

      You would need to register the ‘trigger’ method on an action, for example, when an event is scheduled using wp_schedule_single_event, there is a $hook variable. On that, you can set an action name and use that inside of add_action to hook the trigger method.

      Then, when the payment is complete, check the product attribute and schedule the event to fire the hook 1 day before that date and provide order_id or what info you need inside of the $args variable. Those should be received inside of trigger method which you can then use to create the email content you need.


  10. I am stuck at schedule send an email. I am using a custom email. however I want to send this custom email when woo commerce payment is complete. so I am using this hook

    add_action(‘woocommerce_payment_complete’,array( $this, ‘trigger’ ));

    it is working fine, but I want this email to send after 5 min. I mean the time interval.

    wp_schedule_single_event( time() + 60, ‘hook’);

    this function will work for this. So Should I use this function inside trigger method before sending email? normally wp_email work with schedule email. but in this custom email class, I want to send this custom email as a scheduled email when payment has been completed.



    1. You would need to hook on woocommerce_payment_complete and create an event using wp_schedule_single_event. Then when you define the ‘hook’ (for example: ‘woocommerce_payment_complete_send_custom_email’), you also pass the same data that woocommerce_payment_complete passes. For example: wp_schedule_single_event( time() + 5 * MINUTE_IN_SECONDS, ‘woocommerce_payment_complete_send_custom_email’, array( $order_id ) );

      Then inside of the email class, add add_action( 'woocommerce_payment_complete_send_custom_email', array( $this, 'trigger' ) ); instead of using woocommerce_payment_complete. Then inside of the trigger method, the first arguments should be also $order_id so you should be fine with using it.

      Be aware that using the WP CRON will work only if users are loading your site. So, this email could be sent 1 hour after if no one has loaded your site within 1 hours of the payment unless you disable WP CRON and create a CRON on your server to trigger those. Article on it: https://kinsta.com/knowledgebase/disable-wp-cron/


  11. Thank you so much, Igor. This helped me a lot with my personal project. 🙂


  12. Hello,
    How can I trigger, on Custom Order Status Change to “wc-order_shipped” ?


    1. I’ve been fighting with this for days.
      I finally found something that works for me.

      add to the Custom_WC_Email class
      a new function
      * Add shipped custom status to email actions list.
      public function register_email_action( $email_actions ) {
      $email_actions[] = ‘woocommerce_order_status_shipped’;

      return $email_actions;

      add to the __construct of Custom_WC_Email
      // Register the custom status for actions.
      add_filter( ‘woocommerce_email_actions’, array( $this, ‘register_email_action’ ) );

      finally update WC_Customer_Cancel_Order extends WC_Email:
      // Action to which we hook onto to send the email.
      add_action( ‘woocommerce_order_status_pending_to_cancelled_notification’, array( $this, ‘trigger’ ) );
      // Action to which we hook onto to send the email.
      add_action( ‘woocommerce_order_status_shipped_notification’, array( $this, ‘trigger’ ), 10, 2 );

      Hope that helps. It’s taken hours and hours of searching.


  13. Thanks Igor! Really helped me sort out my Website mess!


  14. Thanks for the Info Igor!


  15. Did he also send a message about a new order dynamically to the product author, WooCommerce?


  16. Hi, Instead of {blogname}, I’d like to show {order_number} on the subject of the email. Would you please help me with that?


    1. Hi Milad, you should check with WooCommerce documentation. I think that should be possible by default.

      But if not, you would get the $subject = $this->subject() and then you can use str_replace( '{order_number}', $order->get_order_number(), $subject) or something similar.


  17. ¡Hi Igor! How can hide products prices? I tried it but I can’t do it 🙁

    Thanks! Regards.


    1. Hi Rafa, how do you want to hide the prices? And where?


  18. Hi Igor,

    I have used this and a js script to trigger the email on css class click. I have multiple classes like this, each with it’s own template. I want to pass the data from the ajax call to my php file. Do you know how I could do that?

    Code – https://stackoverflow.com/questions/35018177/woocommerce-trigger-custom-email-via-ajax


    1. Hi, WooCommerce emails are usually triggered by a hook, so you could pass data through the AJAX using the data parameter in jQuery AJAX call. Then you can, based on that data, call a hook do_action( ‘my_hook_that_will_trigger_email’, $data1, $data2, $data3… ) and then inside of the Email class use add_action( 'my_hook_that_will_trigger_email', array( $this, 'method_to_call' ), 10, 3 ) where 3 is the number of data we accept in method.

      Another way would be to get the instantiated email from the WC emails and call directly the method with those data passed to it.


  19. Thank you for your sharing.

    I will use your code to a premium plugin I’m currently building


  20. geoffrey olivier October 8, 2021 at 10:57 am

    Hello, I download your plugin but, I have lot of variable undefined.
    for exemple in wc-customer-cancelled-order.php -> $email_heading or $sent_to_admin and lot of other is undefined. Can you tell me why please ?


    1. Hi Geoffrey where do you see those errors? In the email sent by WooCommerce?
      Those variables are provided by WooCommerce. If you check WooCommerce templates, you’ll see the same thing, your editor will say those variables are undefined. But they are not at the time of running that email since those are provided by WooCommerce.


      1. geoffrey olivier October 8, 2021 at 2:38 pm

        Ok thanks, just to be sure. I ue PhpStorm, maybe I miss an plugin.


  21. Hello, the other emails, not custom, are no longer being sent, are they normal? Otherwise great tutorial, it works.


    1. Shouldn’t happen. Might be something with how the emails are being sent that could stop them. Not sure.


  22. Hi,
    your tutorial is very useful, I created a custom plugin and added something like your code to my plugin and email wasn’t sent, but if I copy the mail template to the theme woocommerce template folder, email sending? Do you think what’s the problem? i used below code
    $this->template_html = ’emails/next-coupon-email.php’;
    $this->template_plain = ’emails/plain/next-coupon-email.php’;
    $this->template_base = ATA_DIS_DIR . ‘include/admin/’;


  23. Hi,
    your tutorial is very useful, I created a custom plugin and added something like your code to my plugin and email wasn’t sent, but if I copy the mail template to the theme woocommerce template folder, email sending? Do you think what’s the problem? i used below code :
    $this->template_html = ’emails/next-coupon-email.php’;
    $this->template_plain = ’emails/plain/next-coupon-email.php’;
    $this->template_base = ATA_DIS_DIR . ‘include/admin/’;


    1. As I don’t know your whole folder structure, can’t be sure. It might be that the template base is pointing elsewhere so emails are not found?


  24. Thanks for sharing, it’s really helpful


  25. Thank you for sharing


Leave a reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.