Commerce v1 Orders Emails & Messages

Order Messages are communications from the shop specific to a certain order. Typically email notifications to the customer, but could also include emails to the merchant about the new order, text (SMS) messages sent via a third party integration, or other forms of order-specific communication.

Viewing & sending Messages

To manage Order Messages, go to Commerce > Orders and when viewing an order navigate to the Messages tab. There you can see all previous messages, and create new ones.

When creating messages, you can choose if it should be sent immediately or not. When you uncheck the Send message right away checkbox, it will be stored as a draft.

To send a draft hover over Actions in the grid and select Send Message. To view a message contents, click View Message Content.

For automated messages, such as those created through an Email Status Change Action, messages are usually sent right away.

Multiple recipients are supported, separate them with a comma.

Message Types

The Commerce core provides two message types: Email and Internal.

Email Message

The email message type is the most common one. It sends, surprisingly, an email.

Email sending is done through MODX using PhpMailer, so make sure that your email settings (in MODX under System > Settings > Email area) are configured properly to send email. It's strongly encouraged to use SMTP with an email delivery service like Mandrill, Postmark, Amazon SES, or others. Make sure that SPF and DKIM are configured properly for optimal delivery.

Internal Note

The internal message type creates an internal note. It is not sent to anyone, but remains available to be checked on the order.

Available Placeholders

For the Email Message Type, and possibly third party message types, you can use a whole bunch of placeholders in the Subject and Content fields.

These are parsed as Twig templates, so you can use logic in the emails as well.

An example of all placeholders is included at the end of this page. We'll discuss a couple special fields first.

Order Fields

Order fields are available with the order prefix, like {{ order.id }}, {{ order.total_formatted }} and {{ order.total_quantity }}.

Order Items

An array of order items is found in the items placeholder, which could be used like this:

<ul>
{% for item in items %}
   <li>{{ item.quantity }}x {{ item.name }} - {{ item.total_formatted }}</li>
{% endfor %}
</ul>

When items are assigned to a product, you can find the product information in item.product. The exact information you'll find there may depend on the product type.

For example: {{ item.product.weight_formatted }}

Addresses

Address information is available with the billing_address and shipping_address prefixes. For example {{ billing_address.fullname }}, {{ shipping_address.address1 }} and {{ shipping_address.email }}.

Note that depending on your checkout, it is possible only one or the other is available - build your template accordingly.

To automatically format the address, use the format_address filter like this: {{ shipping_address|format_address }}.

Shipments & Shipping Methods

An array of order shipments is available as {{ shipments }}, each containing the shipping method that was chosen. Here's how you may use that:

{% for shipment in shipments %}
    <p>Shipment {{ shipment.id }} will be delivered by {{ shipment.method.name }}</p>
{% endfor %}

As of v1.1, the {{ shipment.total_weight }}, {{ shipment.tracking_url }} and {{ shipment.email_note }} placeholders are available.

  • The tracking_url needs to be provided by a custom order shipment implementation and may not be available until an order has been processed.
  • The total_weight is calculated from each products' configured weight times the quantity. The unit this is in will be based on the weight unit set on the first product that was processed.
  • The email_note is fully-rendered HTML based on the shipping methods' configured email note at the time the email is sent. In v0.12 and 1.0 you can access the email note as {{ shipping_method_note }}.

If you're looking to organise your order items by shipment, you will need to combine the iteration and match item.shipment to shipment.id, like this:

{% for shipment in shipments %}
    <h3>{{ shipment.name }}</h3>
    
    {% if shipment.email_note %}
        {{ shipment.email_note|raw }}
    {% endif %}
    
    <ul>
        {% for item in items %}
            {% if item.shipment == shipment.id %}
                <li>{{ item.quantity }}x {{ item.name }} - {{ item.total_formatted }}</li>
            {% endif %}
        {% endfor %}
    </ul>
    
    <p>Total {{ shipment.product_quantity }} products, weight: {{ shipment.total_weight }}</p>
    
    {% if shipment.tracking_url %}
        <p><a href="{{ shipment.tracking_url }}" class="button">Track shipment {{ shipment.tracking_reference }}</a></p>
    {% elseif shipment.tracking_reference %}
        <p>Tracking reference: {{ shipment.tracking_reference }}</p>
    {% endif %}
{% endfor %}

(Prior to v0.8, you could use {{ shipping_method }}, but that has been deprecated and no longer returns the method for orders placed since the 0.8 update)

Transactions & Payment Methods

Transactions for an order are available in a {{ transactions }} array that you can iterate over (as of v0.11.0-rc2).

Only completed transactions are included in this array, so you don't have to filter out failed/pending transactions.

