Walkthrough: Building a Commerce email message template

Abstract

This walkthrough provides the code necessary to build a Commerce email message template.

When you select a Commerce marketing automation campaign during site creation:

  • An email manager root is created with the naming convention <site> Emails.

  • The templates used in the Commerce emails are located in the /sitecore/Templates/Feature/Commerce Experience Accelerator/Emails folder.

    Diagram showing where email templates are stored in the Content Editor
  • The renderings used in the Commerce emails are located in the sitecore/Layout/Renderings/Feature/Commerce Experience Accelerator/Commerce Emails folder.

    EmailrenderingsTree3.png
  • The layouts used in Commerce emails are located in the sitecore/Layout/Layouts/Feature/Commerce Experience Accelerator/Commerce Emails folder.

    EmailRenderingTreeLayout93.png

When a customer manipulates the cart or places an order, they are automatically added to the relevant marketing automation plan. The automation service evaluates the plan and determines if an email must be sent to the customer. As emails are initiated from the automation service, the email request is placed on the message bus. The Content Management (CM) server takes this information and builds a URL (which includes the commerce query parameters). This is included in the email rendering and sent to the customer in the form of an abandoned cart email message or an orders placed email message.

Note

To avoid having to make changes to code when customizing a component (such as an email message), define labels and messages on data templates.

If you do not want to use the templates provided, you can create your own Commerce email message templates or extend the existing templates. To do so:

Note

The Send Email marketing action does not include commerce data in the email message.

The New Order Placed email message, for example, renders in the Experience Editor as follows.

NewOrderPlaced.png

To create or modify message content fields:

  1. On the Sitecore Launchpad, click Content Editor.

  2. Go to sitecore/Templates/Feature/Commerce Experience Accelerator/Emails.

  3. Expand the Cart, Order,Products or Shared folder.

    MessageContentFolders93.png
  4. Select an email component (header, lines, total, and so on).

  5. On the Builder tab, add, modify, or delete template field labels and types as required.

  6. Click Save to save your changes.

  1. In the Content Editor, go to the sitecore/Layout/Renderings/Feature/Commerce Experience Accelerator/Commerce Emails folder.

  2. Expand the Email Cart, Catalog, Order, or Shared folder.

  3. Select the email component (cart lines, cart total and so on).

  4. On the Content tab, in the Data section, modify the controller actions as required.

  5. Click Save to save your changes.

In order for the Commerce email messages to retrieve Commerce data from the storefront, you need to add the following controller code.

public ActionResult OrderHeader()
{
    EmailOrderModel model = null;
    string renderingViewPath = string.Empty;
    var visitorContext = Foundation.Connect.VisitorContext.Current;
  
// This code sets up the context for the Commerce site and user if the Sitecore and contact
// information are present based on the query parameters.

using (var commerceSiteContext = new SetupCommerceSiteContext(visitorContext))
    {
     var linkRepository = ServiceLocator.ServiceProvider.GetService<IEmailOrderRepository>();
     model = linkRepository.GetOrder(commerceSiteContext);
     
// This code calls the base GetRenderingView() controller method, which supports the SXA view
// override feature.

    renderingViewPath = GetRenderingView("EmailOrderHeader");
    }
     return View(renderingViewPath, model);
}

Note

For more information about the SXA views, see Customize rendering HTML per site.

The repository method code is called from the controller within the SetupCommerceSiteContext context switch that sets the Sitecore site and current logged-in user based on the provided query parameters.

