Abandoned checkout emails, with Mechanic.

Mechanic is a development and ecommerce automation platform for Shopify. :)

Abandoned checkout emails

This task monitors checkouts in your store, and kicks off an email a configurable number of hours after a checkout is created – if the checkout wasn't completed, and if the customer provided their email address.

Runs Occurs whenever user/checkouts/abandoned is triggered, with a 24 hour delay, Occurs whenever a checkout is created, Occurs whenever a checkout is updated, and Occurs whenever an order is created. Configuration includes email subject, custom message, action button text, cart items header text, primary brand color as hex rgb, and hours to wait before sending email.

15-day free trial – unlimited tasks

Documentation

This task monitors checkouts in your store, and kicks off an email a configurable number of hours after a checkout is created – if the checkout wasn't completed, and if the customer provided their email address.

This task uses the same basic formatting as the standard Shopify abandoned checkout notification template. You may override the action button background with your own brand color, provided it is entered as an RGB hex value (e.g. #abc123).

You can configure the custom message to the customer, the action button text, and the item list header. And since the task will set an email header to the primary locale (language) of your shop, these may be set to your native shop language. Product and variant titles will automatically be output in the language they are listed in your shop.

Even though they don't appear in the task preview, item images will appear in the sent emails.

Important: When using this task, make sure that Shopify's abandoned checkout notifications are disabled to prevent sending duplicate emails to customers.

Developer details

Mechanic is designed to benefit everybody: merchants, customers, developers, agencies, Shopifolks, everybody.

That’s why we make it easy to configure automation without code, why we make it easy to tweak the underlying code once tasks are installed, and why we publish it all here for everyone to learn from.

(By the way, have you seen our documentation? Have you joined the Slack community?)

Open source
View on GitHub to contribute to this task
Subscriptions
user/checkouts/abandoned+{{ options.hours_to_wait_before_sending_email__number_required }}.hours
shopify/checkouts/create
shopify/checkouts/update
shopify/orders/create
Tasks use subscriptions to sign up for specific kinds of events. Learn more
Options
email subject (required), custom message (multiline, required), action button text (required), cart items header text (required), primary brand color as hex rgb, hours to wait before sending email (number, required)
Code
{% assign subject = options.email_subject__required %}
{% assign custom_message = options.custom_message__multiline_required %}
{% assign action_button_text = options.action_button_text__required %}
{% assign cart_items_header_text = options.cart_items_header_text__required %}
{% assign primary_brand_color = options.primary_brand_color_as_hex_rgb | default: "#1990c6" %}

{% assign abandoned_event = "user/checkouts/abandoned" %}

{% if event.topic == "shopify/checkouts/create" or event.topic == "shopify/checkouts/update" %}

  {% if event.preview %}
    {% assign checkout = hash %}
    {% assign checkout["token"] = "00000000000000000000000000000000" %}
    {% assign checkout["abandoned_checkout_url"] = "https://example.shop/checkouts/..." %}
  {% endif %}

  {% comment %}
    When a checkout is created or updated, save it to the cache.
  {% endcomment %}

  {% capture checkout_cache_key %}checkout:{{ checkout.token }}{% endcapture %}

  {% action "cache", "set", checkout_cache_key, checkout %}

  {% if event.topic == "shopify/checkouts/create" %}
    {% comment %}
      ... and if we're looking at the create event, schedule our abandoned checkout followup.
    {% endcomment %}

    {% action "event" %}
      {
        "topic": {{ abandoned_event | json }},
        "data": {
          "checkout_token": {{ checkout.token | json }}
        }
      }
    {% endaction %}
  {% endif %}

{% elsif event.topic == "shopify/orders/create" %}

  {% if event.preview %}
    {% assign order = hash %}
    {% assign order["name"] = "#1234" %}
    {% assign order["checkout_token"] = "00000000000000000000000000000000" %}
  {% endif %}

  {% comment %}
    When an order comes in, cache the order name associated with the checkout token. We'll use this
    to determine if a checkout made it all the way to an order, thus making the checkout *not* abandoned.
  {% endcomment %}

  {% if order.checkout_token != blank %}
    {% capture order_name_cache_key %}checkout_order_name:{{ order.checkout_token }}{% endcapture %}

    {% action "cache", "set", order_name_cache_key, order.name %}
  {% endif %}

{% elsif event.topic == abandoned_event %}

  {% comment %}
    At this stage, our job is to check the cache and see if the checkout converted. If it did, we
    do nothing. If it didn't, and we have checkout data safely in the cache, we fire off an email.
    If somehow the checkout didn't convert but also didn't make it to the cache, we bail.
  {% endcomment %}

  {% assign checkout_token = event.data.checkout_token %}

  {% capture order_name_cache_key %}checkout_order_name:{{ checkout_token }}{% endcapture %}
  {% capture checkout_cache_key %}checkout:{{ checkout_token }}{% endcapture %}

  {% comment %}
    Make sure to render an email during event preview - Mechanic and the merchant both need to
    see what this email will look like.
  {% endcomment %}

  {% if cache[order_name_cache_key] and event.preview != true %}
    {% capture message %}Checkout converted as order {{ cache[order_name_cache_key] }} - not sending an email{% endcapture %}
    {% log message %}

  {% elsif cache[checkout_cache_key].email != blank or event.preview %}
    {% assign checkout = cache[checkout_cache_key] %}

    {% assign line_item_output = array %}

    {% for line_item in checkout.line_items %}
      {% assign product = shop.products[line_item.product_id] %}
      {% assign variant = shop.variants[line_item.variant_id] %}
      {% assign variant_image = product.images | where: "id", variant.image_id | first %}
      {% assign image = variant_image | default: product.image %}

      {%- capture line_item_html -%}
        <tr class="order-list__item">
          <td class="order-list__item__cell">
            <table>
              <td>
                {% if image %}
                  <img src="{{ image.src | img_url: 'compact_cropped' }}" align="left" width="60" height="60" class="order-list__product-image"/>
                {% endif %}
              </td>
              <td class="order-list__product-description-cell">
                <span class="order-list__item-title">{{ line_item.title }}&nbsp;&times;&nbsp;{{ line_item.quantity }}</span><br/>

                {% if line_item.variant_title != 'Default Title' %}
                  <span class="order-list__item-variant">{{ line_item.variant_title }}</span><br/>
                {% endif %}
              </td>
            </table>
          </td>
        </tr>
      {%- endcapture -%}

      {% assign line_item_output = line_item_output | push: line_item_html %}
    {% endfor %}

    {%- capture body -%}
      <!DOCTYPE html>
      <html lang="{{ shop.primary_locale }}">
        <head>
        <title>{{ subject }}</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <meta name="viewport" content="width=device-width">
        <style>
          .body {
            height: 100% !important;
            width: 100% !important;
          }
          .body,
          .button.main-action-cell,
          .container,
          .header.row,
          .row,
          .row.actions,
          .row.content,
          .row.section,
          .order-list__item__cell > table {
            border-spacing: 0;
            border-collapse: collapse;
          }
          .actions__cell,
          .button__cell,
          .content__cell,
          .header__cell,
          .order-list__item__cell,
          .order-list__product-description-cell,
          .section__cell,
          .shop-name__cell,
          .empty-line {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
              'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
              sans-serif;
          }
          .order-list__item,
          .order-list__product-description-cell,
          .header.row,
          .row,
          .row.actions,
          .row.content,
          .row.section {
            width: 100%;
          }
          .shop-name__text > a {
            font-size: 30px;
            color: #333;
            text-decoration: none;
          }
          .header.row {
            margin: 40px 0 20px;
          }
          .container {
            width: 560px;
            text-align: left;
            margin: 0 auto;
          }
          .shop-name__text {
            font-weight: 400;
            font-size: 30px;
            color: #333;
            margin: 0;
          }
          .content__cell {
            padding-bottom: 40px;
            border: 0;
          }
          .row.actions {
            margin-top: 20px;
          }
          .button.main-action-cell {
            float: left;
            margin-right: 15px;
          }
          .button__cell {
            border-radius: 4px;
            background: {{ primary_brand_color }};
          }
          .button__text {
            font-size: 16px;
            text-decoration: none;
            display: block;
            color: #fff;
            padding: 20px 25px;
          }
          .section__cell {
            padding: 40px 0;
          }
          .order-list__item__cell {
            padding-bottom: 15px;
          }
          .order-list__product-image {
            margin-right: 15px;
            border-radius: 8px;
            border: 1px solid #e5e5e5;
          }
          .order-list__item-title {
            font-size: 16px;
            font-weight: 600;
            line-height: 1.4;
            color: #555;
          }
          .order-list__item-variant {
            font-size: 14px;
            color: #999;
          }
          .spacer {
            min-width: 600px;
            height: 0;
          }
          .empty-line {
            line-height: 0;
          }
          a, a:hover, a:active, a:visited {
            color: {{ primary_brand_color }};
          }
        </style>
        </head>
        <body>
          <table class="body">
            <tr>
              <td>
                <table class="header row">
                  <tr>
                    <td class="header__cell">
                      <center>
                        <table class="container">
                          <tr>
                            <td>
                              <table class="row">
                                <tr>
                                  <td class="shop-name__cell">
                                    <h1 class="shop-name__text">
                                      <a href="https://{{ shop.domain }}/">{{ shop.name }}</a>
                                    </h1>
                                  </td>
                                </tr>
                              </table>
                            </td>
                          </tr>
                        </table>
                      </center>
                    </td>
                  </tr>
                </table>
                <table class="row content">
                  <tr>
                    <td class="content__cell">
                      <center>
                        <table class="container">
                          <tr>
                            <td>
                              <p>{{ custom_message | strip | newline_to_br }}</p>
                              <table class="row actions">
                                <tr>
                                  <td class="empty-line">&nbsp;</td>
                                </tr>
                                <tr>
                                  <td class="actions__cell">
                                    <table class="button main-action-cell">
                                      <tr>
                                        <td class="button__cell"><a href="{{ checkout.abandoned_checkout_url }}" class="button__text">{{ action_button_text }}</a></td>
                                      </tr>
                                    </table>
                                  </td>
                                </tr>
                              </table>
                            </td>
                          </tr>
                        </table>
                      </center>
                    </td>
                  </tr>
                </table>
                <table class="row section">
                  <tr>
                    <td class="section__cell">
                      <center>
                        <table class="container">
                          <tr>
                            <td>
                              <h3>{{ cart_items_header_text }}</h3>
                            </td>
                          </tr>
                        </table>
                        <table class="container">
                          <tr>
                            <td>
                              <table class="row">
                                {{ line_item_output | join: newline }}
                              </table>
                            </td>
                          </tr>
                        </table>
                      </center>
                    </td>
                  </tr>
                </table>
              </td>
            </tr>
          </table>
        </body>
      </html>
    {%- endcapture -%}

    {% action "email" %}
      {
        "to": {{ checkout.email | json }},
        "subject": {{ subject | json }},
        "body": {{ body | json }},
        "reply_to": {{ shop.customer_email | json }},
        "from_display_name": {{ shop.name | json }}
      }
    {% endaction %}

  {% else %}
    {% log "Checkout did not convert to an order, but no email address was ever given - unable to send an email" %}
  {% endif %}
{% endif %}
Task code is written in Mechanic Liquid, an extension of open-source Liquid enhanced for automation. Learn more
Defaults
Email subject
Just one more step to finish your order!
Custom message
Hi, you added one or more items to your shopping cart and haven't completed your purchase. You can complete it now while it's still available.
Action button text
Complete your purchase
Cart items header text
Items in your cart
Primary brand color as hex rgb
#cb6015
Hours to wait before sending email
24