I am looking for a way to send an email based on the products ordered.

I was thinking this needed to be done using custom order statuses. Although, this would require the ability to change the default order status based on product. And with multiple product types ordered, would require the ability to set multiple statuses to an order.

So, at this stage the ideal solution would be a custom email that is sent conditionally based on products ordered.

I have so far been unable to find a solution for this. Any chance this is possible?

1 Answer 1


This is not hard to implement in Commerce if you're ok with all the content going in one email (which I think is a better approach from the customer's point of view - we used to do this as separate emails years ago but customers got irritated that one order results in a whole bunch of emails). If you do want to trigger multiple separate emails, that's a tiny bit harder - but would not be difficult to implement in a plugin.

(Sketch of that approach - listen to onOrderComplete and loop through cart items. For any that match your desired triggers, parse a template and then email it off with Craft's email functions - simple stuff as far as plugins go - let me know if you want me to expand on this).

But back to the one email approach - your order emails (receipts or any triggered by any other status) - are full twig templates - that means you can use conditionals, includes etc, to achieve what you want.

Basically, in your email template, you can loop through the products in the order and set content into some 'storage' variables (with the content being directly written in that template, or stored in fields on your products, or stored in entries in a section, or using include of other templates - anything really!).... and then output that content later in the email.

We do this to produce a 'Super Receipt' - basically a receipt that includes a whole lot of additional 'How To Get The Best From Your XXX' type content. This content comes, in our case, from 3 levels - the product category for stuff that applies to e.g. all monitors, the brand, for brand specific stuff (how to get support from NEC, for example), and finally from product specific content. This allows for very quickly building up libraries of this content, and a nice hierarchy of concerns. Categories, Brands and Products each have a field autoResponder that holds this content.

Clients love it.

Here's the code.

    {# Store items we've seen already so we don't get duplicate AR content #}
    {% set seenItems = [] %}
    {% set seenProductCategories = [] %}
    {% set seenProductBrands = [] %}

    {# And store the actual content we need to print out at the end in groups #}
    {% set arProductContent = {} %}
    {% set arBrandContent = {} %}
    {% set arCatgeoryContent = {} %}

    {% for lineItem in order.lineItems %}

        {# No need to look at the same products twice... #}
        {% if lineItem.purchasable.product.id not in seenItems %}

            {% set seenItems = seenItems|merge([lineItem.purchasable.product.id]) %}

                {# Category AR content #}
                {% if lineItem.purchasable.product.id not in suppressedIds %}
                    {% for category in lineItem.purchasable.product.categories %}
                        {% if category.autoResponder|length %}
                            {% set arCatgeoryContent = arCatgeoryContent|merge({(category.title):category.autoResponder})  %}                             
                        {% endif %}
                    {% endfor %}  
                {% endif %} 

                {# Brand AR Content #}
                {% set brand = lineItem.purchasable.product.brand.first() %}
                {% if brand|length %}
                    {% if brand.id not in seenProductBrands %}
                        {% if brand.autoResponder|length %}
                            {% set seenProductBrands = seenProductBrands|merge([brand.id]) %}
                            {% set arBrandContent = arBrandContent|merge({(brand.title):brand.autoResponder})  %}
                        {% endif %}
                    {% endif %}
                {% endif %}

                {# Product AR Content  #}
                {% set product = lineItem.purchasable.product %}
                {% if product.autoResponder|length and product.type != "service" %}                        
                    {% set preamble = '<small>See also: <a href="' ~ product.url ~ '#wiki">' ~ product.title ~ ' wiki</a></small>' %}
                    {% set arProductContent = arProductContent|merge({(product.title): preamble|raw ~ product.autoResponder})  %}
                {% endif %}

         {% endif %}

    {% endfor %}

    {# Now, print it all out, most general & needed to most specific #}

    {% for key, content in arCatgeoryContent %}
        {{ emailLib.paraWithHeader ( key, content|raw )}}
    {% endfor %}

    {% for key, content in arBrandContent %}
        {{ emailLib.paraWithHeader ( key, content|raw )}}
    {% endfor %}

    {% for key, content in arProductContent %}
        {{ emailLib.paraWithHeader ( key, content|raw )}}
    {% endfor %}
  • Thanks Jeremy! Huge help. We started working through the one email approach, which was working out well. However, is there a way to conditionally send to different emails? The product that we need the new email for needs to go to a third party for logistics. We of course don't want them receiving all orders. Dec 10, 2016 at 7:27
  • Well, you could use a plugin and ‘commerce_email.onBeforeSendEmail’ or ‘onSendEmail’ events...this gives you the chance to 'intercept' the email sending and change/add/removes addresses, and/or go nuts and use Craft's email function to do whatever you like really. Dec 13, 2016 at 7:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.