Note that a transaction with the Manual payment gateway is considered successful/paid by Commerce, even though may users apply that as a "pay in store"-type payment.

It is technically possible for there to be more than one completed transaction (e.g. partial payments or loyalty cards), though that's uncommon at this time.

Transactions include the payment method as `{{ transaction.method }}.

An example:

{% for transaction in transactions %}
    <p>Thank you for your {{ transaction.amount_formatted }} payment via {{ transaction.method.name }}.</p>
{% endfor %}

As of v1.1, the payment-method specific email note is also available on the transaction as {{ transaction.email_note|raw }}. In v0.12 and 1.0 you can access the email note as {{ payment_method_note|raw }}.

Order fields

Custom order fields are available in order_fields, already rendered for the customer (as of v0.11).

To render a specific order field:

{{ order_fields.coupon|raw }}

To render all order fields, iterate over the array:

{% for name, value in order_fields %}
    <p><b>{{ lex('commerce.' ~ name) }}</b>: {{ value|raw }}</p>
{% endfor %}

Note that we're applying the raw filter to the value - this allows custom order fields to output HTML (like a link or .

Payment/Shipping notes

On payment and shipping methods you can set an Email Note that is automatically included in the email when those methods are selected. This is useful for including method-specific instructions or thank you messages.

In v0.12 and v1.0, use {{ payment_method_note|raw }} and {{ shipping_method_note|raw }} respectively.

In v1.1+ the email note is also available in the transaction and shipment as {{ transaction.email_note }} or {{ shipment.email_note }}. Which one to use depends on the layout of your email.

Invoices

The last generated PDF invoice for an order is available in the invoice order field, like: {{ order_fields.invoice }}. That will only contain the invoice reference.

To attach the PDF invoice to the email, enable "Attach invoice" on the email status change.

Example data

This is an example of all the data available in an order message. You can get this data yourself by enabling the commerce.debug system setting, and using {{ dump() }} or {{ dump(placeholder name) }}.

array (size=9)
  'order' => 
    array (size=38)
      'id' => int 4072
      'class_key' => string 'comProcessingOrder' (length=18)
      'properties' => 
        array (size=1)
          'guest_checkout' => boolean true
      'secret' => string '3409....7157a20' (length=64)
      'test' => boolean true
      'status' => int 2
      'currency' => string 'EUR' (length=3)
      'subtotal' => int 5000
      'discount' => int 0
      'shipping' => int 250
      'transaction' => int 0
      'total_before_tax' => int 5250
      'total_ex_tax' => int 5250
      'tax' => int 1103
      'total' => int 6353
      'total_due' => int 0
      'total_quantity' => int 2
      'created_on' => int 1523970986
      'last_updated_on' => int 1523971054
      'status_updated_on' => int 1523971054
      'created_by' => int 1
      'last_updated_by' => int 1
      'status_updated_by' => int 1
      'shipping_method' => int 0
      'parent' => int 0
      'user' => int 1
      'subtotal_formatted' => string '€50.00' (length=8)
      'discount_formatted' => string '€0.00' (length=7)
      'shipping_formatted' => string '€2.50' (length=7)
      'transaction_formatted' => string '€0.00' (length=7)
      'total_before_tax_formatted' => string '€52.50' (length=8)
      'total_ex_tax_formatted' => string '€52.50' (length=8)
      'tax_formatted' => string '€11.03' (length=8)
      'total_formatted' => string '€63.53' (length=8)
      'total_due_formatted' => string '€0.00' (length=7)
      'created_on_formatted' => string '2018-04-17 at 15:16:26 CEST' (length=27)
      'last_updated_on_formatted' => string '2018-04-17 at 15:17:34 CEST' (length=27)
      'status_updated_on_formatted' => string '2018-04-17 at 15:17:34 CEST' (length=27)
  'taxes' => 
    array (size=1)
      27 => 
        array (size=15)
          'id' => int 27
          'class_key' => string 'comTaxRate' (length=10)
          'properties' => null
          'key' => string 'VAT-NL-standard' (length=15)
          'name' => string 'Netherlands Standard VAT' (length=24)
          'rate_provider' => string 'modmore\Commerce\Taxes\EUVat' (length=28)
          'percentage' => float 21
          'percentage_formatted' => string '21%' (length=3)
          'total_taxed_amount' => int 5250
          'total_tax_amount' => int 1103
          'total' => int 6353
          'is_inclusive' => boolean false
          'total_taxed_amount_formatted' => string '€52.50' (length=8)
          'total_tax_amount_formatted' => string '€11.03' (length=8)
          'total_formatted' => string '€63.53' (length=8)
  'items' => 
    array (size=1)
      0 => 
        array (size=34)
          'id' => int 4541
          'class_key' => string 'comOrderItem' (length=12)
          'properties' => null
          'order' => int 4072
          'product' => 
            array (size=23)
              'id' => int 78
              'class_key' => string 'comProduct' (length=10)
              'properties' => null
              'sku' => string 'REDACTOR-UNLIMITED' (length=18)
              'barcode' => string '0' (length=1)
              'name' => string 'Redactor Unlimited Subscription' (length=31)
              'description' => string '' (length=0)
              'price' => int 2500
              'stock' => int 4957
              'weight' => float 0
              'weight_unit' => string 'kg' (length=2)
              'image' => string '' (length=0)
              'tax_group' => int 1
              'delivery_type' => int 2
              'target' => int 0
              'removed' => boolean false
              'removed_on' => int 0
              'removed_by' => boolean false
              'price_formatted' => string '€25.00' (length=8)
              'removed_on_formatted' => string '' (length=0)
              'link' => boolean false
              'edit_link' => string '/manager/?namespace=commerce&a=index&ca=product/update&id=78' (length=60)
              'weight_formatted' => string '0 kg' (length=4)
          'idx' => int 10
          'currency' => string 'EUR' (length=3)
          'status' => int 0
          'allow_update' => boolean true
          'delivery_type' => int 2
          'shipment' => int 2166
          'sku' => string 'REDACTOR-UNLIMITED' (length=18)
          'name' => string 'Redactor Unlimited Subscription' (length=31)
          'link' => string '' (length=0)
          'description' => string '' (length=0)
          'price' => int 2500
          'image' => string '' (length=0)
          'quantity' => int 2
          'subtotal' => int 5000
          'discount' => int 0
          'shipping' => int 0
          'total_before_tax' => int 5000
          'total_ex_tax' => int 5000
          'tax' => int 1050
          'total' => int 6050
          'comment' => string '' (length=0)
          'price_formatted' => string '€25.00' (length=8)
          'subtotal_formatted' => string '€50.00' (length=8)
          'discount_formatted' => string '€0.00' (length=7)
          'shipping_formatted' => string '€0.00' (length=7)
          'total_before_tax_formatted' => string '€50.00' (length=8)
          'total_ex_tax_formatted' => string '€50.00' (length=8)
          'tax_formatted' => string '€10.50' (length=8)
          'total_formatted' => string '€60.50' (length=8)
  'billing_address' => 
    array (size=20)
      'id' => int 773
      'class_key' => string 'comAddress' (length=10)
      'properties' => null
      'user' => int 1
      'remember' => string '1' (length=1)
      'fullname' => string 'Mark Hamstra' (length=12)
      'firstname' => string '' (length=0)
      'lastname' => string '' (length=0)
      'company' => string 'Mark Hamstra Web Development' (length=28)
      'address1' => string 'Some Street 14' (length=14)
      'address2' => string '' (length=0)
      'address3' => string '' (length=0)
      'zip' => string '9876ZX' (length=6)
      'city' => string 'Leeuwarden' (length=10)
      'state' => string 'Friesland' (length=9)
      'country' => string 'NL' (length=2)
      'phone' => string '' (length=0)
      'mobile' => string '' (length=0)
      'email' => string '[email protected]' (length=21)
      'notes' => string '' (length=0)
  'shipping_address' => 
    array (size=20)
      'id' => int 773
      'class_key' => string 'comAddress' (length=10)
      'properties' => null
      'user' => int 1
      'remember' => string '1' (length=1)
      'fullname' => string 'Mark Hamstra' (length=12)
      'firstname' => string '' (length=0)
      'lastname' => string '' (length=0)
      'company' => string 'Mark Hamstra Web Development' (length=28)
      'address1' => string 'Some Street 14' (length=14)
      'address2' => string '' (length=0)
      'address3' => string '' (length=0)
      'zip' => string '9876ZX' (length=6)
      'city' => string 'Leeuwarden' (length=10)
      'state' => string 'Friesland' (length=9)
      'country' => string 'NL' (length=2)
      'phone' => string '' (length=0)
      'mobile' => string '' (length=0)
      'email' => string '[email protected]' (length=21)
      'notes' => string '' (length=0)
  'shipments' => 
    array (size=1)
      0 => 
        array (size=30)
          'id' => int 2166
          'class_key' => string 'comOrderShipment' (length=17)
          'properties' => null
          'test' => boolean true
          'order' => int 4072
          'delivery_type' => int 2
          'status' => int 0
          'method' => 
            array (size=22)
              'id' => int 56
              'class_key' => string 'comShippingMethodByWeight' (length=25)
              'properties' => 
                array (size=4)
                  ...
              'delivery_type' => int 2
              'enabled_in_test' => boolean true
              'enabled_in_live' => boolean false
              'name' => string 'Standard Shipping' (length=17)
              'description' => string '2 - 4 days' (length=10)
              'price' => int 250
              'price_percentage' => float 0
              'minimum_order_total' => int 0
              'maximum_order_total' => int 0
              'countries' => string '' (length=0)
              'sortorder' => int 1
              'removed' => boolean false
              'removed_on' => int 0
              'removed_by' => boolean false
              'price_formatted' => string '€2.50' (length=7)
              'price_percentage_formatted' => string '0%' (length=2)
              'minimum_order_total_formatted' => string '€0.00' (length=7)
              'maximum_order_total_formatted' => string '€0.00' (length=7)
              'removed_on_formatted' => string '' (length=0)
          'tracking_reference' => string '' (length=0)
          'currency' => string 'EUR' (length=3)
          'product_quantity' => int 2
          'product_value' => int 6050
          'product_value_ex_tax' => int 5000
          'weight' => float 0
          'weight_unit' => string 'kg' (length=2)
          'fee' => int 250
          'is_inclusive' => boolean false
          'tax_group' => int 1
          'tax_rate' => int 27
          'tax_percentage' => float 21
          'fee_ex_tax' => int 250
          'tax_amount' => int 53
          'fee_incl_tax' => int 303
          'product_value_formatted' => string '€60.50' (length=8)
          'product_value_ex_tax_formatted' => string '€50.00' (length=8)
          'fee_formatted' => string '€2.50' (length=7)
          'tax_percentage_formatted' => string '21%' (length=3)
          'fee_ex_tax_formatted' => string '€2.50' (length=7)
          'tax_amount_formatted' => string '€0.53' (length=7)
          'fee_incl_tax_formatted' => string '€3.03' (length=7)
  'transactions' => 
    array (size=1)
      0 => 
        array (size=17)
          'id' => int 716
          'class_key' => string 'comTransaction' (length=14)
          'properties' => 
            array (size=0)
              ...
          'test' => boolean true
          'order' => int 4072
          'status' => int 5
          'method' => 
            array (size=21)
              'id' => int 208
              'class_key' => string 'comPaymentMethod' (length=16)
              'properties' => null
              'enabled_in_test' => boolean true
              'enabled_in_live' => boolean false
              'gateway' => string 'modmore\Commerce\Gateways\Manual' (length=32)
              'name' => string 'Dummy Gateway' (length=13)
              'description' => string '' (length=0)
              'price' => int 0
              'price_percentage' => float 0
              'minimum_order_total' => int 0
              'maximum_order_total' => int 0
              'sortorder' => int 5
              'removed' => boolean false
              'removed_on' => int 0
              'removed_by' => boolean false
              'price_formatted' => string '€0.00' (length=7)
              'price_percentage_formatted' => string '0%' (length=2)
              'minimum_order_total_formatted' => string '€0.00' (length=7)
              'maximum_order_total_formatted' => string '€0.00' (length=7)
              'removed_on_formatted' => string '' (length=0)
          'reference' => null
          'currency' => string 'EUR' (length=3)
          'amount' => int 6353
          'fee' => int 0
          'created_on' => int 1523971053
          'completed_on' => int 1523971054
          'amount_formatted' => string '€63.53' (length=8)
          'fee_formatted' => string '€0.00' (length=7)
          'created_on_formatted' => string '2018-04-17 at 15:17:33 CEST' (length=27)
          'completed_on_formatted' => string '2018-04-17 at 15:17:34 CEST' (length=27)
  'config' => 
    array (size=4)
      'site_url' => string 'https://site.local/' (length=23)
      'site_name' => string 'My Webshop' (length=10)
      'email_header' => string 'https://site.local/assets/email-header.jpg' (length=46)
      'email_footer' => string 'My Webshop &bull; <em>Powered by Commerce</em>' (length=46)
  'message' => 
    array (size=15)
      'id' => int 189
      'class_key' => string 'comOrderEmailMessage' (length=20)
      'properties' => 
        array (size=3)
          'subject' => string 'Test' (length=4)
          'bcc' => string '' (length=0)
          'send_now' => string '1' (length=1)
      'order' => int 4072
      'content' => string '{{ dump() }}' (length=12)
      'recipient' => string '[email protected]' 
      'from' => string '[email protected]'
      'created_on' => int 1524133740
      'created_by' => int 1
      'sent' => boolean true
      'sent_on' => int 1524133740
      'sent_by' => int 1
      'created_on_formatted' => string '2018-04-19 at 12:29:00 CEST' (length=27)
      'sent_on_formatted' => string '2018-04-19 at 12:29:00 CEST' (length=27)
      'from_name' => string 'My Webshop' (length=10)