A custom email
The standard emails are not always enough. The lesson on statuses introduced the Ready status — the customer hears nothing about it. Your own email is a class extending WC_Email: it gets a row in the settings, a subject, a template and the styling shared by all shop emails.
The email class
Create the file includes/class-acme-email-ready.php:
// wp-content/plugins/acme-shop/includes/class-acme-email-ready.php
class Acme_Email_Ready extends WC_Email
{
public function __construct()
{
$this->id = 'acme_ready';
$this->customer_email = true;
$this->title = 'Order ready';
$this->description = 'Sent to the customer when the order has been made.';
$this->template_html = 'emails/acme-ready.php';
$this->template_base = dirname(__DIR__) . '/templates/';
$this->placeholders = ['{order_number}' => ''];
parent::__construct();
}
public function get_default_subject(): string
{
return 'Order #{order_number} is ready';
}
public function get_default_heading(): string
{
return 'Your order is ready';
}
}The properties set the ID, the name in the admin panel and the template path. customer_email means the email goes to the customer. The {order_number} placeholder in the subject is replaced with the order number.
Sending
Add two methods to the class:
// wp-content/plugins/acme-shop/includes/class-acme-email-ready.php
public function trigger(int $orderId): void
{
$this->object = wc_get_order($orderId);
if (!$this->object || !$this->is_enabled()) {
return;
}
$this->recipient = $this->object->get_billing_email();
$this->placeholders['{order_number}'] = $this->object->get_order_number();
$this->send(
$this->get_recipient(),
$this->get_subject(),
$this->get_content(),
$this->get_headers(),
$this->get_attachments()
);
}
public function get_content_html(): string
{
return wc_get_template_html(
$this->template_html,
['order' => $this->object, 'email_heading' => $this->get_heading(), 'email' => $this],
'',
$this->template_base
);
}trigger() finds the order, sets the recipient and sends the email. get_content_html() builds the email body from the template and passes variables into it.
The template
Create the file templates/emails/acme-ready.php:
<!-- wp-content/plugins/acme-shop/templates/emails/acme-ready.php -->
<?php do_action('woocommerce_email_header', $email_heading, $email); ?>
<p>Hello, <?php echo esc_html($order->get_billing_first_name()); ?>!</p>
<p>Order #<?php echo esc_html($order->get_order_number()); ?> has been made and will be handed to delivery tomorrow.</p>
<?php do_action('woocommerce_email_footer', $email); ?>The header and footer hooks print the shop's common styling: the logo, colors, the signature. The real file starts with <?php.
Registration
// wp-content/plugins/acme-shop/includes/emails.php
add_filter('woocommerce_email_classes', function (array $emails): array {
require_once __DIR__ . '/class-acme-email-ready.php';
$emails['Acme_Email_Ready'] = new Acme_Email_Ready();
return $emails;
});The class file is included inside the filter: the WC_Email class is loaded only by that moment. Open WooCommerce → Settings → Emails — an Order ready row has appeared in the list, where the email can be switched off or its subject changed.
Triggering on a status
// wp-content/plugins/acme-shop/includes/emails.php
add_action('woocommerce_order_status_ready', function (int $orderId) {
$emails = WC()->mailer()->get_emails();
if (isset($emails['Acme_Email_Ready'])) {
$emails['Acme_Email_Ready']->trigger($orderId);
}
});WC()->mailer() loads all shop emails. Move an order to the Ready status in the admin panel: the customer receives an email with the subject Order #1024 is ready.
What an email consists of
| Part | Where |
|---|---|
| The class | includes/class-acme-email-ready.php |
| The template | templates/emails/acme-ready.php |
| Registration | The woocommerce_email_classes filter |
| Triggering | An order status hook and the trigger() method |
Common mistake
The class file is included in the plugin's main file next to the other require lines. At that moment the WC_Email class does not exist yet, and the site crashes with a class not found error. Include the email class only inside the woocommerce_email_classes filter.