Tag customers by order tier, with Mechanic.

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

Tag customers by order tier

Use this task to tag customers by tier, based on how many orders they've placed or by the sum of all their order totals (i.e. total spend). Optionally, configure a customer segment query, limiting the set of customers that are processed. You may also configure an order query, specifying for things like a rolling time period, or fulfillment status. This task is useful for rewarding customers who establish or maintain a specific spend level.

Runs Occurs when a user manually triggers the task. Configuration includes customer tags and order minimums, only keep the customer tag for the highest order minimum, tag customers by, only process customers matching this segment query, only count orders matching this query, run hourly, and run daily.

15-day free trial – unlimited tasks

Documentation

Use this task to tag customers by tier, based on how many orders they've placed or by the sum of all their order totals (i.e. total spend). Optionally, configure a customer segment query, limiting the set of customers that are processed. You may also configure an order query, specifying for things like a rolling time period, or fulfillment status. This task is useful for rewarding customers who establish or maintain a specific spend level.

Configure the tier tags on the left-hand side of the "Customer tags and order minimums" field, and the corresponding minimium numbers on the right.

The options for querying customers and orders use the specific query syntax as found in the "Customers / Segments" and "Orders" sections of the Shopify admin areas respectively.

For example, to only count customers with enabled online accounts who are tagged with "qualifies", use this customer segment query:

customer_account_status = 'ENABLED' AND customer_tags CONTAINS 'qualifies'

To count paid orders from the last 365 days, use this orders query:

financial_status:paid created_at:>={{ "now - 1 year" | date: "%Y-%m-%d" }}

Important: The customer segment query must use the exact casing and syntax as a query that is run from the customer segments admin screen. More information on the the syntax for these can be found here.

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
mechanic/user/trigger
{% if options.run_hourly__boolean %}
  mechanic/scheduler/hourly
{% elsif options.run_daily__boolean %}
  mechanic/scheduler/daily
{% endif %}
Tasks use subscriptions to sign up for specific kinds of events. Learn more
Options
customer tags and order minimums (keyval, number, required) , only keep the customer tag for the highest order minimum (boolean) , tag customers by (choice, o1, count, of, orders, o2, sum, of, order, totals, required) , only process customers matching this segment query , only count orders matching this query , run hourly (boolean) , run daily (boolean)
Code
{% assign customer_tags_and_order_minimums = options.customer_tags_and_order_minimums__keyval_number_required %}
{% assign only_tag_the_highest_order_minimum = options.only_keep_the_customer_tag_for_the_highest_order_minimum__boolean %}
{% assign tag_customers_by = options.tag_customers_by__choice_o1_count_of_orders_o2_sum_of_order_totals_required %}
{% assign customer_segment_query = options.only_process_customers_matching_this_segment_query %}
{% assign only_count_orders_matching_this_query = options.only_count_orders_matching_this_query %}