public virtual EmailOrderModel GetOrder(SetupCommerceSiteContext commerceSiteContext)
{
  var orderId = string.Empty;

// This code looks for the order_id query parameter and, if specified, retrieves the model for
// a specific order.

  if (Sitecore.Modules.EmailCampaign.Core.ExmContext.QueryString != null)
  {
      orderId = Sitecore.Modules.EmailCampaign.Core.ExmContext.QueryString["order_id"];
  }
  var model = ModelProvider.GetModel<EmailOrderModel>(); ;
  var visitorContext = Foundation.Connect.VisitorContext.Current;

// This code verifies if the user context was switched to a valid user.  If the context was
// switched, the order is retrieved using the Storefront foundation code.

  if (commerceSiteContext.HasSwitchedUsers)
  {
  var storefrontContext = ServiceLocator.ServiceProvider.GetService<IStorefrontContext>();
  var customerId = visitorContext.CustomerId;
  Engine.Connect.Entities.CommerceOrder order = null;

// This code caches the returned order on the site context items collection. To avoid 
// retrieving the same order multiple times when an email has multiple renderings using the
// same model (called from within the same HttpContext), the order is retrieved once and
// cached so all other calls retrieve the order from the cache.  The site context item 
// collection is emptied at the end of the HttpRequest.

 if (SiteContext.Items[OrderModelKey] != null)
 {
     order = (Engine.Connect.Entities.CommerceOrder)SiteContext.Items[OrderModelKey];
 }
 else
 {
     var orderManager = ServiceLocator.ServiceProvider.GetService<IOrderManager>();
     var response = orderManager.GetOrderDetails(storefrontContext.CurrentStorefront, visitorContext, orderId);
     order = response.Result as Engine.Connect.Entities.CommerceOrder;
     SiteContext.Items[OrderModelKey] = order;
  }
     model.Init(order);
  }
  else
  {
     model.InitMockData();
  }
 return model;
  }

To render the cshtml view, the following email order header view is required.

@using System.Web.Mvc.Ajax
@using System.Web.Mvc.Html
@using Sitecore.XA.Foundation.SitecoreExtensions.Extensions
@using Sitecore.Commerce.XA.Foundation.Common.ExtensionMethods
@using Sitecore.Mvc
@using Sitecore.Commerce.XA.Feature.Emails
@using Sitecore.Commerce.XA.Feature.Emails.Models
@using System.Linq
@model EmailOrderModel
@{
    var orderInformationLabel = Html.Sitecore().Field(Constants.Templates.EmailOrderHeader.FieldNames.OrderInformationLabel, Html.Sitecore().CurrentItem);

// This code retrieves the labels from the item data source. These are the values retrieved
// from the template.

    var orderDateLabel = Html.Sitecore().Field(Constants.Templates.EmailOrderHeader.FieldNames.OrderDateLabel, Html.Sitecore().CurrentItem);
    var orderIdLabel = Html.Sitecore().Field(Constants.Templates.EmailOrderHeader.FieldNames.OrderIdLabel, Html.Sitecore().CurrentItem);
   var orderStatusLabel = Html.Sitecore().Field(Constants.Templates.EmailOrderHeader.FieldNames.OrderStatusLabel, Html.Sitecore().CurrentItem);
    var deliveryAddressLabel = Html.Sitecore().Field(Constants.Templates.EmailOrderHeader.FieldNames.OrderDeliveryAddressLabel, Html.Sitecore().CurrentItem);
    var billingAddressLabel = Html.Sitecore().Field(Constants.Templates.EmailOrderHeader.FieldNames.OrderBillingAddressLabel, Html.Sitecore().CurrentItem);
}

// This code specifies the CSS for this view.  Each view must define its own embedded 
// CSS unlike standard web pages. The rest of the view (.cshtml file) retrieves the data from
// the EmailOrderModel.

<style type="text/css">
    .message-area {
        display: flex;
        justify-content: space-between;
    }

        .message-area .email-text-container {
            flex-basis: 70%;
            box-sizing: border-box;
        }

        .message-area .email-order-header-container {
            flex-basis: 30%;
            margin-right: 20px;
            margin-bottom: 20px;
            border: 1px solid #6B717B;
            -webkit-border-radius: 5px;
            -moz-border-radius: 5px;
            border-radius: 5px;
            box-sizing: border-box;
        }

    .email-order-header-container {
        padding: 20px;
        font-family: "Open Sans",sans-serif !important;
        font-size: 14px;
        color: #6B717B;
    }

        .email-order-header-container .order-header {
            font-size: 16px;
            font-weight: bold;
            color: #383C41;
            margin: 0;
        }

        .email-order-header-container .order-header + .order-details {
            margin-top: 20px;
        }

        .email-order-header-container .order-address-line + .order-address-line {
            margin-top: 5px;
        }

    .order-information-container .order-detail {
        display: flex;
        justify-content: space-between;
    }

              .order-information-container .order-detail + .order-detail {
                     margin-top: 5px;
              }

              .order-information-container .order-detail .label:after {
                     content: ": ";
              }
              
              .order-information-container .order-detail .value {
                     max-width: 60%;
                     word-wrap: break-word;
              }

    .order-billing-container,
    .order-delivery-container {
        margin-top: 40px;
    }
