Add a custom note to your WooCommerce emails. Use actions instead of changing templates. It can be simple or complex.
WooCommerce sends emails to customers and site admin at multiple points during the order process. Just like WooCommerce pages, the content of these emails can be modified with actions, filters or by modifying the templates. Using actions and filters is the better choice because they rarely change while it is not unusual for templates to be changed and require development work to upgrade any modification you have made to your local copies.
I am working on a site where one category of products is made to order and therefore are not shipped out the same day like other products. While the information is in the product description I want to add it to the order emails so that the customer is reminded of this difference in delivery time.
Order Email Layout
Many of the WooCommerce order emails follow the same layout with 4 do_action() calls.
Each email has a PHP class associated with it. Different email classes attach different functions to these 4 actions.
Add the Note
I used the ‘‘woocommerce_email_order_details‘ action with a low priority to display it above the order contents.
If your note is generic and applies to all orders, your code will be super simple. My note was only if the order contained products from a specific category.
Check order for products
The action passes 4 parameters to your function – the order object ($order), whether the email is being sent to the admin ($sent_to_admin), whether it is the html or plain text email being sent ($plain_text) and finally the email object ($email).
The admin didn’t need to see the note so my code returns immediately if being sent to the admin.
Next is goes through each item in the order. It checks if the product is in the ‘shapetrousers‘ category. For variable products ($product->is_type( ‘variation’ )) it gets the parent product before checking the category because variations are children of the main product.
The message displayed in plain text emails is simply the html message without the markup.
The Code
Going Further: The Email Object
The fourth parameter in the action is the email object. You can examine this to change the emails that your code runs on.
For example, to add a note to only the Processing Order email you would examine the $email->id value.
So you created a plugin to do this? It wasn’t clear in the beginning. I like the idea of a plugin instead of creating a huge functions.php file directly. What if I wanted to show tracking numbers in the ‘order complete email”? Like the last note added in that email.
Joel – Thanks for your comment. I write my code as a plugin to make it easier for people to use the code on their site. Using a plugin also makes it very easy to stop using the code. I recently wrote about how to use my code: https://www.damiencarbery.com/2018/10/how-to-use-my-code-snippets/
For your question, you can certainly add the tracking numbers to the ‘Order complete email’. I wrote a bit of code (another small plugin): https://gist.github.com/damiencarbery/f77b8aa18107290825058a2f7ca4e1e3#file-tracking-info-to-woo-email-php This code *only* runs for the ‘Order complete email’. I used the customer’s full name as an example, because I don’t know how your site is storing the tracking numbers. I hope that this helps.
How would one add attribute name/title as a prefix to attribute value in the email string inside the order table “PRODUCT” column?? Right now it is default to just value being injected into the string… how do we force the name as well? For example with “Size” and value of “8”… I would like the email to read “Size:8”
@Raven: Challenge accepted! I did some source level debugging and lots of source code searches and eventually found a filter that controller whether the attribute name is shown. I’ve written a post about this and it includes the tiny bit of code that solves the problem: Display attribute name and value in WooCommerce
Do you know if there is a way to put tracking information into the order complete email when it’s automatically sent from a shipping label provider to WooCommerce? The tracking information is put in the the order notes at the moment.
Elizabeth and I exchanged a number of emails about this. She uses Shippo service so she hired me to write some custom code to get the tracking info from Shippo into her WooCommerce orders. I wrote a custom REST API endpoint for this. Displaying the tracking info in the email was the easy part and I’ve written a post showing how to Add Tracking Info to WooCommerce order.
Hey damien,
do you know how I can trigger a certain e-mail template depending on the ordered product variation?
The background is the following: I sell beats on my site, which can be purchased in different licenses (product variations). For each license or product variation I have to send the customer a different automatically generated license agreement.
I think it is not too complicated to realize this, but since I have absolutely no idea about the coding I am a bit desperate… :/
Maybe you have an idea.
Thanks in advance!
Greetings Ray
@Ray: As with most parts of WooCommerce, there is a filter! Would it work for you to attach a license agreement to the completed order email? If so, you could look at the StackExchange answer for Add a pdf attachment to WooCommerce completed order email notification.
Combine this code with code that looks at the items in the order and attach the appropriate file.
@damien: in fact I have already managed to attach an additional PDF file to each Order Completed mail. But unfortunately this is not enough. Because for each customer – depending on the purchased license – an individual license agreement should be generated as a separate e-mail. In addition to the legal text, this must also contain order data such as the name of the customer (licensee), name of the seller (licensor), time of order, etc.
So my goal is that the purchase of a certain product variation triggers the sending of the appropriate email template. For example, if the customer buys one product in the Standard license and one in the Premium license, two additional emails have to be sent besides the invoice: “License-Agreement-Standard” and “License-Agreement-Premium” (automatically generated and matched to the purchased products).
It is difficult to describe my intention exactly :/ But maybe you understand what I am getting at. I think you could achieve this for example by adapting the code presented at https://cloudredux.com/adding-sending-custom-woocommerce-email-for-woocommerce-3-0. As a trigger() you would have to use the product variation, so to speak. Unfortunately, this exceeds my coding knowledge by far…
Greetings Ray
The cloudredux.com article shows you how to create a custom email. I think you might need two – one for each additional email. Each would be triggered with ‘woocommerce_payment_complete’, like the example.
You could combine this with my Conditionally disable WooCommerce emails code. This is called when the $this->is_enabled() function is called. The filter function will check the items in the order and return false if the particular product or variation is *not* in the order.
I’m afraid I don’t quite understand. :/ What I do understand is that I need several templates. For example standard-license-agreement.php and premium-license-agreement.php. But how I send them depending on the ordered products or better the license (product variation) at the status “woocommerce_payment_complete” is not clear to me.
I looked at your conditionally disable WooCommerce emails code. So if I try to do that, the way to do it would not be to send an email only if product variation = XY, but to always send an email unless the product variation = XY. Did I understand that correctly? Then I think the following lines underneath “// Add your own conditions here e.g. use preg_match() for email addresses of a particular format.” should be adjusted with the right conditions?
But how would the right condition have to be met to query a specific product variation? Maybe something like: ‘if ( ‘product-variation-name’ == $object->get_variation_attributes()’ or something like that?
Yes, you’ll need two new email templates. Their trigger() function will run on the ‘woocommerce_order_status_completed_notification‘ action, just like the Completed Order email.
The condition will be similar to the code here: https://gist.github.com/damiencarbery/6af7ab6f2700284c18f8febd6c87c40d#file-attach-files-to-wc-order-email-php It loops through the order’s items looking for certain product or variation IDs. I think that you will need to change
$items = $object->get_items();
to
$items = $object->object->get_items();
because the filter function for whether an email is enabled receives the email object, not the order object.
Hey, Damien,
thank you for your efforts. Unfortunately, I still can’t make any progress and give up now.
If one day you have a finished code for it or know a plugin that offers the desired functions, please let me know. The individual IDs of the product variations are organized under edit-tags.php?taxonomy=pa_license&post_type=product, if that should be of any importance.
Anyway, I want to thank you again for getting into this.
Greetings Ray
I don’t know of a plugin that will do what you need but you could ask the developers of Metorik Engage if their plugin will work.
Or you can hire me.
Hi Damien,
I have a simple request (i think):
Is it possible to add the product description below each product, in the email that the admin gets when a new order is placed.
Sample image of email here: https://prnt.sc/s793xz
@Andreas: Thanks for the question. Here is code that will add the short description to all emails. You would need additional code to only enable this function for the new order email.
add_action( 'woocommerce_order_item_meta_end', 'dcwd_order_item_meta_end', 10, 4 ); function dcwd_order_item_meta_end( $item_id, $item, $order, $plain_text ) { $product = wc_get_product( $item->get_product_id() ); //echo 'Product title: ' . $product->get_title();
if ( $plain_text ) { echo "\n", $product->get_short_description(); } else { echo '<p>' . $product->get_short_description(), '</p>'; } }
Andreas replied having found a solution at: https://stackoverflow.com/questions/43564232/add-the-product-description-to-woocommerce-email-notifications
Hi Damien,
I have applied this code in my functions.php
on the email content looks fine.
but got the below error after checkout, in packingslip and invoice.
——————————————————————- Myst Bundle #1 – Pods and Device Kit
Fatal error: Uncaught ArgumentCountError: Too few arguments to function dcwd_order_item_meta_end(), 3 passed in /htdocs/clickandbuilds/Phoenix2retail/wp-includes/class-wp-hook.php on line 287 and exactly 4 expected in /htdocs/clickandbuilds/Phoenix2retail/wp-content/themes/flatsome-child/functions.php:81 Stack trace: #0 /htdocs/clickandbuilds/Phoenix2retail/wp-includes/class-wp-hook.php(287): dcwd_order_item_meta_end(63, Object(WC_Order_Item_Product), Object(Automattic\WooCommerce\Admin\Overrides\Order)) #1 /htdocs/clickandbuilds/Phoenix2retail/wp-includes/class-wp-hook.php(311): WP_Hook->apply_filters(”, Array) #2 /htdocs/clickandbuilds/Phoenix2retail/wp-includes/plugin.php(478): WP_Hook->do_action(Array) #3 /htdocs/clickandbuilds/Phoenix2retail/wp-content/plugins/woocommerce-pdf-invoices/includes/templates/packing-slip/simple/minimal/body.php(109): do_action(‘woocommerce_ord… in /htdocs/clickandbuilds/Phoenix2retail/wp-content/themes/flatsome-child/functions.php on line 81
The WooCommerce PDF Invoices calls do_action( ‘woocommerce_order_item_meta_end’ ) and includes 3 parameters, leaving out the $plain_text one. In WooCommerce plugin it always uses 4 parameters, sometimes passing false for the last parameter.
Please try changing my function definition line to: function dcwd_order_item_meta_end( $item_id, $item, $order, $plain_text = false )
Hi Damien, What I want is an order message based on the variation ordered. So, if there are two variations (blue and white) under color (attribute) for example, how do I make those that ordered white get the additional info while those that order blue not get any extra info?
@Iva – Good question. I wrote a new version of my code to look for at the variation name (that’s the substr call), in this case it looks for ‘White’ at the end of the name. https://gist.github.com/damiencarbery/4a7d97fd58b262a1b1c97c4132d645ea#file-custom-variation-note-to-order-email-php
Hi, I installed and activated the plugin but I don’t see any new fields that actually allow me to add a note to the order on the order details page. I also included the tracking number code but I do not have that box, just the standard Woocommerce Shippment Tracking box that links to Woocommerce shipping label service.
@Theresa – The code in this post does not add any boxes to the Dashboard. You have to edit the code to change the message.
For the tracking info code you have to install the CMB2 plugin – it provides the code that I call to create the meta box.
Hi Damien,
I’m looking to add a pre tax product price column to the product table in order emails.
I’ve struggled to find how to add a new column to the table with this data in it so it shows for each products ordered.
Any direction or advice would be greatly appreciated!
@Chris: The solution is not trivial but should not be hard either.You would have to make local copies (into your theme) of two template files:
* woocommerce/templates/emails/email-order-details.php * woocommerce/templates/emails/email-order-items.php
For email-order-details.php you’d have the extra heading column for “Pre tax price”.In email-order-items.php you’d add the extra cell for the pre tax price and use $item or $order to get the pre tax price.
Hi Damien,
What if I wanted to display a custom note for shipping options instead of products? In times like these, I would like to offer Click & Collect for shipping (local pickup) and it would be a great option to add a custom note if this shipping option has been selected.
Thanks :-)
@Martin: The function receives the $order object and you could query it to find the shipping method used – maybe:
$order->get_shipping_method()
or
$order->get_shipping_to_display()
I don’t know what these functions return – you’ll have to experiment. If you do, please let me know what you find out.
@Damien: Just to get back to you. I got this working. Maybe you can use it for something :-)
add_action( 'woocommerce_email_order_details', 'wc_completed_order_email_instructions', 10, 4 );
function wc_completed_order_email_instructions( $order, $sent_to_admin, $plain_text, $email ) { // Only for "Customer completed order" email notification if( ! ( 'customer_completed_order' == $email->id ) ) return;
foreach( $order->get_items('shipping') as $shipping_item ){ $shipping_rate_id = $shipping_item->get_method_id(); $method_array = explode(':', $shipping_rate_id ); $shipping_method_id = reset($method_array); // Display a custom text for local pickup shipping method only if( 'local_pickup' == $shipping_method_id ){ echo '<strong>Click & Collect:</strong> Your order is ready for pickup'; break; } } }
@Martin: After @Alfonso posted his comment I did a little bit of testing of the two methods I mentioned. They are not as helpful as I hoped – ‘
get_method_id()
‘ is much better.'Flat rate' == $order->get_shipping_method() '€5.00 via Flat rate' == $order->get_shipping_to_display()
This is the name I used for the flat rate method and then the price. If the site admin changes the name then this information will change.
'flat_rate' == $shipping_item->get_method_id()
This returns the internal name of the shipping method so it cannot be changed. It is better to use this in the code.
@Martin Your modification was exactly the basis for something I had been trying to achieve for a client. I just modified it a bit so it now sends the administrator a very visible notice only on orders marked for home delivery instead of local pickup. Hope it’s useful for someone else:
// Add notice to home delivery orders add_action( 'woocommerce_email_order_details', 'wc_home_delivery_notice', 10, 4 );
function wc_home_delivery_notice( $order, $sent_to_admin, $plain_text, $email ) { // Only for "New order" email notification if( ! ( 'new_order' == $email->id ) ) return;
foreach( $order->get_items('shipping') as $shipping_item ){ $shipping_rate_id = $shipping_item->get_method_id(); $method_array = explode(':', $shipping_rate_id ); $shipping_method_id = reset($method_array); // Display a custom text for home delivery shipping method only if( 'flat_rate' == $shipping_method_id ){ echo 'Para entrega a domicilio'; break; } } }
Thank you for this useful code snippet. Please, tell us how we can make a different custom note for a product from a different product category than “shapetrousers”. Let’s say if has term “shapetrousers”, the custom note on email is “Shapetrousers products are custom made so please allow 10 days for delivery.” (as in your snippet), but if has term “another-term”, the custom note on email should be for example: ” This is a custom note for ANOTHER TERM”. Thank you for your support. Kind regards, Marcel
@Marcel: If we add a new variable for “another-term” it should work. There is probably a better way but this is a quick fix.
$order_has_shapetrousers_product = false; $order_has_another_term_product = false;
$items = $order->get_items(); foreach ( $items as $item_id => $item ) { $product = $item->get_product(); if ( $product->is_type( 'variation' ) ) { $product = wc_get_product( $product->get_parent_id() ); }
if ( has_term( 'shapetrousers', 'product_cat', $product->get_id() ) ) { $order_has_shapetrousers_product = true; } if ( has_term( 'another-term', 'product_cat', $product->get_id() ) ) { $order_has_another_term_product = true; } }
if ( $order_has_shapetrousers_product ) { if ( $plain_text ) { echo "\nNOTE: Shapetrousers products are custom made so please allow 10 days for delivery.\n"; } else { echo '<p><strong>Note</strong>: Shapetrousers products are custom made so please allow 10 days for delivery.</p>'; } } if ( $order_has_another_term_product ) { if ( $plain_text ) { echo "\nNOTE: another-term products are special.\n"; } else { echo '<p><strong>Note</strong>: another-term products are special .</p>'; } }
@Damien: I have implemented your code and it works fine. I wanted to go further and add another condition, namely that the notice is only added in the Processing Order email. For this I added: && $ email-> id == ‘customer_processing_order’.
The snippet part looks like this: if (has_term (‘shapetrousers’, ‘product_cat’, $ product-> get_id ()) && $ email-> id == ‘customer_processing_order’) { $ order_has_shapetrousers_product = true; }
if (has_term (‘another-term’, ‘product_cat’, $ product-> get_id ()) && $ email-> id == ‘customer_processing_order’) { $ order_has_another_term_product = true; }
The problem is that the custom note only appears in the email just for “shapetrousers” category, but doesn’t happen when I buy a product from “another term” category – the custom note practically doesn’t appear in the email.
What would be the possible cause? Marcel
I found this solution: if ( $email->id == ‘customer_processing_order’ && $sent_to_admin ) { return; } and it is working. Your thoughts, Damien?
@Marcel – I do not think that that will work because the ‘customer_process_order’ email is not sent to admin. Use two different lines:
if ( $sent_to_admin ) { return; } if ( $email->id != ‘customer_processing_order ) { return; }