{% if event.topic == "mechanic/user/trigger" or event.topic contains "mechanic/scheduler/" %}
  {% comment %}
    -- get IDs of all customers who match the segment query
    -- Note: a segment query cannot be null, so if one has not been configured in the task then send an empty string
  {% endcomment %}

  {% assign cursor = nil %}
  {% assign customer_segment_member_ids = array %}

  {% for n in (1..100) %}
    {% capture query %}
      query {
        customerSegmentMembers(
          first: 1000
          after: {{ cursor | json }}
          query: {{ customer_segment_query | default: "" | json }}
        ) {
          pageInfo {
            hasNextPage
            endCursor
          }
          edges {
            node {
              id
            }
          }
        }
      }
    {% endcapture %}

    {% assign result = query | shopify %}

    {% if event.preview %}
      {% capture result_json %}
        {
          "data": {
            "customerSegmentMembers": {
              "edges": [
                {
                  "node": {
                    "id": "gid://shopify/CustomerSegmentMember/1234567890"
                  }
                }
              ]
            }
          }
        }
      {% endcapture %}

      {% assign result = result_json | parse_json %}
    {% endif %}

    {% assign customer_segment_member_ids
      = result.data.customerSegmentMembers.edges
      | map: "node"
      | map: "id"
      | concat: customer_segment_member_ids
    %}

    {% if result.data.customerSegmentMembers.pageInfo.hasNextPage %}
      {% assign cursor = result.data.customerSegmentMembers.pageInfo.endCursor %}
    {% else %}
      {% break %}
    {% endif %}
  {% endfor %}

  {% unless event.preview %}
    {% log count_of_customers_matching_query: customer_segment_member_ids.size %}
  {% endunless %}

  {% for customer_segment_member_id in customer_segment_member_ids %}
    {% comment %}
      -- get customer tags and all of their orders that meet the optional criteria
    {% endcomment %}

    {% assign cursor = nil %}
    {% assign customer_orders = array %}

    {% for n in (1..10) %}
      {% capture query %}
        query {
          customer(id: {{ customer_segment_member_id | remove: "SegmentMember" | json }}) {
            id
            defaultEmailAddress {
              emailAddress
            }
            tags
            orders(
              first: 250
              after: {{ cursor | json }}
              query: {{ only_count_orders_matching_this_query | json }}
            ) {
              pageInfo {
                hasNextPage
                endCursor
              }
              nodes {
                id
                name
                totalPriceSet {
                  shopMoney {
                    amount
                  }
                }
              }
            }
          }
        }
      {% endcapture %}

      {% assign result = query | shopify %}

      {% if event.preview %}
        {% capture result_json %}
          {
            "data": {
              "customer": {
                "id": "gid://shopify/Customer/1234567890",
                "orders": {
                  "nodes": [
                    {
                      "id": "gid://shopify/Order/1234567890",
                      "name": "#PREVIEW",
                      "totalPriceSet": {
                        "shopMoney": {
                          "amount": {{ customer_tags_and_order_minimums.first.last | json }}
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        {% endcapture %}

        {% assign result = result_json | parse_json %}
      {% endif %}

      {% assign customer = result.data.customer %}
      {% assign customer_orders = customer_orders | concat: customer.orders.nodes %}

      {% if result.data.customer.orders.pageInfo.hasNextPage %}
        {% assign cursor = result.data.customer.orders.pageInfo.endCursor %}
      {% else %}
        {% break %}
      {% endif %}
    {% endfor %}

    {% assign customer_orders_value = nil %}

    {% if tag_customers_by == "count_of_orders" %}
      {% assign customer_orders_value = customer_orders.size %}

      {% if event.preview %}
        {% assign customer_orders_value = customer_tags_and_order_minimums.first.last %}
      {% endif %}

    {% elsif tag_customers_by == "sum_of_order_totals" %}
      {% assign customer_orders_value = 0 %}

      {% for order in customer_orders %}
        {% assign customer_orders_value = customer_orders_value | plus: order.totalPriceSet.shopMoney.amount %}
      {% endfor %}
    {% endif %}

    {% unless customer_orders == blank %}
      {% assign customer_orders_names = customer_orders | map: "name" %}

      {% unless event.preview %}
        {% log
          customer_id: customer.id,
          customer_email: customer.defaultEmailAddress.emailAddress,
          customer_tags: customer.tags,
          customer_orders_value: customer_orders_value,
          customer_orders_names: customer_orders_names
        %}
      {% endunless %}
    {% endunless %}

    {% assign best_fitting_tag_minimum = 0 %}
    {% assign best_fitting_tag = nil %}
    {% assign all_fitting_tags = array %}
    {% assign all_possible_tags = array %}

    {% for pair in customer_tags_and_order_minimums %}
      {% assign tag = pair[0] %}
      {% assign tag_minimum = pair[1] %}

      {% assign all_possible_tags[all_possible_tags.size] = tag %}

      {% if customer_orders_value >= tag_minimum %}
        {% assign all_fitting_tags[all_fitting_tags.size] = tag %}

        {% if tag_minimum >= best_fitting_tag_minimum %}
          {% assign best_fitting_tag = tag %}
          {% assign best_fitting_tag_minimum = tag_minimum %}
        {% endif %}
      {% endif %}
    {% endfor %}

    {% assign tags_to_add = array %}
    {% assign tags_to_remove = array %}

    {% if best_fitting_tag %}
      {% unless customer.tags contains best_fitting_tag %}
        {% assign tags_to_add[tags_to_add.size] = best_fitting_tag %}
      {% endunless %}
    {% endif %}

    {% if only_tag_the_highest_order_minimum %}
      {% for tag in all_possible_tags %}
        {% if tag != best_fitting_tag and customer.tags contains tag %}
          {% assign tags_to_remove[tags_to_remove.size] = tag %}
        {% endif %}
      {% endfor %}

    {% else %}
      {% for tag in all_fitting_tags %}
        {% unless tag == best_fitting_tag or customer.tags contains tag %}
          {% assign tags_to_add[tags_to_add.size] = tag %}
        {% endunless %}
      {% endfor %}
    {% endif %}

    {% if tags_to_add != blank or tags_to_remove != blank %}
      {% action "shopify" %}
        mutation {
          {% if tags_to_add != blank %}
            tagsAdd(
              id: {{ customer.id | json }}
              tags: {{ tags_to_add | json }}
            ) {
              node {
                ... on Customer {
                  id
                  defaultEmailAddress {
                    emailAddress
                  }
                  tags
                }
              }
              userErrors {
                field
                message
              }
            }
          {% endif %}

          {% if tags_to_remove != blank %}
            tagsRemove(
              id: {{ customer.id | json }}
              tags: {{ tags_to_remove | json }}
            ) {
              node {
                ... on Customer {
                  id
                  defaultEmailAddress {
                    emailAddress
                  }
                  tags
                }
              }
              userErrors {
                field
                message
              }
            }
          {% endif %}
        }
      {% endaction %}
    {% endif %}
  {% endfor %}
{% endif %}
Task code is written in Mechanic Liquid, an extension of open-source Liquid enhanced for automation. Learn more
Defaults
Customer tags and order minimums
{"10-orders" => "10", "100-orders" => "100"}
Only keep the customer tag for the highest order minimum
true
Tag customers by
count_of_orders
Only process customers matching this segment query
customer_account_status = 'ENABLED' AND customer_tags CONTAINS 'qualifies'
Only count orders matching this query
financial_status:paid created_at:>={{ "now - 1 year" | date: "%Y-%m-%d" }}