diff --git a/docs/auto-tag-customers-with-the-location-of-their-purchase/README.md b/docs/auto-tag-customers-with-the-location-of-their-purchase/README.md
index af197d59..363aebf2 100644
--- a/docs/auto-tag-customers-with-the-location-of-their-purchase/README.md
+++ b/docs/auto-tag-customers-with-the-location-of-their-purchase/README.md
@@ -2,7 +2,7 @@
Tags: Auto-Tag, Customers, Location, Orders
-When an order is created, this task adds the location of the purchase to the customer's tags. Useful for stores with multiple Shopify-powered locations.
+When an order is created, this task adds the location name of the purchase to the customer's tags. Useful for stores with multiple Shopify-powered POS locations.
* View in the task library: [tasks.mechanic.dev/auto-tag-customers-with-the-location-of-their-purchase](https://tasks.mechanic.dev/auto-tag-customers-with-the-location-of-their-purchase)
* Task JSON, for direct import: [task.json](../../tasks/auto-tag-customers-with-the-location-of-their-purchase.json)
@@ -23,18 +23,17 @@ When an order is created, this task adds the location of the purchase to the cus
```liquid
shopify/orders/create
mechanic/user/trigger
-mechanic/shopify/bulk_operation
```
[Learn about event subscriptions in Mechanic](https://learn.mechanic.dev/core/tasks/subscriptions)
## Documentation
-When an order is created, this task adds the location of the purchase to the customer's tags. Useful for stores with multiple Shopify-powered locations.
+When an order is created, this task adds the location name of the purchase to the customer's tags. Useful for stores with multiple Shopify-powered POS locations.
-This task will run for each new order that's created, applying the order location as a customer tag. Optionally, define a tag to be used for orders that have no physical location.
-
-Run this task manually to have Mechanic scan your entire customer base, and each customer's order history.
+This task will run for each new order that's created, applying the POS location name as a customer tag. Optionally, define a tag to be used for online orders.
+
+Run this task manually to have Mechanic scan each customer with an order history.
## Installing this task
diff --git a/docs/auto-tag-customers-with-the-location-of-their-purchase/script.liquid b/docs/auto-tag-customers-with-the-location-of-their-purchase/script.liquid
index 4d2a3d6d..da528bb8 100644
--- a/docs/auto-tag-customers-with-the-location-of-their-purchase/script.liquid
+++ b/docs/auto-tag-customers-with-the-location-of-their-purchase/script.liquid
@@ -1,33 +1,59 @@
+{% assign tag_for_online_orders = options.tag_for_online_orders %}
+
{% if event.topic contains "shopify/orders" %}
+ {% capture query %}
+ query {
+ order(id: {{ order.admin_graphql_api_id | json }}) {
+ id
+ name
+ customer {
+ id
+ tags
+ }
+ retailLocation {
+ name
+ }
+ }
+ }
+ {% endcapture %}
+
+ {% assign result = query | shopify %}
+
{% if event.preview %}
- {% capture order_json %}
+ {% capture result_json %}
{
- "location": {
- "name": "Storefront"
- },
- "customer": {
- "admin_graphql_api_id": "gid://shopify/Customer/1234567890",
- "tags": ""
+ "data": {
+ "order": {
+ "id": "gid://shopify/Order/1234567890",
+ "customer": {
+ "id": "gid://shopify/Customer/1234567890"
+ },
+ "retailLocation": {
+ "name": "Storefront"
+ }
+ }
}
}
{% endcapture %}
- {% assign order = order_json | parse_json %}
+ {% assign result = result_json | parse_json %}
{% endif %}
- {% assign customer_tags = order.customer.tags | split: ", " %}
-
- {% assign location = order.location.name | default: options.tag_for_online_orders %}
+ {% assign order = result.data.order %}
+ {% assign customer = order.customer %}
+ {% assign location_name = order.retailLocation.name | default: tag_for_online_orders %}
- {% unless location == blank or customer_tags contains location %}
+ {% unless customer == blank or location_name == blank or customer.tags contains location_name %}
{% action "shopify" %}
mutation {
tagsAdd(
- id: {{ order.customer.admin_graphql_api_id | json }}
- tags: [{{ location | json }}]
+ id: {{ customer.id | json }}
+ tags: {{ location_name | json }}
) {
node {
... on Customer {
+ id
+ displayName
tags
}
}
@@ -39,88 +65,151 @@
}
{% endaction %}
{% endunless %}
+
{% elsif event.topic == "mechanic/user/trigger" %}
- {% capture bulk_operation_query %}
- query {
- customers(query: "orders_count:>0") {
- edges {
- node {
- __typename
- id
- tags
- orders {
- edges {
- node {
- __typename
- id
- physicalLocation {
- name
- }
- }
- }
+ {% comment %}
+ -- get IDs of all customers who have placed an order
+ {% endcomment %}
+
+ {% assign cursor = nil %}
+ {% assign customer_ids = array %}
+
+ {% for n in (1..100) %}
+ {% capture query %}
+ query {
+ customerSegmentMembers(
+ first: 1000
+ after: {{ cursor | json }}
+ query: "number_of_orders > 0"
+ ) {
+ pageInfo {
+ hasNextPage
+ endCursor
+ }
+ edges {
+ node {
+ id
}
}
}
}
- }
- {% endcapture %}
-
- {% action "shopify" %}
- mutation {
- bulkOperationRunQuery(
- query: {{ bulk_operation_query | json }}
- ) {
- bulkOperation {
- id
- status
- }
- userErrors {
- field
- message
- }
- }
- }
- {% endaction %}
-{% elsif event.topic == "mechanic/shopify/bulk_operation" %}
- {% if event.preview %}
- {% capture objects_jsonl %}
- {"__typename":"Customer","id":"gid:\/\/shopify\/Customer\/1234567890","tags":[]}
- {"__typename":"Order","id":"gid:\/\/shopify\/Order\/1234567890","physicalLocation":{"name":"Storefront"},"__parentId":"gid:\/\/shopify\/Customer\/1234567890"}
{% endcapture %}
- {% assign bulkOperation = hash %}
- {% assign bulkOperation["objects"] = objects_jsonl | parse_jsonl %}
+ {% assign result = query | shopify %}
+
+ {% assign customer_segment_members = result.data.customerSegmentMembers.edges | map: "node" %}
+
+ {% comment %}
+ -- remove the "SegmentMember" portion from IDs for easier use in querying each customer for additional data not available in the segment resource
+ {% endcomment %}
+
+ {% for customer_segment_member in customer_segment_members %}
+ {% assign customer_ids[customer_ids.size] = customer_segment_member.id | remove: "SegmentMember" %}
+ {% endfor %}
+
+ {% if result.data.customerSegmentMembers.pageInfo.hasNextPage %}
+ {% assign cursor = result.data.customerSegmentMembers.pageInfo.endCursor %}
+ {% else %}
+ {% break %}
+ {% endif %}
+ {% endfor %}
+
+ {% unless event.preview %}
+ {% log count_of_customers_who_have_placed_an_order: customer_ids.size %}
+ {% endunless %}
+
+ {% if event.preview %}
+ {% assign customer_ids[0] = "gid://shopify/Customer/1234567890" %}
{% endif %}
- {% assign customers = bulkOperation.objects | where: "__typename", "Customer" %}
- {% assign orders = bulkOperation.objects | where: "__typename", "Order" %}
+ {% for customer_id in customer_ids %}
+ {% comment %}
+ -- get all relevant order data for this customer
+ {% endcomment %}
- {% for customer in customers %}
- {% assign customer_orders = orders | where: "__parentId", customer.id %}
+ {% assign cursor = nil %}
{% assign tags_to_add = array %}
- {% for order in customer_orders %}
- {% assign location = order.physicalLocation.name | default: options.tag_for_online_orders %}
+ {% for n in (1..10) %}
+ {% capture query %}
+ query {
+ customer(id: {{ customer_id | json }}) {
+ id
+ tags
+ orders(
+ first: 250
+ after: {{ cursor | json }}
+ ) {
+ pageInfo {
+ hasNextPage
+ endCursor
+ }
+ nodes {
+ id
+ name
+ retailLocation {
+ name
+ }
+ }
+ }
+ }
+ }
+ {% 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",
+ "retailLocation": {
+ "name": "Storefront"
+ }
+ }
+ ]
+ }
+ }
+ }
+ }
+ {% endcapture %}
- {% if location == blank or customer.tags contains location %}
- {% continue %}
+ {% assign result = result_json | parse_json %}
{% endif %}
- {% assign tags_to_add[tags_to_add.size] = location %}
+ {% assign customer = result.data.customer %}
+
+ {% comment %}
+ -- process orders to see which location names should be set as tags
+ {% endcomment %}
+
+ {% for order in customer.orders.nodes %}
+ {% assign location_name = order.retailLocation.name | default: tag_for_online_orders %}
+
+ {% unless location_name == blank or customer.tags contains location_name or tags_to_add contains location_name %}
+ {% assign tags_to_add = tags_to_add | push: location_name %}
+ {% endunless %}
+ {% endfor %}
+
+ {% if result.data.customer.orders.pageInfo.hasNextPage %}
+ {% assign cursor = result.data.customer.orders.pageInfo.endCursor %}
+ {% else %}
+ {% break %}
+ {% endif %}
{% endfor %}
- {% if tags_to_add != empty %}
+ {% if tags_to_add != blank %}
{% action "shopify" %}
mutation {
tagsAdd(
id: {{ customer.id | json }}
- tags: {{ tags_to_add | uniq | json }}
+ tags: {{ tags_to_add | sort_natural | json }}
) {
- node {
- ... on Customer {
- tags
- }
- }
userErrors {
field
message
diff --git a/docs/copy-order-tags-to-customers/README.md b/docs/copy-order-tags-to-customers/README.md
index 6448d18f..4baac76a 100644
--- a/docs/copy-order-tags-to-customers/README.md
+++ b/docs/copy-order-tags-to-customers/README.md
@@ -2,7 +2,7 @@
Tags: Auto-Tag, Customers
-Run this task to scan all of your customers and their order histories in bulk, copying order and/or product tags to the relevant customer. Optionally, configure a specific set of tags to look for, when scanning. Optionally, choose to remove those tags if a qualifying source can't be found - useful for adding customer tags that expire after ordering!
+Run this task to scan all of your customers and their order histories, copying order and/or product tags to the relevant customer. Optionally, configure a specific set of tags to look for, when scanning. Optionally, choose to remove those tags if a qualifying source can't be found.
* View in the task library: [tasks.mechanic.dev/copy-order-tags-to-customers](https://tasks.mechanic.dev/copy-order-tags-to-customers)
* Task JSON, for direct import: [task.json](../../tasks/copy-order-tags-to-customers.json)
@@ -31,28 +31,26 @@ mechanic/user/trigger
{% if options.run_daily__boolean %}
mechanic/scheduler/daily
{% endif %}
-
-mechanic/shopify/bulk_operation
```
[Learn about event subscriptions in Mechanic](https://learn.mechanic.dev/core/tasks/subscriptions)
## Documentation
-Run this task to scan all of your customers and their order histories in bulk, copying order and/or product tags to the relevant customer. Optionally, configure a specific set of tags to look for, when scanning. Optionally, choose to remove those tags if a qualifying source can't be found - useful for adding customer tags that expire after ordering!
-
-Run this task to scan all of your customers and their order histories in bulk, copying order and/or product tags to the relevant customer. Optionally, configure a specific set of tags to look for, when scanning. Optionally, choose to remove those tags if a qualifying source can't be found.
-
-Both the customer and order query options support Liquid. This means that you can dynamically query for orders, based on things like the current time.
-
-For example, use these options to achieve customer tags that auto-expire a year after the newest qualifying order:
-
-* "Only include orders matching this query": `created_at:>={{ "now" | date: "%s" | minus: 31536000 | date: "%Y-%m-%d" }}`
-* "Only copy these tags": (use whatever order or product tag(s) you want to copy)
-* "Remove those tags if a qualifying source cannot be found": yes
-* "Run daily": yes
-
-Note: the 31536000 value is a quantity of seconds; 31536000 is the number of seconds in a year. To adjust, replace this value with the number of seconds you want to use. For example, 30 days is 2592000 seconds.
+Run this task to scan all of your customers and their order histories, copying order and/or product tags to the relevant customer. Optionally, configure a specific set of tags to look for, when scanning. Optionally, choose to remove those tags if a qualifying source can't be found.
+
+Both the customer and order query options support Liquid. This means that you can dynamically query for orders, based on things like the current date.
+
+For example, use these options to achieve customer tags that auto-expire a year after the newest qualifying order:
+
+* "Only include orders matching this query": `created_at:>={{ "now - 1 year" | date: "%Y-%m-%d" }}`
+* "Only copy these tags": (use whatever order or product tag(s) you want to copy)
+* "Remove those tags if a qualifying source cannot be found": yes
+* "Run daily": yes
+
+**Important:** The customers 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](https://shopify.dev/docs/api/shopifyql/segment-query-language-reference).
+
+For example, to only include customers that have the "subscriber", use this query: `customer_tags CONTAINS 'subscriber'`
## Installing this task
diff --git a/docs/copy-order-tags-to-customers/script.liquid b/docs/copy-order-tags-to-customers/script.liquid
index 5354d52f..7d711cb3 100644
--- a/docs/copy-order-tags-to-customers/script.liquid
+++ b/docs/copy-order-tags-to-customers/script.liquid
@@ -1,200 +1,242 @@
-{% comment %}
- Option order:
+{% assign include_order_tags = options.include_order_tags__boolean %}
+{% assign include_product_tags = options.include_product_tags__boolean %}
+{% assign customer_segment_query = options.only_include_customers_matching_this_query %}
+{% assign only_include_orders_matching_this_query = options.only_include_orders_matching_this_query %}
+{% assign only_copy_these_tags = options.only_copy_these_tags__array %}
+{% assign remove_those_tags_if_a_qualifying_source_cannot_be_found = options.remove_those_tags_if_a_qualifying_source_cannot_be_found__boolean %}
+
+{% unless include_order_tags or include_product_tags %}
+ {% error "Choose at least one of 'Include order tags' and 'Include product tags'. :)" %}
+{% endunless %}
+
+{% if remove_those_tags_if_a_qualifying_source_cannot_be_found and only_copy_these_tags == blank %}
+ {% error "Mechanic can only remove tags it knows about. If you choose 'Remove those tags [...]', you must also fill in 'Only copy these tags'." %}
+{% endif %}
- {{ options.include_order_tags__boolean }}
- {{ options.include_product_tags__boolean }}
+{% 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_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 %}
- {{ options.only_include_customers_matching_this_query }}
- {{ options.only_include_orders_matching_this_query }}
+ {% assign result = query | shopify %}
- {{ options.only_copy_these_tags__array }}
- {{ options.remove_those_tags_if_a_qualifying_source_cannot_be_found__boolean }}
+ {% assign customer_segment_members = result.data.customerSegmentMembers.edges | map: "node" %}
- {{ options.run_daily__boolean }}
-{% endcomment %}
+ {% comment %}
+ -- remove the "SegmentMember" portion from IDs for easier use in querying each customer for additional data not available in the segment resource
+ {% endcomment %}
-{% if options.include_order_tags__boolean == false and options.include_product_tags__boolean == false %}
- {"error": "Choose at least one of 'Include order tags' and 'Include product tags'. :)"}
-{% endif %}
+ {% for customer_segment_member in customer_segment_members %}
+ {% assign customer_ids[customer_ids.size] = customer_segment_member.id | remove: "SegmentMember" %}
+ {% endfor %}
-{% if options.remove_those_tags_if_a_qualifying_source_cannot_be_found__boolean and options.only_copy_these_tags__array == blank %}
- {"error": "Mechanic can only remove tags it knows about. If you choose 'Remove those tags [...]', you must also fill in 'Only copy these tags'."}
-{% endif %}
+ {% if result.data.customerSegmentMembers.pageInfo.hasNextPage %}
+ {% assign cursor = result.data.customerSegmentMembers.pageInfo.endCursor %}
+ {% else %}
+ {% break %}
+ {% endif %}
+ {% endfor %}
-{% if event.topic == "mechanic/user/trigger" or event.topic contains "mechanic/scheduler/" %}
- {% capture bulk_operation_query %}
- query {
- customers(query: {{ options.only_include_customers_matching_this_query | json }}) {
- edges {
- node {
- __typename
+ {% unless event.preview %}
+ {% log count_of_customers_matching_query: customer_ids.size %}
+ {% endunless %}
+
+ {% if event.preview %}
+ {% assign customer_ids[0] = "gid://shopify/Customer/1234567890" %}
+ {% endif %}
+
+ {% for customer_id in customer_ids %}
+ {% comment %}
+ -- get all relevant order data for this customer
+ {% endcomment %}
+
+ {% assign cursor = nil %}
+ {% assign tags_should_have = array %}
+
+ {% for n in (1..10) %}
+ {% capture query %}
+ query {
+ customer(id: {{ customer_id | json }}) {
id
tags
- orders(query: {{ options.only_include_orders_matching_this_query | json }}) {
- edges {
- node {
- __typename
- id
- {% if options.include_order_tags__boolean %}
- tags
- {% endif %}
- {% if options.include_product_tags__boolean %}
- lineItems {
- edges {
- node {
- __typename
- id
- product {
- __typename
- id
- tags
+ orders(
+ first: 250
+ after: {{ cursor | json }}
+ ) {
+ pageInfo {
+ hasNextPage
+ endCursor
+ }
+ nodes {
+ id
+ {% if include_order_tags %}tags{% endif %}
+ {% if include_product_tags %}
+ lineItems(first: 250) {
+ nodes {
+ product {
+ tags
+ }
+ }
+ }
+ {% endif %}
+ }
+ }
+ }
+ }
+ {% 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",
+ "tags": {{ only_copy_these_tags.first | default: "order preview tag" | json }},
+ "lineItems": {
+ "nodes": [
+ {
+ "product": {
+ "tags": {{ only_copy_these_tags.first | default: "product preview tag" | json }}
+ }
}
- }
+ ]
}
}
- {% endif %}
+ ]
}
}
}
}
- }
- }
- }
- {% endcapture %}
-
- {% action "shopify" %}
- mutation {
- bulkOperationRunQuery(
- query: {{ bulk_operation_query | json }}
- ) {
- bulkOperation {
- id
- status
- }
- userErrors {
- field
- message
- }
- }
- }
- {% endaction %}
-{% elsif event.topic == "mechanic/shopify/bulk_operation" %}
- {% assign customer_ids = bulkOperation.objects | where: "__typename", "Customer" | map: "id" %}
+ {% endcapture %}
- {% assign tags_available_by_customer_id = hash %}
+ {% assign result = result_json | parse_json %}
+ {% endif %}
- {% if options.include_order_tags__boolean %}
- {% assign orders = bulkOperation.objects | where: "__typename", "Order" %}
- {% for order in orders %}
- {% assign customer = order.__parent %}
+ {% assign customer = result.data.customer %}
- {% if tags_available_by_customer_id[customer.id] == nil %}
- {% assign tags_available_by_customer_id[customer.id] = array %}
- {% endif %}
+ {% comment %}
+ -- process order tags and/or product tags to see which a customer should have based on task configuration
+ {% endcomment %}
- {% assign tags_available_by_customer_id[customer.id] = tags_available_by_customer_id[customer.id] | concat: order.tags %}
- {% endfor %}
- {% endif %}
+ {% for order in customer.orders.nodes %}
+ {% if include_order_tags %}
+ {% for tag in order.tags %}
+ {% if only_copy_these_tags != blank %}
+ {% unless only_copy_these_tags contains tag %}
+ {% continue %}
+ {% endunless %}
+ {% endif %}
- {% if options.include_product_tags__boolean %}
- {% assign lineItems = bulkOperation.objects | where: "__typename", "LineItem" | where: "product" %}
- {% for lineItem in lineItems %}
- {% assign product = lineItem.product %}
- {% assign order = lineItem.__parent %}
- {% assign customer = order.__parent %}
+ {% assign tags_should_have = tags_should_have | push: tag %}
+ {% endfor %}
+ {% endif %}
- {% if tags_available_by_customer_id[customer.id] == nil %}
- {% assign tags_available_by_customer_id[customer.id] = array %}
- {% endif %}
+ {% if include_product_tags %}
+ {% for line_item in order.lineItems.nodes %}
+ {% for tag in line_item.product.tags %}
+ {% if only_copy_these_tags != blank %}
+ {% unless only_copy_these_tags contains tag %}
+ {% continue %}
+ {% endunless %}
+ {% endif %}
+
+ {% assign tags_should_have = tags_should_have | push: tag %}
+ {% endfor %}
+ {% endfor %}
+ {% endif %}
+ {% endfor %}
- {% assign tags_available_by_customer_id[customer.id] = tags_available_by_customer_id[customer.id] | concat: product.tags %}
+ {% if result.data.customer.orders.pageInfo.hasNextPage %}
+ {% assign cursor = result.data.customer.orders.pageInfo.endCursor %}
+ {% else %}
+ {% break %}
+ {% endif %}
{% endfor %}
- {% endif %}
- {% if event.preview %}
- {% assign preview_tags = "foo,bar,baz" | split: "," %}
- {% assign customer_ids[0] = "gid://shopify/Customer/1234567890" %}
- {% assign tags_available_by_customer_id["gid://shopify/Customer/1234567890"] = options.only_copy_these_tags__array | default: preview_tags %}
- {% assign bulkOperation = hash %}
- {% assign bulkOperation["objects"] = array %}
- {% assign bulkOperation["objects"][0] = '{"id":"gid://shopify/Customer/1234567890","tags":""}' | parse_json %}
- {% endif %}
+ {% comment %}
+ -- determine which tags should be added or removed from the customer
+ {% endcomment %}
- {% for customer_id in customer_ids %}
- {% assign customer = bulkOperation.objects | where: "id", customer_id | first %}
-
- {% assign tags_available = tags_available_by_customer_id[customer_id] | default: array %}
+ {% assign tags_to_add = array %}
+ {% assign tags_to_remove = array %}
- {% if options.only_copy_these_tags__array == blank %}
- {% assign tags_applicable = tags_available %}
- {% else %}
- {% assign tags_applicable = array %}
+ {% for tag in tags_should_have %}
+ {% unless customer.tags contains tag or tags_to_add contains tag %}
+ {% assign tags_to_add = tags_to_add | push: tag %}
+ {% endunless %}
+ {% endfor %}
- {% for whitelisted_tag in options.only_copy_these_tags__array %}
- {% if tags_available contains whitelisted_tag %}
- {% assign tags_applicable[tags_applicable.size] = whitelisted_tag %}
+ {% if remove_those_tags_if_a_qualifying_source_cannot_be_found %}
+ {% for tag in only_copy_these_tags %}
+ {% if customer.tags contains tag %}
+ {% unless tags_should_have contains tag %}
+ {% assign tags_to_remove = tags_to_remove | push: tag %}
+ {% endunless %}
{% endif %}
{% endfor %}
{% endif %}
- {% assign tags_applicable = tags_applicable | sort | uniq %}
-
- {% if tags_applicable != empty %}
- {% assign tags_to_copy = array %}
-
- {% for tag in tags_applicable %}
- {% unless customer.tags contains tag %}
- {% assign tags_to_copy[tags_to_copy.size] = tag %}
- {% endunless %}
- {% endfor %}
-
- {% if tags_to_copy == empty %}
- {% capture log_message %}Customer {{ customer.id }} already has all applicable tags ({{ tags_applicable | join: ", " }}); nothing to do.{% endcapture %}
- {"log": {{ log_message | json }}}
- {% else %}
- {% action "shopify" %}
- mutation {
- tagsAdd(
- id: {{ customer.id | json }}
- tags: {{ tags_to_copy | json }}
- ) {
- userErrors {
- field
- message
- }
+ {% if tags_to_add != blank %}
+ {% action "shopify" %}
+ mutation {
+ tagsAdd(
+ id: {{ customer.id | json }}
+ tags: {{ tags_to_add | sort_natural | json }}
+ ) {
+ userErrors {
+ field
+ message
}
}
- {% endaction %}
- {% endif %}
+ }
+ {% endaction %}
{% endif %}
- {% if options.remove_those_tags_if_a_qualifying_source_cannot_be_found__boolean and options.only_copy_these_tags__array != blank %}
- {% assign tags_to_remove = array %}
-
- {% for tag in options.only_copy_these_tags__array %}
- {% unless tags_applicable contains tag %}
- {% if customer.tags contains tag %}
- {% assign tags_to_remove[tags_to_remove.size] = tag %}
- {% endif %}
- {% endunless %}
- {% endfor %}
-
- {% if tags_to_remove != empty %}
- {% action "shopify" %}
- mutation {
- tagsRemove(
- id: {{ customer.id | json }}
- tags: {{ tags_to_remove | json }}
- ) {
- userErrors {
- field
- message
- }
+ {% if tags_to_remove != blank %}
+ {% action "shopify" %}
+ mutation {
+ tagsRemove(
+ id: {{ customer.id | json }}
+ tags: {{ tags_to_remove | sort_natural | json }}
+ ) {
+ userErrors {
+ field
+ message
}
}
- {% endaction %}
- {% endif %}
+ }
+ {% endaction %}
{% endif %}
-
{% endfor %}
{% endif %}
diff --git a/docs/email-a-report-of-customers-who-havent-ordered-in-x-days/README.md b/docs/email-a-report-of-customers-who-havent-ordered-in-x-days/README.md
index 62310628..1d16c196 100644
--- a/docs/email-a-report-of-customers-who-havent-ordered-in-x-days/README.md
+++ b/docs/email-a-report-of-customers-who-havent-ordered-in-x-days/README.md
@@ -27,15 +27,10 @@ Use this task to request or schedule an email digest of customers, having a cert
## Subscriptions
```liquid
-mechanic/shopify/bulk_operation
-
mechanic/user/trigger
-
{% if options.send_email_daily__boolean %}
mechanic/scheduler/daily
-{% endif %}
-
-{% if options.send_email_every_monday__boolean %}
+{% elsif options.send_email_every_monday__boolean %}
mechanic/scheduler/monday
{% endif %}
```
@@ -46,9 +41,7 @@ mechanic/user/trigger
Use this task to request or schedule an email digest of customers, having a certain tag, who haven't placed an order in a certain number of days.
-Run this task manually to request a report immediately, or configure the task to run automatically on a schedule.
-
-[YouTube: Watch the development video!](https://youtu.be/y1fV3aQrS1g)
+Run this task manually to request a report immediately, or configure the task to run automatically on a schedule.
## Installing this task
diff --git a/docs/email-a-report-of-customers-who-havent-ordered-in-x-days/script.liquid b/docs/email-a-report-of-customers-who-havent-ordered-in-x-days/script.liquid
index 044e5ca0..1d946785 100644
--- a/docs/email-a-report-of-customers-who-havent-ordered-in-x-days/script.liquid
+++ b/docs/email-a-report-of-customers-who-havent-ordered-in-x-days/script.liquid
@@ -1,125 +1,180 @@
+{% assign required_customer_tag = options.required_customer_tag__required %}
+{% assign interval_days = options.include_customers_who_have_not_placed_an_order_in_this_many_days__number_required %}
+{% assign email_subject_prefix = options.email_subject_prefix__required %}
+{% assign email_recipient = options.email_recipient__required_email %}
+{% assign send_email_anyway_if_no_customers_are_found = options.send_email_anyway_if_no_customers_are_found__boolean %}
+
+{% if interval_days <= 0 %}
+ {% error "The number of days must be greater than 0. :)" %}
+{% endif %}
+
+{% assign now_s = "now" | date: "%s" | times: 1 %}
+
{% if event.topic == "mechanic/user/trigger" or event.topic contains "mechanic/scheduler/" %}
- {% capture bulk_operation_query %}
- query {
- customers(
- query: {{ options.required_customer_tag__required | json | prepend: "orders_count:>=1 AND tag:" | json }}
- ) {
- edges {
- node {
- displayName
- email
- id
- legacyResourceId
+ {% comment %}
+ -- get IDs of all customers with the required tag and who have placed an order over X days
+ {% endcomment %}
- lastOrder {
- createdAt
- legacyResourceId
- name
+ {%- capture customer_segment_query -%}
+ customer_tags CONTAINS '{{ required_customer_tag }}' AND orders_placed(until: -{{ interval_days }}d) = true
+ {%- endcapture -%}
+
+ {% assign cursor = nil %}
+ {% assign customer_ids = array %}
+
+ {% for n in (1..100) %}
+ {% capture query %}
+ query {
+ customerSegmentMembers(
+ first: 1000
+ after: {{ cursor | json }}
+ query: {{ customer_segment_query | json }}
+ ) {
+ pageInfo {
+ hasNextPage
+ endCursor
+ }
+ edges {
+ node {
+ id
}
}
}
}
- }
- {% endcapture %}
+ {% endcapture %}
- {% action "shopify" %}
- mutation {
- bulkOperationRunQuery(
- query: {{ bulk_operation_query | json }}
- ) {
- bulkOperation {
- id
- status
- }
- userErrors {
- field
- message
- }
- }
- }
- {% endaction %}
-{% elsif event.topic == "mechanic/shopify/bulk_operation" %}
- {% assign now_s = "now" | date: "%s" | times: 1 %}
- {% assign interval_d = options.include_customers_who_have_not_placed_an_order_in_this_many_days__number_required %}
- {% assign interval_s = interval_d | times: 24 | times: 60 | times: 60 %}
- {% assign order_time_threshold_s = now_s | minus: interval_s %}
-
- {% if interval_d <= 0 %}
- {% error "The number of days must be greater than 0. :)" %}
- {% endif %}
+ {% assign result = query | shopify %}
+
+ {% assign customer_segment_members = result.data.customerSegmentMembers.edges | map: "node" %}
+
+ {% comment %}
+ -- remove the "SegmentMember" portion from IDs for easier use in querying each customer for additional data not available in the segment resource
+ {% endcomment %}
+
+ {% for customer_segment_member in customer_segment_members %}
+ {% assign customer_ids[customer_ids.size] = customer_segment_member.id | remove: "SegmentMember" %}
+ {% endfor %}
+
+ {% if result.data.customerSegmentMembers.pageInfo.hasNextPage %}
+ {% assign cursor = result.data.customerSegmentMembers.pageInfo.endCursor %}
+ {% else %}
+ {% break %}
+ {% endif %}
+ {% endfor %}
+
+ {% unless event.preview %}
+ {% log count_of_customers_who_qualify: customer_ids.size %}
+ {% endunless %}
{% if event.preview %}
- {% capture objects_json %}
- [
- {
- "displayName": "Alpha Beta",
- "email": "customer@example.com",
- "id": "gid://shopify/Customer/1234567890",
- "legacyResourceId": "1234567890",
- "lastOrder": {
- "createdAt": {{ now_s | minus: interval_s | minus: 432000 | date: "%Y-%m-%dT%H:%M:%SZ" | json }},
- "legacyResourceId": "1234567890",
- "name": "#1000"
+ {% assign customer_ids[0] = "gid://shopify/Customer/1234567890" %}
+ {% endif %}
+
+ {% comment %}
+ -- get additional customer and last order data and save list item in an array for email body processing
+ {% endcomment %}
+
+ {% assign list_items = array %}
+
+ {% for customer_id in customer_ids %}
+ {% capture query %}
+ query {
+ customer(id: {{ customer_id | json }}) {
+ legacyResourceId
+ displayName
+ email
+ lastOrder {
+ legacyResourceId
+ name
+ processedAt
}
}
- ]
+ }
{% endcapture %}
- {% assign bulkOperation = hash %}
- {% assign bulkOperation["objects"] = objects_json | parse_json %}
- {% endif %}
+ {% assign result = query | shopify %}
- {% assign qualifying_customers = array %}
+ {% if event.preview %}
+ {% capture result_json %}
+ {
+ "data": {
+ "customer": {
+ "legacyResourceId": "1234567890",
+ "displayName": "Alpha Beta",
+ "email": "customer@example.com",
+ "lastOrder": {
+ "processedAt": {{ interval_days | times: -86400 | plus: now_s | minus: 432000 | date: "%Y-%m-%dT%H:%M:%SZ" | json }},
+ "legacyResourceId": "1234567890",
+ "name": "#PREVIEW"
+ }
+ }
+ }
+ }
+ {% endcapture %}
- {% for customer in bulkOperation.objects %}
- {% assign order_time_s = customer.lastOrder.createdAt | date: "%s" | times: 1 %}
- {% if order_time_s < order_time_threshold_s %}
- {% assign qualifying_customers[qualifying_customers.size] = customer %}
+ {% assign result = result_json | parse_json %}
{% endif %}
+
+ {% assign customer = result.data.customer %}
+
+ {% assign lastOrder_days_ago
+ = customer.lastOrder.processedAt
+ | date: "%s"
+ | minus: now_s
+ | divided_by: -86400
+ %}
+
+ {% capture list_item %}
+
+ {{ customer.displayName | default: "(unnamed)" }}
+ ({{ customer.email | default: "(no email)" }})
+
+ Last ordered on {{ customer.lastOrder.processedAt | date: "%Y-%m-%d" }},
+ {{ lastOrder_days_ago }} {{ lastOrder_days_ago | pluralize: "day", "days" }} ago
+ – {{ customer.lastOrder.name }}
+
+ {% endcapture %}
+
+ {% assign list_items = list_items | push: list_item %}
{% endfor %}
+ {% comment %}
+ -- generate the email subject and body with the list item data from qualifying customers
+ {% endcomment %}
+
{% capture email_subject %}
- {{ options.email_subject_prefix__required }} {{ qualifying_customers.size }} {{ qualifying_customers.size | pluralize: "customer", "customers" }} found
+ {{ email_subject_prefix }} {{ list_items.size }} {{ list_items.size | pluralize: "customer", "customers" }} found
{% endcapture %}
- {% if qualifying_customers != empty %}
+ {% if list_items != blank %}
{% capture email_body %}
Hello,
- Filtering for customers tagged {{ options.required_customer_tag__required | json }},
- the following {{ qualifying_customers.size }} {{ qualifying_customers.size | pluralize: "customer was", "customers were" }}
- found to have not placed an order in the last {{ interval_d }} {{ interval_d | pluralize: "day", "days" }}:
+ Filtering for customers tagged {{ required_customer_tag | json }},
+ the following {{ list_items.size }} {{ list_items.size | pluralize: "customer was", "customers were" }}
+ found to have not placed an order in the last {{ interval_days }} {{ interval_days | pluralize: "day", "days" }}:
- {% for customer in qualifying_customers %}
- {% assign lastOrder_days_ago = customer.lastOrder.createdAt | date: "%s" | times: 1 | minus: now_s | times: -1 | divided_by: 60 | divided_by: 60 | divided_by: 24 %}
- -
- {{ customer.displayName | default: "(unnamed)" }}
- ({{ customer.email | default: "(no email)" }})
-
- Last ordered on {{ customer.lastOrder.createdAt | date: "%Y-%m-%d" }},
- {{ lastOrder_days_ago }} {{ lastOrder_days_ago | pluralize: "day", "days" }} ago
- – {{ customer.lastOrder.name }}
-
- {% endfor %}
+ {{ list_items | join: newline }}
Thanks,
- Mechanic, for {{ shop.name }}
{% endcapture %}
{% action "email" %}
{
- "to": {{ options.email_recipient__required_email | json }},
+ "to": {{ email_recipient | json }},
"subject": {{ email_subject | strip | json }},
"body": {{ email_body | json }},
"reply_to": {{ shop.customer_email | json }},
"from_display_name": {{ shop.name | json }}
}
{% endaction %}
- {% elsif options.send_email_anyway_if_no_customers_are_found__boolean %}
+
+ {% elsif send_email_anyway_if_no_customers_are_found %}
{% capture email_body %}
Hello,
- We didn't find any customers (tagged {{ options.required_customer_tag__required | json }}) having zero orders placed in the last {{ interval_d }} day(s).
+ We didn't find any customers (tagged {{ required_customer_tag | json }}) having zero orders placed in the last {{ interval_days }} day(s).
Thanks,
- Mechanic, for {{ shop.name }}
@@ -127,7 +182,7 @@
{% action "email" %}
{
- "to": {{ options.email_recipient__required_email | json }},
+ "to": {{ email_recipient | json }},
"subject": {{ email_subject | strip | json }},
"body": {{ email_body | unindent | strip | newline_to_br | json }},
"reply_to": {{ shop.customer_email | json }},
diff --git a/docs/tag-and-segment-customers-by-days-since-last-order/README.md b/docs/tag-and-segment-customers-by-days-since-last-order/README.md
index cf85f3b4..3482d44f 100644
--- a/docs/tag-and-segment-customers-by-days-since-last-order/README.md
+++ b/docs/tag-and-segment-customers-by-days-since-last-order/README.md
@@ -27,7 +27,6 @@ This task will run daily to query all customers in your shop who have placed an
## Subscriptions
```liquid
-mechanic/shopify/bulk_operation
mechanic/scheduler/daily
mechanic/user/trigger
```
diff --git a/docs/tag-and-segment-customers-by-days-since-last-order/script.liquid b/docs/tag-and-segment-customers-by-days-since-last-order/script.liquid
index c24ef1fa..869052c6 100644
--- a/docs/tag-and-segment-customers-by-days-since-last-order/script.liquid
+++ b/docs/tag-and-segment-customers-by-days-since-last-order/script.liquid
@@ -31,59 +31,108 @@
{% endif %}
{% if event.topic == "mechanic/user/trigger" or event.topic contains "mechanic/scheduler/" %}
- {% capture bulk_operation_query %}
- query {
- customers(
- query: "orders_count:>0"
- ) {
- edges {
- node {
- id
- tags
- lastOrder {
- processedAt
+ {% comment %}
+ -- get IDs of all customers who have placed an order
+ {% endcomment %}
+
+ {% assign cursor = nil %}
+ {% assign customer_ids = array %}
+
+ {% for n in (1..100) %}
+ {% capture query %}
+ query {
+ customerSegmentMembers(
+ first: 1000
+ after: {{ cursor | json }}
+ query: "number_of_orders > 0"
+ ) {
+ pageInfo {
+ hasNextPage
+ endCursor
+ }
+ edges {
+ node {
+ id
}
}
}
}
- }
- {% endcapture %}
-
- {% action "shopify" %}
- mutation {
- bulkOperationRunQuery(
- query: {{ bulk_operation_query | json }}
- ) {
- bulkOperation {
+ {% endcapture %}
+
+ {% assign result = query | shopify %}
+
+ {% assign customer_segment_members = result.data.customerSegmentMembers.edges | map: "node" %}
+
+ {% comment %}
+ -- remove the "SegmentMember" portion from IDs for easier use in querying each customer for additional data not available in the segment resource
+ {% endcomment %}
+
+ {% for customer_segment_member in customer_segment_members %}
+ {% assign customer_ids[customer_ids.size] = customer_segment_member.id | remove: "SegmentMember" %}
+ {% endfor %}
+
+ {% if result.data.customerSegmentMembers.pageInfo.hasNextPage %}
+ {% assign cursor = result.data.customerSegmentMembers.pageInfo.endCursor %}
+ {% else %}
+ {% break %}
+ {% endif %}
+ {% endfor %}
+
+ {% unless event.preview %}
+ {% log
+ count_of_customers_who_have_placed_an_order: customer_ids.size,
+ tag_rules: tag_rules
+ %}
+ {% endunless %}
+
+ {% if event.preview %}
+ {% assign customer_ids[0] = "gid://shopify/Customer/1234567890" %}
+ {% endif %}
+
+ {% for customer_id in customer_ids %}
+ {% comment %}
+ -- get customer tags and last order date
+ {% endcomment %}
+
+ {% capture query %}
+ query {
+ customer(id: {{ customer_id | json }}) {
id
- status
- }
- userErrors {
- field
- message
+ tags
+ lastOrder {
+ processedAt
+ }
}
}
- }
- {% endaction %}
+ {% endcapture %}
-{% elsif event.topic == "mechanic/shopify/bulk_operation" %}
- {% log tag_rules: tag_rules %}
+ {% assign result = query | shopify %}
- {% if event.preview %}
- {% capture bulkOperation_objects_jsonl %}
- {"id":"gid:\/\/shopify\/Customer\/1234567890","lastOrder":{"processedAt":{{ tag_rules[0].threshold_s | json }}}}
- {% endcapture %}
+ {% if event.preview %}
+ {% capture result_json %}
+ {
+ "data": {
+ "customer": {
+ "id": "gid://shopify/Customer/1234567890",
+ "lastOrder": {
+ "processedAt": {{ tag_rules[0].threshold_s | json }}
+ }
+ }
+ }
+ }
+ {% endcapture %}
- {% assign bulkOperation = hash %}
- {% assign bulkOperation["objects"] = bulkOperation_objects_jsonl | parse_jsonl %}
- {% endif %}
+ {% assign result = result_json | parse_json %}
+ {% endif %}
- {% assign customers = bulkOperation.objects %}
+ {% assign customer = result.data.customer %}
- {% for customer in customers %}
- {% assign tag_should_have = nil %}
+ {% comment %}
+ -- determine the tag that the customer should have based their last order date
+ {% endcomment %}
{% assign customer_last_order_processed_at_s = customer.lastOrder.processedAt | date: "%s" | times: 1 %}
+ {% assign tag_should_have = nil %}
{% for tag_rule in tag_rules %}
{% if customer_last_order_processed_at_s <= tag_rule.threshold_s %}
@@ -103,7 +152,6 @@
... on Customer {
id
email
- tags
lastOrder {
name
processedAt
@@ -119,12 +167,18 @@
{% endaction %}
{% endunless %}
+ {% comment %}
+ -- remove any other configured tags from the customer if needed
+ {% endcomment %}
+
{% assign tags_to_remove = array %}
- {% for customer_tag in customer.tags %}
- {% if configured_tags contains customer_tag and tag_should_have != customer_tag %}
- {% assign tags_to_remove = tags_to_remove | push: customer_tag %}
- {% endif %}
+ {% for configured_tag in configured_tags %}
+ {% unless configured_tag == tag_should_have %}
+ {% if customer.tags contains configured_tag %}
+ {% assign tags_to_remove = tags_to_remove | push: configured_tag %}
+ {% endif %}
+ {% endunless %}
{% endfor %}
{% if tags_to_remove != blank %}
@@ -138,7 +192,6 @@
... on Customer {
id
email
- tags
lastOrder {
name
processedAt
@@ -155,11 +208,15 @@
{% endif %}
{% endfor %}
+ {% comment %}
+ -- if task option enabled, then create customer segments for each configured tag (if they don't already exist)
+ {% endcomment %}
+
{% if create_customer_segments_by_tag %}
{% assign cursor = nil %}
{% assign segments = array %}
- {% for n in (1..100) %}
+ {% for n in (1..10) %}
{% capture query %}
query {
segments(
@@ -181,29 +238,11 @@
{% assign result = query | shopify %}
- {% if event.preview %}
- {% capture result_json %}
- {
- "data": {
- "segments": {
- "nodes": []
- }
- }
- }
- {% endcapture %}
-
- {% assign result = result_json | parse_json %}
- {% endif %}
-
- {% assign segments = segemnts | concat: result.data.segments.nodes %}
- {% comment %}
-
{% assign segments
= result.data.segments.nodes
| default: array
| concat: segments
%}
- {% endcomment %}
{% if result.data.segments.pageInfo.hasNextPage %}
{% assign cursor = result.data.segments.pageInfo.endCursor %}
diff --git a/tasks/auto-tag-customers-with-the-location-of-their-purchase.json b/tasks/auto-tag-customers-with-the-location-of-their-purchase.json
index a0e33299..0b151723 100644
--- a/tasks/auto-tag-customers-with-the-location-of-their-purchase.json
+++ b/tasks/auto-tag-customers-with-the-location-of-their-purchase.json
@@ -1,5 +1,5 @@
{
- "docs": "When an order is created, this task adds the location of the purchase to the customer's tags. Useful for stores with multiple Shopify-powered locations.\n\nThis task will run for each new order that's created, applying the order location as a customer tag. Optionally, define a tag to be used for orders that have no physical location.\r\n\r\nRun this task manually to have Mechanic scan your entire customer base, and each customer's order history.",
+ "docs": "When an order is created, this task adds the location name of the purchase to the customer's tags. Useful for stores with multiple Shopify-powered POS locations.\n\nThis task will run for each new order that's created, applying the POS location name as a customer tag. Optionally, define a tag to be used for online orders.\n\nRun this task manually to have Mechanic scan each customer with an order history.",
"halt_action_run_sequence_on_error": false,
"name": "Auto-tag customers with the location of their purchase",
"online_store_javascript": null,
@@ -8,13 +8,12 @@
},
"order_status_javascript": null,
"perform_action_runs_in_sequence": false,
- "script": "{% if event.topic contains \"shopify/orders\" %}\n {% if event.preview %}\n {% capture order_json %}\n {\n \"location\": {\n \"name\": \"Storefront\"\n },\n \"customer\": {\n \"admin_graphql_api_id\": \"gid://shopify/Customer/1234567890\",\n \"tags\": \"\"\n }\n }\n {% endcapture %}\n\n {% assign order = order_json | parse_json %}\n {% endif %}\n\n {% assign customer_tags = order.customer.tags | split: \", \" %}\n\n {% assign location = order.location.name | default: options.tag_for_online_orders %}\n\n {% unless location == blank or customer_tags contains location %}\n {% action \"shopify\" %}\n mutation {\n tagsAdd(\n id: {{ order.customer.admin_graphql_api_id | json }}\n tags: [{{ location | json }}]\n ) {\n node {\n ... on Customer {\n tags\n }\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endunless %}\n{% elsif event.topic == \"mechanic/user/trigger\" %}\n {% capture bulk_operation_query %}\n query {\n customers(query: \"orders_count:>0\") {\n edges {\n node {\n __typename\n id\n tags\n orders {\n edges {\n node {\n __typename\n id\n physicalLocation {\n name\n }\n }\n }\n }\n }\n }\n }\n }\n {% endcapture %}\n\n {% action \"shopify\" %}\n mutation {\n bulkOperationRunQuery(\n query: {{ bulk_operation_query | json }}\n ) {\n bulkOperation {\n id\n status\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n{% elsif event.topic == \"mechanic/shopify/bulk_operation\" %}\n {% if event.preview %}\n {% capture objects_jsonl %}\n {\"__typename\":\"Customer\",\"id\":\"gid:\\/\\/shopify\\/Customer\\/1234567890\",\"tags\":[]}\n {\"__typename\":\"Order\",\"id\":\"gid:\\/\\/shopify\\/Order\\/1234567890\",\"physicalLocation\":{\"name\":\"Storefront\"},\"__parentId\":\"gid:\\/\\/shopify\\/Customer\\/1234567890\"}\n {% endcapture %}\n\n {% assign bulkOperation = hash %}\n {% assign bulkOperation[\"objects\"] = objects_jsonl | parse_jsonl %}\n {% endif %}\n\n {% assign customers = bulkOperation.objects | where: \"__typename\", \"Customer\" %}\n {% assign orders = bulkOperation.objects | where: \"__typename\", \"Order\" %}\n\n {% for customer in customers %}\n {% assign customer_orders = orders | where: \"__parentId\", customer.id %}\n {% assign tags_to_add = array %}\n\n {% for order in customer_orders %}\n {% assign location = order.physicalLocation.name | default: options.tag_for_online_orders %}\n\n {% if location == blank or customer.tags contains location %}\n {% continue %}\n {% endif %}\n\n {% assign tags_to_add[tags_to_add.size] = location %}\n {% endfor %}\n\n {% if tags_to_add != empty %}\n {% action \"shopify\" %}\n mutation {\n tagsAdd(\n id: {{ customer.id | json }}\n tags: {{ tags_to_add | uniq | json }}\n ) {\n node {\n ... on Customer {\n tags\n }\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n {% endfor %}\n{% endif %}",
+ "script": "{% assign tag_for_online_orders = options.tag_for_online_orders %}\n\n{% if event.topic contains \"shopify/orders\" %}\n {% capture query %}\n query {\n order(id: {{ order.admin_graphql_api_id | json }}) {\n id\n name\n customer {\n id\n tags\n }\n retailLocation {\n name\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"order\": {\n \"id\": \"gid://shopify/Order/1234567890\",\n \"customer\": {\n \"id\": \"gid://shopify/Customer/1234567890\"\n },\n \"retailLocation\": {\n \"name\": \"Storefront\"\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% assign order = result.data.order %}\n {% assign customer = order.customer %}\n {% assign location_name = order.retailLocation.name | default: tag_for_online_orders %}\n\n {% unless customer == blank or location_name == blank or customer.tags contains location_name %}\n {% action \"shopify\" %}\n mutation {\n tagsAdd(\n id: {{ customer.id | json }}\n tags: {{ location_name | json }}\n ) {\n node {\n ... on Customer {\n id\n displayName\n tags\n }\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endunless %}\n\n{% elsif event.topic == \"mechanic/user/trigger\" %}\n {% comment %}\n -- get IDs of all customers who have placed an order\n {% endcomment %}\n\n {% assign cursor = nil %}\n {% assign customer_ids = array %}\n\n {% for n in (1..100) %}\n {% capture query %}\n query {\n customerSegmentMembers(\n first: 1000\n after: {{ cursor | json }}\n query: \"number_of_orders > 0\"\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n node {\n id\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% assign customer_segment_members = result.data.customerSegmentMembers.edges | map: \"node\" %}\n\n {% comment %}\n -- remove the \"SegmentMember\" portion from IDs for easier use in querying each customer for additional data not available in the segment resource\n {% endcomment %}\n\n {% for customer_segment_member in customer_segment_members %}\n {% assign customer_ids[customer_ids.size] = customer_segment_member.id | remove: \"SegmentMember\" %}\n {% endfor %}\n\n {% if result.data.customerSegmentMembers.pageInfo.hasNextPage %}\n {% assign cursor = result.data.customerSegmentMembers.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% unless event.preview %}\n {% log count_of_customers_who_have_placed_an_order: customer_ids.size %}\n {% endunless %}\n\n {% if event.preview %}\n {% assign customer_ids[0] = \"gid://shopify/Customer/1234567890\" %}\n {% endif %}\n\n {% for customer_id in customer_ids %}\n {% comment %}\n -- get all relevant order data for this customer\n {% endcomment %}\n\n {% assign cursor = nil %}\n {% assign tags_to_add = array %}\n\n {% for n in (1..10) %}\n {% capture query %}\n query {\n customer(id: {{ customer_id | json }}) {\n id\n tags\n orders(\n first: 250\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n name\n retailLocation {\n name\n }\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"customer\": {\n \"id\": \"gid://shopify/Customer/1234567890\",\n \"orders\": {\n \"nodes\": [\n {\n \"id\": \"gid://shopify/Order/1234567890\",\n \"retailLocation\": {\n \"name\": \"Storefront\"\n }\n }\n ]\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% assign customer = result.data.customer %}\n\n {% comment %}\n -- process orders to see which location names should be set as tags\n {% endcomment %}\n\n {% for order in customer.orders.nodes %}\n {% assign location_name = order.retailLocation.name | default: tag_for_online_orders %}\n\n {% unless location_name == blank or customer.tags contains location_name or tags_to_add contains location_name %}\n {% assign tags_to_add = tags_to_add | push: location_name %}\n {% endunless %}\n {% endfor %}\n\n {% if result.data.customer.orders.pageInfo.hasNextPage %}\n {% assign cursor = result.data.customer.orders.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% if tags_to_add != blank %}\n {% action \"shopify\" %}\n mutation {\n tagsAdd(\n id: {{ customer.id | json }}\n tags: {{ tags_to_add | sort_natural | json }}\n ) {\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n {% endfor %}\n{% endif %}\n",
"subscriptions": [
"shopify/orders/create",
- "mechanic/user/trigger",
- "mechanic/shopify/bulk_operation"
+ "mechanic/user/trigger"
],
- "subscriptions_template": "shopify/orders/create\nmechanic/user/trigger\nmechanic/shopify/bulk_operation",
+ "subscriptions_template": "shopify/orders/create\nmechanic/user/trigger",
"tags": [
"Auto-Tag",
"Customers",
diff --git a/tasks/copy-order-tags-to-customers.json b/tasks/copy-order-tags-to-customers.json
index c6ee4d9c..96f85e4e 100644
--- a/tasks/copy-order-tags-to-customers.json
+++ b/tasks/copy-order-tags-to-customers.json
@@ -1,5 +1,5 @@
{
- "docs": "Run this task to scan all of your customers and their order histories in bulk, copying order and/or product tags to the relevant customer. Optionally, configure a specific set of tags to look for, when scanning. Optionally, choose to remove those tags if a qualifying source can't be found - useful for adding customer tags that expire after ordering!\n\nRun this task to scan all of your customers and their order histories in bulk, copying order and/or product tags to the relevant customer. Optionally, configure a specific set of tags to look for, when scanning. Optionally, choose to remove those tags if a qualifying source can't be found.\r\n\r\nBoth the customer and order query options support Liquid. This means that you can dynamically query for orders, based on things like the current time.\r\n\r\nFor example, use these options to achieve customer tags that auto-expire a year after the newest qualifying order:\r\n\r\n* \"Only include orders matching this query\": `created_at:>={{ \"now\" | date: \"%s\" | minus: 31536000 | date: \"%Y-%m-%d\" }}`\r\n* \"Only copy these tags\": (use whatever order or product tag(s) you want to copy)\r\n* \"Remove those tags if a qualifying source cannot be found\": yes\r\n* \"Run daily\": yes\r\n\r\nNote: the 31536000 value is a quantity of seconds; 31536000 is the number of seconds in a year. To adjust, replace this value with the number of seconds you want to use. For example, 30 days is 2592000 seconds.",
+ "docs": "Run this task to scan all of your customers and their order histories, copying order and/or product tags to the relevant customer. Optionally, configure a specific set of tags to look for, when scanning. Optionally, choose to remove those tags if a qualifying source can't be found.\n\nBoth the customer and order query options support Liquid. This means that you can dynamically query for orders, based on things like the current date.\n\nFor example, use these options to achieve customer tags that auto-expire a year after the newest qualifying order:\n\n* \"Only include orders matching this query\": `created_at:>={{ \"now - 1 year\" | date: \"%Y-%m-%d\" }}`\n* \"Only copy these tags\": (use whatever order or product tag(s) you want to copy)\n* \"Remove those tags if a qualifying source cannot be found\": yes\n* \"Run daily\": yes\n\n**Important:** The customers 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](https://shopify.dev/docs/api/shopifyql/segment-query-language-reference).\n\nFor example, to only include customers that have the \"subscriber\", use this query: `customer_tags CONTAINS 'subscriber'`",
"halt_action_run_sequence_on_error": false,
"name": "Copy order and/or product tags to customers",
"online_store_javascript": null,
@@ -14,12 +14,12 @@
},
"order_status_javascript": null,
"perform_action_runs_in_sequence": false,
- "script": "{% comment %}\n Option order:\n\n {{ options.include_order_tags__boolean }}\n {{ options.include_product_tags__boolean }}\n\n {{ options.only_include_customers_matching_this_query }}\n {{ options.only_include_orders_matching_this_query }}\n\n {{ options.only_copy_these_tags__array }}\n {{ options.remove_those_tags_if_a_qualifying_source_cannot_be_found__boolean }}\n\n {{ options.run_daily__boolean }}\n{% endcomment %}\n\n{% if options.include_order_tags__boolean == false and options.include_product_tags__boolean == false %}\n {\"error\": \"Choose at least one of 'Include order tags' and 'Include product tags'. :)\"}\n{% endif %}\n\n{% if options.remove_those_tags_if_a_qualifying_source_cannot_be_found__boolean and options.only_copy_these_tags__array == blank %}\n {\"error\": \"Mechanic can only remove tags it knows about. If you choose 'Remove those tags [...]', you must also fill in 'Only copy these tags'.\"}\n{% endif %}\n\n{% if event.topic == \"mechanic/user/trigger\" or event.topic contains \"mechanic/scheduler/\" %}\n {% capture bulk_operation_query %}\n query {\n customers(query: {{ options.only_include_customers_matching_this_query | json }}) {\n edges {\n node {\n __typename\n id\n tags\n orders(query: {{ options.only_include_orders_matching_this_query | json }}) {\n edges {\n node {\n __typename\n id\n {% if options.include_order_tags__boolean %}\n tags\n {% endif %}\n {% if options.include_product_tags__boolean %}\n lineItems {\n edges {\n node {\n __typename\n id\n product {\n __typename\n id\n tags\n }\n }\n }\n }\n {% endif %}\n }\n }\n }\n }\n }\n }\n }\n {% endcapture %}\n\n {% action \"shopify\" %}\n mutation {\n bulkOperationRunQuery(\n query: {{ bulk_operation_query | json }}\n ) {\n bulkOperation {\n id\n status\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n{% elsif event.topic == \"mechanic/shopify/bulk_operation\" %}\n {% assign customer_ids = bulkOperation.objects | where: \"__typename\", \"Customer\" | map: \"id\" %}\n\n {% assign tags_available_by_customer_id = hash %}\n\n {% if options.include_order_tags__boolean %}\n {% assign orders = bulkOperation.objects | where: \"__typename\", \"Order\" %}\n {% for order in orders %}\n {% assign customer = order.__parent %}\n\n {% if tags_available_by_customer_id[customer.id] == nil %}\n {% assign tags_available_by_customer_id[customer.id] = array %}\n {% endif %}\n\n {% assign tags_available_by_customer_id[customer.id] = tags_available_by_customer_id[customer.id] | concat: order.tags %}\n {% endfor %}\n {% endif %}\n\n {% if options.include_product_tags__boolean %}\n {% assign lineItems = bulkOperation.objects | where: \"__typename\", \"LineItem\" | where: \"product\" %}\n {% for lineItem in lineItems %}\n {% assign product = lineItem.product %}\n {% assign order = lineItem.__parent %}\n {% assign customer = order.__parent %}\n\n {% if tags_available_by_customer_id[customer.id] == nil %}\n {% assign tags_available_by_customer_id[customer.id] = array %}\n {% endif %}\n\n {% assign tags_available_by_customer_id[customer.id] = tags_available_by_customer_id[customer.id] | concat: product.tags %}\n {% endfor %}\n {% endif %}\n\n {% if event.preview %}\n {% assign preview_tags = \"foo,bar,baz\" | split: \",\" %}\n {% assign customer_ids[0] = \"gid://shopify/Customer/1234567890\" %}\n {% assign tags_available_by_customer_id[\"gid://shopify/Customer/1234567890\"] = options.only_copy_these_tags__array | default: preview_tags %}\n {% assign bulkOperation = hash %}\n {% assign bulkOperation[\"objects\"] = array %}\n {% assign bulkOperation[\"objects\"][0] = '{\"id\":\"gid://shopify/Customer/1234567890\",\"tags\":\"\"}' | parse_json %}\n {% endif %}\n\n {% for customer_id in customer_ids %}\n {% assign customer = bulkOperation.objects | where: \"id\", customer_id | first %}\n\n {% assign tags_available = tags_available_by_customer_id[customer_id] | default: array %}\n\n {% if options.only_copy_these_tags__array == blank %}\n {% assign tags_applicable = tags_available %}\n {% else %}\n {% assign tags_applicable = array %}\n\n {% for whitelisted_tag in options.only_copy_these_tags__array %}\n {% if tags_available contains whitelisted_tag %}\n {% assign tags_applicable[tags_applicable.size] = whitelisted_tag %}\n {% endif %}\n {% endfor %}\n {% endif %}\n\n {% assign tags_applicable = tags_applicable | sort | uniq %}\n\n {% if tags_applicable != empty %}\n {% assign tags_to_copy = array %}\n\n {% for tag in tags_applicable %}\n {% unless customer.tags contains tag %}\n {% assign tags_to_copy[tags_to_copy.size] = tag %}\n {% endunless %}\n {% endfor %}\n\n {% if tags_to_copy == empty %}\n {% capture log_message %}Customer {{ customer.id }} already has all applicable tags ({{ tags_applicable | join: \", \" }}); nothing to do.{% endcapture %}\n {\"log\": {{ log_message | json }}}\n {% else %}\n {% action \"shopify\" %}\n mutation {\n tagsAdd(\n id: {{ customer.id | json }}\n tags: {{ tags_to_copy | json }}\n ) {\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n {% endif %}\n\n {% if options.remove_those_tags_if_a_qualifying_source_cannot_be_found__boolean and options.only_copy_these_tags__array != blank %}\n {% assign tags_to_remove = array %}\n\n {% for tag in options.only_copy_these_tags__array %}\n {% unless tags_applicable contains tag %}\n {% if customer.tags contains tag %}\n {% assign tags_to_remove[tags_to_remove.size] = tag %}\n {% endif %}\n {% endunless %}\n {% endfor %}\n\n {% if tags_to_remove != empty %}\n {% action \"shopify\" %}\n mutation {\n tagsRemove(\n id: {{ customer.id | json }}\n tags: {{ tags_to_remove | json }}\n ) {\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n {% endif %}\n\n {% endfor %}\n{% endif %}",
+ "script": "{% assign include_order_tags = options.include_order_tags__boolean %}\n{% assign include_product_tags = options.include_product_tags__boolean %}\n{% assign customer_segment_query = options.only_include_customers_matching_this_query %}\n{% assign only_include_orders_matching_this_query = options.only_include_orders_matching_this_query %}\n{% assign only_copy_these_tags = options.only_copy_these_tags__array %}\n{% assign remove_those_tags_if_a_qualifying_source_cannot_be_found = options.remove_those_tags_if_a_qualifying_source_cannot_be_found__boolean %}\n\n{% unless include_order_tags or include_product_tags %}\n {% error \"Choose at least one of 'Include order tags' and 'Include product tags'. :)\" %}\n{% endunless %}\n\n{% if remove_those_tags_if_a_qualifying_source_cannot_be_found and only_copy_these_tags == blank %}\n {% error \"Mechanic can only remove tags it knows about. If you choose 'Remove those tags [...]', you must also fill in 'Only copy these tags'.\" %}\n{% endif %}\n\n{% if event.topic == \"mechanic/user/trigger\" or event.topic contains \"mechanic/scheduler/\" %}\n {% comment %}\n -- get IDs of all customers who match the segment query\n -- Note: a segment query cannot be null, so if one has not been configured in the task then send an empty string\n {% endcomment %}\n\n {% assign cursor = nil %}\n {% assign customer_ids = array %}\n\n {% for n in (1..100) %}\n {% capture query %}\n query {\n customerSegmentMembers(\n first: 1000\n after: {{ cursor | json }}\n query: {{ customer_segment_query | default: \"\" | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n node {\n id\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% assign customer_segment_members = result.data.customerSegmentMembers.edges | map: \"node\" %}\n\n {% comment %}\n -- remove the \"SegmentMember\" portion from IDs for easier use in querying each customer for additional data not available in the segment resource\n {% endcomment %}\n\n {% for customer_segment_member in customer_segment_members %}\n {% assign customer_ids[customer_ids.size] = customer_segment_member.id | remove: \"SegmentMember\" %}\n {% endfor %}\n\n {% if result.data.customerSegmentMembers.pageInfo.hasNextPage %}\n {% assign cursor = result.data.customerSegmentMembers.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% unless event.preview %}\n {% log count_of_customers_matching_query: customer_ids.size %}\n {% endunless %}\n\n {% if event.preview %}\n {% assign customer_ids[0] = \"gid://shopify/Customer/1234567890\" %}\n {% endif %}\n\n {% for customer_id in customer_ids %}\n {% comment %}\n -- get all relevant order data for this customer\n {% endcomment %}\n\n {% assign cursor = nil %}\n {% assign tags_should_have = array %}\n\n {% for n in (1..10) %}\n {% capture query %}\n query {\n customer(id: {{ customer_id | json }}) {\n id\n tags\n orders(\n first: 250\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n {% if include_order_tags %}tags{% endif %}\n {% if include_product_tags %}\n lineItems(first: 250) {\n nodes {\n product {\n tags\n }\n }\n }\n {% endif %}\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"customer\": {\n \"id\": \"gid://shopify/Customer/1234567890\",\n \"orders\": {\n \"nodes\": [\n {\n \"id\": \"gid://shopify/Order/1234567890\",\n \"tags\": {{ only_copy_these_tags.first | default: \"order preview tag\" | json }},\n \"lineItems\": {\n \"nodes\": [\n {\n \"product\": {\n \"tags\": {{ only_copy_these_tags.first | default: \"product preview tag\" | json }}\n }\n }\n ]\n }\n }\n ]\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% assign customer = result.data.customer %}\n\n {% comment %}\n -- process order tags and/or product tags to see which a customer should have based on task configuration\n {% endcomment %}\n\n {% for order in customer.orders.nodes %}\n {% if include_order_tags %}\n {% for tag in order.tags %}\n {% if only_copy_these_tags != blank %}\n {% unless only_copy_these_tags contains tag %}\n {% continue %}\n {% endunless %}\n {% endif %}\n\n {% assign tags_should_have = tags_should_have | push: tag %}\n {% endfor %}\n {% endif %}\n\n {% if include_product_tags %}\n {% for line_item in order.lineItems.nodes %}\n {% for tag in line_item.product.tags %}\n {% if only_copy_these_tags != blank %}\n {% unless only_copy_these_tags contains tag %}\n {% continue %}\n {% endunless %}\n {% endif %}\n\n {% assign tags_should_have = tags_should_have | push: tag %}\n {% endfor %}\n {% endfor %}\n {% endif %}\n {% endfor %}\n\n {% if result.data.customer.orders.pageInfo.hasNextPage %}\n {% assign cursor = result.data.customer.orders.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% comment %}\n -- determine which tags should be added or removed from the customer\n {% endcomment %}\n\n {% assign tags_to_add = array %}\n {% assign tags_to_remove = array %}\n\n {% for tag in tags_should_have %}\n {% unless customer.tags contains tag or tags_to_add contains tag %}\n {% assign tags_to_add = tags_to_add | push: tag %}\n {% endunless %}\n {% endfor %}\n\n {% if remove_those_tags_if_a_qualifying_source_cannot_be_found %}\n {% for tag in only_copy_these_tags %}\n {% if customer.tags contains tag %}\n {% unless tags_should_have contains tag %}\n {% assign tags_to_remove = tags_to_remove | push: tag %}\n {% endunless %}\n {% endif %}\n {% endfor %}\n {% endif %}\n\n {% if tags_to_add != blank %}\n {% action \"shopify\" %}\n mutation {\n tagsAdd(\n id: {{ customer.id | json }}\n tags: {{ tags_to_add | sort_natural | json }}\n ) {\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n\n {% if tags_to_remove != blank %}\n {% action \"shopify\" %}\n mutation {\n tagsRemove(\n id: {{ customer.id | json }}\n tags: {{ tags_to_remove | sort_natural | json }}\n ) {\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n {% endfor %}\n{% endif %}\n",
"subscriptions": [
"mechanic/user/trigger",
"mechanic/shopify/bulk_operation"
],
- "subscriptions_template": "mechanic/user/trigger\n{% if options.run_daily__boolean %}\n mechanic/scheduler/daily\n{% endif %}\n\nmechanic/shopify/bulk_operation",
+ "subscriptions_template": "mechanic/user/trigger\n{% if options.run_daily__boolean %}\n mechanic/scheduler/daily\n{% endif %}",
"tags": [
"Auto-Tag",
"Customers"
diff --git a/tasks/email-a-report-of-customers-who-havent-ordered-in-x-days.json b/tasks/email-a-report-of-customers-who-havent-ordered-in-x-days.json
index c93a459e..fb447b09 100644
--- a/tasks/email-a-report-of-customers-who-havent-ordered-in-x-days.json
+++ b/tasks/email-a-report-of-customers-who-havent-ordered-in-x-days.json
@@ -1,5 +1,5 @@
{
- "docs": "Use this task to request or schedule an email digest of customers, having a certain tag, who haven't placed an order in a certain number of days.\n\nRun this task manually to request a report immediately, or configure the task to run automatically on a schedule.\r\n\r\n[YouTube: Watch the development video!](https://youtu.be/y1fV3aQrS1g)",
+ "docs": "Use this task to request or schedule an email digest of customers, having a certain tag, who haven't placed an order in a certain number of days.\n\nRun this task manually to request a report immediately, or configure the task to run automatically on a schedule.",
"halt_action_run_sequence_on_error": false,
"name": "Email a report of customers who haven't ordered in X days",
"online_store_javascript": null,
@@ -14,12 +14,11 @@
},
"order_status_javascript": null,
"perform_action_runs_in_sequence": false,
- "script": "{% if event.topic == \"mechanic/user/trigger\" or event.topic contains \"mechanic/scheduler/\" %}\n {% capture bulk_operation_query %}\n query {\n customers(\n query: {{ options.required_customer_tag__required | json | prepend: \"orders_count:>=1 AND tag:\" | json }}\n ) {\n edges {\n node {\n displayName\n email\n id\n legacyResourceId\n\n lastOrder {\n createdAt\n legacyResourceId\n name\n }\n }\n }\n }\n }\n {% endcapture %}\n\n {% action \"shopify\" %}\n mutation {\n bulkOperationRunQuery(\n query: {{ bulk_operation_query | json }}\n ) {\n bulkOperation {\n id\n status\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n{% elsif event.topic == \"mechanic/shopify/bulk_operation\" %}\n {% assign now_s = \"now\" | date: \"%s\" | times: 1 %}\n {% assign interval_d = options.include_customers_who_have_not_placed_an_order_in_this_many_days__number_required %}\n {% assign interval_s = interval_d | times: 24 | times: 60 | times: 60 %}\n {% assign order_time_threshold_s = now_s | minus: interval_s %}\n\n {% if interval_d <= 0 %}\n {% error \"The number of days must be greater than 0. :)\" %}\n {% endif %}\n\n {% if event.preview %}\n {% capture objects_json %}\n [\n {\n \"displayName\": \"Alpha Beta\",\n \"email\": \"customer@example.com\",\n \"id\": \"gid://shopify/Customer/1234567890\",\n \"legacyResourceId\": \"1234567890\",\n \"lastOrder\": {\n \"createdAt\": {{ now_s | minus: interval_s | minus: 432000 | date: \"%Y-%m-%dT%H:%M:%SZ\" | json }},\n \"legacyResourceId\": \"1234567890\",\n \"name\": \"#1000\"\n }\n }\n ]\n {% endcapture %}\n\n {% assign bulkOperation = hash %}\n {% assign bulkOperation[\"objects\"] = objects_json | parse_json %}\n {% endif %}\n\n {% assign qualifying_customers = array %}\n\n {% for customer in bulkOperation.objects %}\n {% assign order_time_s = customer.lastOrder.createdAt | date: \"%s\" | times: 1 %}\n {% if order_time_s < order_time_threshold_s %}\n {% assign qualifying_customers[qualifying_customers.size] = customer %}\n {% endif %}\n {% endfor %}\n\n {% capture email_subject %}\n {{ options.email_subject_prefix__required }} {{ qualifying_customers.size }} {{ qualifying_customers.size | pluralize: \"customer\", \"customers\" }} found\n {% endcapture %}\n\n {% if qualifying_customers != empty %}\n {% capture email_body %}\n Hello,
\n \n Filtering for customers tagged {{ options.required_customer_tag__required | json }},\n the following {{ qualifying_customers.size }} {{ qualifying_customers.size | pluralize: \"customer was\", \"customers were\" }}\n found to have not placed an order in the last {{ interval_d }} {{ interval_d | pluralize: \"day\", \"days\" }}:\n
\n \n {% for customer in qualifying_customers %}\n {% assign lastOrder_days_ago = customer.lastOrder.createdAt | date: \"%s\" | times: 1 | minus: now_s | times: -1 | divided_by: 60 | divided_by: 60 | divided_by: 24 %}\n - \n {{ customer.displayName | default: \"(unnamed)\" }}\n ({{ customer.email | default: \"(no email)\" }})\n
\n Last ordered on {{ customer.lastOrder.createdAt | date: \"%Y-%m-%d\" }},\n {{ lastOrder_days_ago }} {{ lastOrder_days_ago | pluralize: \"day\", \"days\" }} ago\n – {{ customer.lastOrder.name }}\n \n {% endfor %}\n
\n Thanks,
- Mechanic, for {{ shop.name }}
\n {% endcapture %}\n\n {% action \"email\" %}\n {\n \"to\": {{ options.email_recipient__required_email | json }},\n \"subject\": {{ email_subject | strip | json }},\n \"body\": {{ email_body | json }},\n \"reply_to\": {{ shop.customer_email | json }},\n \"from_display_name\": {{ shop.name | json }}\n }\n {% endaction %}\n {% elsif options.send_email_anyway_if_no_customers_are_found__boolean %}\n {% capture email_body %}\n Hello,\n\n We didn't find any customers (tagged {{ options.required_customer_tag__required | json }}) having zero orders placed in the last {{ interval_d }} day(s).\n\n Thanks,\n - Mechanic, for {{ shop.name }}\n {% endcapture %}\n\n {% action \"email\" %}\n {\n \"to\": {{ options.email_recipient__required_email | json }},\n \"subject\": {{ email_subject | strip | json }},\n \"body\": {{ email_body | unindent | strip | newline_to_br | json }},\n \"reply_to\": {{ shop.customer_email | json }},\n \"from_display_name\": {{ shop.name | json }}\n }\n {% endaction %}\n {% endif %}\n{% endif %}",
+ "script": "{% assign required_customer_tag = options.required_customer_tag__required %}\n{% assign interval_days = options.include_customers_who_have_not_placed_an_order_in_this_many_days__number_required %}\n{% assign email_subject_prefix = options.email_subject_prefix__required %}\n{% assign email_recipient = options.email_recipient__required_email %}\n{% assign send_email_anyway_if_no_customers_are_found = options.send_email_anyway_if_no_customers_are_found__boolean %}\n\n{% if interval_days <= 0 %}\n {% error \"The number of days must be greater than 0. :)\" %}\n{% endif %}\n\n{% assign now_s = \"now\" | date: \"%s\" | times: 1 %}\n\n{% if event.topic == \"mechanic/user/trigger\" or event.topic contains \"mechanic/scheduler/\" %}\n {% comment %}\n -- get IDs of all customers with the required tag and who have placed an order over X days\n {% endcomment %}\n\n {%- capture customer_segment_query -%}\n customer_tags CONTAINS '{{ required_customer_tag }}' AND orders_placed(until: -{{ interval_days }}d) = true\n {%- endcapture -%}\n\n {% assign cursor = nil %}\n {% assign customer_ids = array %}\n\n {% for n in (1..100) %}\n {% capture query %}\n query {\n customerSegmentMembers(\n first: 1000\n after: {{ cursor | json }}\n query: {{ customer_segment_query | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n node {\n id\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% assign customer_segment_members = result.data.customerSegmentMembers.edges | map: \"node\" %}\n\n {% comment %}\n -- remove the \"SegmentMember\" portion from IDs for easier use in querying each customer for additional data not available in the segment resource\n {% endcomment %}\n\n {% for customer_segment_member in customer_segment_members %}\n {% assign customer_ids[customer_ids.size] = customer_segment_member.id | remove: \"SegmentMember\" %}\n {% endfor %}\n\n {% if result.data.customerSegmentMembers.pageInfo.hasNextPage %}\n {% assign cursor = result.data.customerSegmentMembers.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% unless event.preview %}\n {% log count_of_customers_who_qualify: customer_ids.size %}\n {% endunless %}\n\n {% if event.preview %}\n {% assign customer_ids[0] = \"gid://shopify/Customer/1234567890\" %}\n {% endif %}\n\n {% comment %}\n -- get additional customer and last order data and save list item in an array for email body processing\n {% endcomment %}\n\n {% assign list_items = array %}\n\n {% for customer_id in customer_ids %}\n {% capture query %}\n query {\n customer(id: {{ customer_id | json }}) {\n legacyResourceId\n displayName\n email\n lastOrder {\n legacyResourceId\n name\n processedAt\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"customer\": {\n \"legacyResourceId\": \"1234567890\",\n \"displayName\": \"Alpha Beta\",\n \"email\": \"customer@example.com\",\n \"lastOrder\": {\n \"processedAt\": {{ interval_days | times: -86400 | plus: now_s | minus: 432000 | date: \"%Y-%m-%dT%H:%M:%SZ\" | json }},\n \"legacyResourceId\": \"1234567890\",\n \"name\": \"#PREVIEW\"\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% assign customer = result.data.customer %}\n\n {% assign lastOrder_days_ago\n = customer.lastOrder.processedAt\n | date: \"%s\"\n | minus: now_s\n | divided_by: -86400\n %}\n\n {% capture list_item %}\n \n {{ customer.displayName | default: \"(unnamed)\" }}\n ({{ customer.email | default: \"(no email)\" }})\n
\n Last ordered on {{ customer.lastOrder.processedAt | date: \"%Y-%m-%d\" }},\n {{ lastOrder_days_ago }} {{ lastOrder_days_ago | pluralize: \"day\", \"days\" }} ago\n – {{ customer.lastOrder.name }}\n \n {% endcapture %}\n\n {% assign list_items = list_items | push: list_item %}\n {% endfor %}\n\n {% comment %}\n -- generate the email subject and body with the list item data from qualifying customers\n {% endcomment %}\n\n {% capture email_subject %}\n {{ email_subject_prefix }} {{ list_items.size }} {{ list_items.size | pluralize: \"customer\", \"customers\" }} found\n {% endcapture %}\n\n {% if list_items != blank %}\n {% capture email_body %}\n Hello,
\n \n Filtering for customers tagged {{ required_customer_tag | json }},\n the following {{ list_items.size }} {{ list_items.size | pluralize: \"customer was\", \"customers were\" }}\n found to have not placed an order in the last {{ interval_days }} {{ interval_days | pluralize: \"day\", \"days\" }}:\n
\n \n {{ list_items | join: newline }}\n
\n Thanks,
- Mechanic, for {{ shop.name }}
\n {% endcapture %}\n\n {% action \"email\" %}\n {\n \"to\": {{ email_recipient | json }},\n \"subject\": {{ email_subject | strip | json }},\n \"body\": {{ email_body | json }},\n \"reply_to\": {{ shop.customer_email | json }},\n \"from_display_name\": {{ shop.name | json }}\n }\n {% endaction %}\n\n {% elsif send_email_anyway_if_no_customers_are_found %}\n {% capture email_body %}\n Hello,\n\n We didn't find any customers (tagged {{ required_customer_tag | json }}) having zero orders placed in the last {{ interval_days }} day(s).\n\n Thanks,\n - Mechanic, for {{ shop.name }}\n {% endcapture %}\n\n {% action \"email\" %}\n {\n \"to\": {{ email_recipient | json }},\n \"subject\": {{ email_subject | strip | json }},\n \"body\": {{ email_body | unindent | strip | newline_to_br | json }},\n \"reply_to\": {{ shop.customer_email | json }},\n \"from_display_name\": {{ shop.name | json }}\n }\n {% endaction %}\n {% endif %}\n{% endif %}\n",
"subscriptions": [
- "mechanic/shopify/bulk_operation",
"mechanic/user/trigger"
],
- "subscriptions_template": "mechanic/shopify/bulk_operation\n\nmechanic/user/trigger\n\n{% if options.send_email_daily__boolean %}\n mechanic/scheduler/daily\n{% endif %}\n\n{% if options.send_email_every_monday__boolean %}\n mechanic/scheduler/monday\n{% endif %}",
+ "subscriptions_template": "mechanic/user/trigger\n{% if options.send_email_daily__boolean %}\n mechanic/scheduler/daily\n{% elsif options.send_email_every_monday__boolean %}\n mechanic/scheduler/monday\n{% endif %}",
"tags": [
"Customers",
"Email",
diff --git a/tasks/tag-and-segment-customers-by-days-since-last-order.json b/tasks/tag-and-segment-customers-by-days-since-last-order.json
index dce745c8..eb183379 100644
--- a/tasks/tag-and-segment-customers-by-days-since-last-order.json
+++ b/tasks/tag-and-segment-customers-by-days-since-last-order.json
@@ -15,13 +15,12 @@
"order_status_javascript": null,
"perform_action_runs_in_sequence": false,
"preview_event_definitions": [],
- "script": "{% assign customer_tags_and_days_since_last_order = options.customer_tags_and_days_since_last_order__keyval_number_required %}\n{% assign create_customer_segments_by_tag = options.create_customer_segments_by_tag__boolean %}\n{% assign now_s = \"now\" | date: \"%s\" %}\n\n{% assign tag_rules = array %}\n\n{% for keyval in customer_tags_and_days_since_last_order %}\n {% assign customer_tag = keyval[0] %}\n {% assign days_since_last_order = keyval[1] %}\n\n {% if customer_tag == blank or days_since_last_order == blank or days_since_last_order < 0 %}\n {% continue %}\n {% endif %}\n\n {% assign tag_rule = hash %}\n {% assign tag_rule[\"days\"] = days_since_last_order %}\n {% assign tag_rule[\"tag\"] = customer_tag %}\n {% assign tag_rule[\"threshold_s\"]\n = days_since_last_order\n | times: -86400\n | plus: now_s\n %}\n {% assign tag_rules = tag_rules | push: tag_rule %}\n{% endfor %}\n\n{% assign tag_rules = tag_rules | sort: \"days\" | reverse %}\n{% assign configured_tags = tag_rules | map: \"tag\" %}\n\n{% if tag_rules == blank %}\n {% error \"Please enter at least one customer tag and days since last order combo.\" %}\n{% endif %}\n\n{% if event.topic == \"mechanic/user/trigger\" or event.topic contains \"mechanic/scheduler/\" %}\n {% capture bulk_operation_query %}\n query {\n customers(\n query: \"orders_count:>0\"\n ) {\n edges {\n node {\n id\n tags\n lastOrder {\n processedAt\n }\n }\n }\n }\n }\n {% endcapture %}\n\n {% action \"shopify\" %}\n mutation {\n bulkOperationRunQuery(\n query: {{ bulk_operation_query | json }}\n ) {\n bulkOperation {\n id\n status\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n\n{% elsif event.topic == \"mechanic/shopify/bulk_operation\" %}\n {% log tag_rules: tag_rules %}\n\n {% if event.preview %}\n {% capture bulkOperation_objects_jsonl %}\n {\"id\":\"gid:\\/\\/shopify\\/Customer\\/1234567890\",\"lastOrder\":{\"processedAt\":{{ tag_rules[0].threshold_s | json }}}}\n {% endcapture %}\n\n {% assign bulkOperation = hash %}\n {% assign bulkOperation[\"objects\"] = bulkOperation_objects_jsonl | parse_jsonl %}\n {% endif %}\n\n {% assign customers = bulkOperation.objects %}\n\n {% for customer in customers %}\n {% assign tag_should_have = nil %}\n\n {% assign customer_last_order_processed_at_s = customer.lastOrder.processedAt | date: \"%s\" | times: 1 %}\n\n {% for tag_rule in tag_rules %}\n {% if customer_last_order_processed_at_s <= tag_rule.threshold_s %}\n {% assign tag_should_have = tag_rule.tag %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% unless tag_should_have == blank or customer.tags contains tag_should_have %}\n {% action \"shopify\" %}\n mutation {\n tagsAdd(\n id: {{ customer.id | json }}\n tags: {{ tag_should_have | json }}\n ) {\n node {\n ... on Customer {\n id\n email\n tags\n lastOrder {\n name\n processedAt\n }\n }\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endunless %}\n\n {% assign tags_to_remove = array %}\n\n {% for customer_tag in customer.tags %}\n {% if configured_tags contains customer_tag and tag_should_have != customer_tag %}\n {% assign tags_to_remove = tags_to_remove | push: customer_tag %}\n {% endif %}\n {% endfor %}\n\n {% if tags_to_remove != blank %}\n {% action \"shopify\" %}\n mutation {\n tagsRemove(\n id: {{ customer.id | json }}\n tags: {{ tags_to_remove | json }}\n ) {\n node {\n ... on Customer {\n id\n email\n tags\n lastOrder {\n name\n processedAt\n }\n }\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n {% endfor %}\n\n {% if create_customer_segments_by_tag %}\n {% assign cursor = nil %}\n {% assign segments = array %}\n\n {% for n in (1..100) %}\n {% capture query %}\n query {\n segments(\n first: 250\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n name\n query\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"segments\": {\n \"nodes\": []\n }\n }\n } \n {% endcapture %}\n \n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% assign segments = segemnts | concat: result.data.segments.nodes %}\n {% comment %}\n \n {% assign segments\n = result.data.segments.nodes\n | default: array\n | concat: segments\n %}\n {% endcomment %}\n\n {% if result.data.segments.pageInfo.hasNextPage %}\n {% assign cursor = result.data.segments.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% for tag_rule in tag_rules %}\n {%- capture segment_rule -%}\n customer_tags CONTAINS '{{ tag_rule.tag }}'\n {%- endcapture -%}\n\n {% assign matched_segment\n = segments\n | where: \"name\", tag_rule.tag\n | where: \"query\", segment_rule\n %}\n\n {% if matched_segment != blank %}\n {% log\n message: \"This customer segment already exists.\",\n matched_segment: matched_segment\n %}\n {% continue %}\n {% endif %}\n\n {% action \"shopify\" %}\n mutation {\n segmentCreate(\n name: {{ tag_rule.tag | json }}\n query: {{ segment_rule | json }}\n ) {\n segment {\n id\n name\n query\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endfor %}\n {% endif %}\n{% endif %}\n",
+ "script": "{% assign customer_tags_and_days_since_last_order = options.customer_tags_and_days_since_last_order__keyval_number_required %}\n{% assign create_customer_segments_by_tag = options.create_customer_segments_by_tag__boolean %}\n{% assign now_s = \"now\" | date: \"%s\" %}\n\n{% assign tag_rules = array %}\n\n{% for keyval in customer_tags_and_days_since_last_order %}\n {% assign customer_tag = keyval[0] %}\n {% assign days_since_last_order = keyval[1] %}\n\n {% if customer_tag == blank or days_since_last_order == blank or days_since_last_order < 0 %}\n {% continue %}\n {% endif %}\n\n {% assign tag_rule = hash %}\n {% assign tag_rule[\"days\"] = days_since_last_order %}\n {% assign tag_rule[\"tag\"] = customer_tag %}\n {% assign tag_rule[\"threshold_s\"]\n = days_since_last_order\n | times: -86400\n | plus: now_s\n %}\n {% assign tag_rules = tag_rules | push: tag_rule %}\n{% endfor %}\n\n{% assign tag_rules = tag_rules | sort: \"days\" | reverse %}\n{% assign configured_tags = tag_rules | map: \"tag\" %}\n\n{% if tag_rules == blank %}\n {% error \"Please enter at least one customer tag and days since last order combo.\" %}\n{% endif %}\n\n{% if event.topic == \"mechanic/user/trigger\" or event.topic contains \"mechanic/scheduler/\" %}\n {% comment %}\n -- get IDs of all customers who have placed an order\n {% endcomment %}\n\n {% assign cursor = nil %}\n {% assign customer_ids = array %}\n\n {% for n in (1..100) %}\n {% capture query %}\n query {\n customerSegmentMembers(\n first: 1000\n after: {{ cursor | json }}\n query: \"number_of_orders > 0\"\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n node {\n id\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% assign customer_segment_members = result.data.customerSegmentMembers.edges | map: \"node\" %}\n\n {% comment %}\n -- remove the \"SegmentMember\" portion from IDs for easier use in querying each customer for additional data not available in the segment resource\n {% endcomment %}\n\n {% for customer_segment_member in customer_segment_members %}\n {% assign customer_ids[customer_ids.size] = customer_segment_member.id | remove: \"SegmentMember\" %}\n {% endfor %}\n\n {% if result.data.customerSegmentMembers.pageInfo.hasNextPage %}\n {% assign cursor = result.data.customerSegmentMembers.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% unless event.preview %}\n {% log\n count_of_customers_who_have_placed_an_order: customer_ids.size,\n tag_rules: tag_rules\n %}\n {% endunless %}\n\n {% if event.preview %}\n {% assign customer_ids[0] = \"gid://shopify/Customer/1234567890\" %}\n {% endif %}\n\n {% for customer_id in customer_ids %}\n {% comment %}\n -- get customer tags and last order date\n {% endcomment %}\n\n {% capture query %}\n query {\n customer(id: {{ customer_id | json }}) {\n id\n tags\n lastOrder {\n processedAt\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"customer\": {\n \"id\": \"gid://shopify/Customer/1234567890\",\n \"lastOrder\": {\n \"processedAt\": {{ tag_rules[0].threshold_s | json }}\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% assign customer = result.data.customer %}\n\n {% comment %}\n -- determine the tag that the customer should have based their last order date\n {% endcomment %}\n\n {% assign customer_last_order_processed_at_s = customer.lastOrder.processedAt | date: \"%s\" | times: 1 %}\n {% assign tag_should_have = nil %}\n\n {% for tag_rule in tag_rules %}\n {% if customer_last_order_processed_at_s <= tag_rule.threshold_s %}\n {% assign tag_should_have = tag_rule.tag %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% unless tag_should_have == blank or customer.tags contains tag_should_have %}\n {% action \"shopify\" %}\n mutation {\n tagsAdd(\n id: {{ customer.id | json }}\n tags: {{ tag_should_have | json }}\n ) {\n node {\n ... on Customer {\n id\n email\n lastOrder {\n name\n processedAt\n }\n }\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endunless %}\n\n {% comment %}\n -- remove any other configured tags from the customer if needed\n {% endcomment %}\n\n {% assign tags_to_remove = array %}\n\n {% for configured_tag in configured_tags %}\n {% unless configured_tag == tag_should_have %}\n {% if customer.tags contains configured_tag %}\n {% assign tags_to_remove = tags_to_remove | push: configured_tag %}\n {% endif %}\n {% endunless %}\n {% endfor %}\n\n {% if tags_to_remove != blank %}\n {% action \"shopify\" %}\n mutation {\n tagsRemove(\n id: {{ customer.id | json }}\n tags: {{ tags_to_remove | json }}\n ) {\n node {\n ... on Customer {\n id\n email\n lastOrder {\n name\n processedAt\n }\n }\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n {% endfor %}\n\n {% comment %}\n -- if task option enabled, then create customer segments for each configured tag (if they don't already exist)\n {% endcomment %}\n\n {% if create_customer_segments_by_tag %}\n {% assign cursor = nil %}\n {% assign segments = array %}\n\n {% for n in (1..10) %}\n {% capture query %}\n query {\n segments(\n first: 250\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n name\n query\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% assign segments\n = result.data.segments.nodes\n | default: array\n | concat: segments\n %}\n\n {% if result.data.segments.pageInfo.hasNextPage %}\n {% assign cursor = result.data.segments.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% for tag_rule in tag_rules %}\n {%- capture segment_rule -%}\n customer_tags CONTAINS '{{ tag_rule.tag }}'\n {%- endcapture -%}\n\n {% assign matched_segment\n = segments\n | where: \"name\", tag_rule.tag\n | where: \"query\", segment_rule\n %}\n\n {% if matched_segment != blank %}\n {% log\n message: \"This customer segment already exists.\",\n matched_segment: matched_segment\n %}\n {% continue %}\n {% endif %}\n\n {% action \"shopify\" %}\n mutation {\n segmentCreate(\n name: {{ tag_rule.tag | json }}\n query: {{ segment_rule | json }}\n ) {\n segment {\n id\n name\n query\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endfor %}\n {% endif %}\n{% endif %}\n",
"subscriptions": [
- "mechanic/shopify/bulk_operation",
"mechanic/scheduler/daily",
"mechanic/user/trigger"
],
- "subscriptions_template": "mechanic/shopify/bulk_operation\nmechanic/scheduler/daily\nmechanic/user/trigger",
+ "subscriptions_template": "mechanic/scheduler/daily\nmechanic/user/trigger",
"tags": [
"Customers",
"Loyalty",