</style>
<div class="email-order-header-container">
    @if (!string.IsNullOrWhiteSpace(Model.RenderingMessage))
    {
        <h2>@Model.RenderingMessage</h2>
    }
    else
    {
        <div class="component-content">
            <div class="order-information-container">
                <h3 class="order-header">
                    @orderInformationLabel
                </h3>
                <div class="order-details">
                    <div class="order-detail order-number">
                        <span class="label">
                            @orderIdLabel
                        </span>
                        <span class="value">
                            @Model.TrackingNumber
                        </span>
                    </div>
                    <div class="order-detail order-date">
                        <span class="label">
                            @orderDateLabel
                        </span>
                        <span class="value">
                            @Model.Created.ToDisplayedDate()
                        </span>
                    </div>
                    <div class="order-detail order-status">
                        <span class="label">
                            @orderStatusLabel
                        </span>
                        <span class="value">
                            @Model.Status
                        </span>
                    </div>
                </div>
            </div>
            @if (Model.BillingAddress != null)
            {
                <div class="order-billing-container">
                    <h3 class="order-header">
                        @billingAddressLabel
                    </h3>
                    <div class="order-details">
                        <div class="order-address-line">
                            <span class="billing-address-line">
                                @Model.BillingAddress.Address1,
                            </span>
                            <span class="billing-address-city">
                                @Model.BillingAddress.City
                            </span>
                        </div>
                        <div class="order-address-line">
                            <span class="billing-address-state">
                                @Model.BillingAddress.State, 
                            </span>
                            <span class="billing-address-zipcode">
                                @Model.BillingAddress.ZipPostalCode
                            </span>
                        </div>
                        <div class="order-address-line">
                            <span class="billing-address-country">
                                @Model.BillingAddress.Country
                            </span>
                        </div>
                    </div>
                </div>
            }
            @if (Model.IsSingleShipping && Model.ShippingAddress != null)
            {
                <div class="order-delivery-container">
                    <h3 class="order-header">
                        @deliveryAddressLabel
                    </h3>
                    <div class="order-details">
                        <div class="order-address-line">
                            <span class="shipping-address-line">
                                @Model.ShippingAddress.Address1,
                            </span>
                            <span class="shipping-address-city">
                                @Model.ShippingAddress.City
                            </span>
                        </div>
                        <div class="order-address-line">
                            <span class="shipping-address-state">
                                @Model.ShippingAddress.State, 
                            </span>
                            <span class="shipping-address-zipCode">
                                @Model.ShippingAddress.ZipPostalCode
                            </span>
                        </div>
                        <div class="order-address-line">
                            <span class="shipping-address-country">
                                @Model.ShippingAddress.Country
                            </span>
                        </div>
                    </div>
                </div>
            }
        </div>
    }
</div>

Each email type has a branch template.  The New order placed email has the following branch:

EmailBranch.png

The following procedure describes how the existing Order Header was added to the branch template New Order Placed branch. The procedure is the same for any branch template.

To add a new rendering to the New Order Placed branch template:

  1. In the Content Editor, go to sitecore/Templates/Branches/Feature/Commerce Experience Accelerator/Commerce Emails/New order placed.

  2. Right-click the Message Root and click Insert, Insert from Template.

  3. Go to Templates/Feature/Commerce Experience Accelerator/Emails/Order/Email order header and, in the Item Name field, enter Email order header and click Insert.

  4. In the content tree, click the Message Root for the New Order Placed branch.

  5. On the ribbon, click the Presentation tab and, in the Layout section, click Details.

  6. In the Default section, click Edit and, in the Device Editor dialog box, click the Controls tab.

  7. Click Add and go to Renderings/Feature/Commerce Experience Accelerator/Commerce Emails/Email Order/Email order header, and click Select.

    DeviceEditor.png
  8. With the Email order header selected, click Edit.

  9. In the Control Properties dialog box, in the General section, click Browse beneath Data Source and navigate to the Message Root. Select the Email order header item, and click OK.

  10. In the Control Properties dialog box, click OK and then in the Device Editor dialog box, click OK.

    The email order header controller and its data source are now part of the email message. When a New order placed email message is created, the rendering is included.