Get email alerts for out of stock products, with Mechanic.

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

Get email alerts for out of stock products

Use this task to keep you and your team updated when products go out of stock - and, optionally, when they go back in stock. Filter with a simple product search to only track products you care about. Choose a custom stock threshold to get a heads up before the stock level reaches zero.

Runs Occurs whenever an inventory level is updated and Occurs when a user manually triggers the task. Configuration includes out of stock inventory quantity, only monitor products matching this search query, send email for out of stock products, send email for back in stock products, and stock update email recipients.

15-day free trial – unlimited tasks

Documentation

Use this task to keep you and your team updated when products go out of stock - and, optionally, when they go back in stock. Filter with a simple product search to only track products you care about. Choose a custom stock threshold to get a heads up before the stock level reaches zero.

Configure this task to receive email alerts when products go out of stock, for the threshold of your choice, optionally applying a specific product search.

Run this task manually to scan all products up front. While this task normally remembers which products were out (or in) stock, running this task manually will always result in an email summary of out of stock products.

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
shopify/inventory_levels/update
mechanic/user/trigger
Tasks use subscriptions to sign up for specific kinds of events. Learn more
Options
out of stock inventory quantity (number, required), only monitor products matching this search query, send email for out of stock products (boolean), send email for back in stock products (boolean), stock update email recipients (email, array, required)
Code
{% assign out_of_stock_inventory_quantity = options.out_of_stock_inventory_quantity__number_required %}
{% assign query = options.only_monitor_products_matching_this_search_query | default: "" %}

{% assign metafield_key = task.id | append: query | append: out_of_stock_inventory_quantity | sha256 | slice: 0, 7 | append: "-out-of-stock" %}

{% if event.topic contains "shopify/inventory_levels/" %}
  {% capture product_query %}
    query {
      inventoryLevel(
        id: {{ inventory_level.admin_graphql_api_id | json }}
      ) {
        item {
          variant {
            product {
              id
              title
            }
          }
        }
      }
    }
  {% endcapture %}

  {% assign product_result = product_query | shopify %}
  {% assign product_result = product_result.data.inventoryLevel.item.variant.product %}

  {% assign query = product_result.title | json | prepend: " title:" | prepend: query %}
{% endif %}

{% assign back_in_stock_email_list = array %}
{% assign newly_back_in_stock_email_product_title = nil %}
{% assign newly_back_in_stock_metafield_ids = array %}
{% assign newly_back_in_stock_product_ids = array %}
{% assign newly_out_of_stock_product_ids = array %}
{% assign out_of_stock_email_list = array %}
{% assign out_of_stock_email_product_title = nil %}

{% assign cursor = nil %}

{% for n in (0..100) %}
  {% capture products_search_query %}
    query {
      products(
        first: 250
        query: {{ query | json }}
        after: {{ cursor | json }}
      ) {
        pageInfo {
          hasNextPage
        }
        edges {
          cursor
          node {
            id
            legacyResourceId
            title
            totalInventory
            metafield(
              namespace: "mechanic"
              key: {{ metafield_key | json }}
            ) {
              id
            }
          }
        }
      }
    }
  {% endcapture %}

  {% assign products_search_result = products_search_query | shopify %}

  {% for edge in products_search_result.data.products.edges %}
    {% if edge.node.totalInventory <= out_of_stock_inventory_quantity %}
      {% if edge.node.metafield == nil or event.topic == "mechanic/user/trigger" %}
        {"log": {{ edge.node.title | append: " is out of stock." | json }}}
        {% assign out_of_stock_email_product_title = edge.node.title %}

        {% if edge.node.metafield == nil %}
          {% comment %}
            For a manual run, we always send an email about out-of-stock products. But, we
            don't need to consider these products *newly* out of stock.
          {% endcomment %}
          {% assign newly_out_of_stock_product_ids[newly_out_of_stock_product_ids.size] = edge.node.id %}
        {% endif %}

        {% assign out_of_stock_email_list[out_of_stock_email_list.size] = '<li><a href="https://' | append: shop.domain | append: '/admin/products/' | append: edge.node.legacyResourceId | append: '">' | append: edge.node.title | append: '</a></li>' %}
      {% endif %}
    {% else %}
      {% if edge.node.metafield %}
        {"log": {{ edge.node.title | append: " is back in stock." | json }}}
        {% assign newly_back_in_stock_email_product_title = edge.node.title %}
        {% assign newly_back_in_stock_product_ids[newly_back_in_stock_product_ids.size] = edge.node.id %}
        {% assign newly_back_in_stock_metafield_ids[newly_back_in_stock_metafield_ids.size] = edge.node.metafield.id %}
        {% assign back_in_stock_email_list[back_in_stock_email_list.size] = '<li><a href="https://' | append: shop.domain | append: '/admin/products/' | append: edge.node.legacyResourceId | append: '">' | append: edge.node.title | append: '</a></li>' %}
      {% endif %}
    {% endif %}
  {% endfor %}

  {% if products_search_result.products.pageInfo.hasNextPage %}
    {% assign cursor = products_search_result.data.products.edges.last.cursor %}
  {% else %}
    {% break %}
  {% endif %}
{% endfor %}

{% if event.preview %}
  {% assign out_of_stock_email_list[back_in_stock_email_list.size] = '<li><a>Premium T-Shirt</a></li>' %}
  {% assign newly_out_of_stock_product_ids[newly_out_of_stock_product_ids.size] = 'gid://shopify/Product/1234567890' %}
  {% assign out_of_stock_email_product_title = 'Premium T-Shirt' %}
{% endif %}

{% assign metafields = array %}
{% assign mutations = array %}

{% for product_id in newly_out_of_stock_product_ids %}
  {% capture metafield %}
    {
      ownerId: {{ product_id | json }}
      namespace: "mechanic"
      key: {{ metafield_key | json }}
      type: "number_integer"
      value: "1"
    }
  {% endcapture %}

  {% assign metafields = metafields | push: metafield %}
{% endfor %}

{% assign groups_of_metafields = metafields | in_groups_of: 25, fill_with: false %}

{% for group_of_metafields in groups_of_metafields %}
  {% capture mutation %}
    mutation {
      metafieldsSet(
        metafields: [
          {{ group_of_metafields | join: newline }}
        ]
      ) {
        metafields {
          id
          namespace
          key
          type
          value
          owner {
            ... on Product {
              id
            }
          }
        }
        userErrors {
          code
          field
          message
        }
      }
    }
  {% endcapture %}

  {% assign mutations[mutations.size] = mutation %}
{% endfor %}

{% for metafield_id in newly_back_in_stock_metafield_ids %}
  {% capture mutation %}
    mutation {
      metafieldDelete(
        input: {
          id: {{ metafield_id | json }}
        }
      ) {
        userErrors {
          field
          message
        }
      }
    }
  {% endcapture %}

  {% assign mutations[mutations.size] = mutation %}
{% endfor %}

{"log": "Out of stock: {{ newly_out_of_stock_product_ids.size }} product(s)"}
{"log": "Back in stock: {{ newly_back_in_stock_product_ids.size }} product(s)"}

{% if options.send_email_for_out_of_stock_products__boolean != true %}
  {% assign out_of_stock_email_list = array %}
{% endif %}

{% if options.send_email_for_back_in_stock_products__boolean != true %}
  {% assign back_in_stock_email_list = array %}
{% endif %}

{% if out_of_stock_email_list.size > 0 or back_in_stock_email_list.size > 0 %}
  {% capture email_subject %}
    {% if out_of_stock_email_list.size == 1 and back_in_stock_email_list.size == 0 %}
      Out of stock: {{ out_of_stock_email_product_title }}
    {% elsif back_in_stock_email_list.size == 1 and out_of_stock_email_list.size == 0 %}
      Back in stock: {{ newly_back_in_stock_email_product_title }}
    {% elsif out_of_stock_email_list.size > 1 and back_in_stock_email_list.size == 0 %}
      Out of stock: {{ out_of_stock_email_list.size }} products
    {% elsif back_in_stock_email_list.size > 1 and out_of_stock_email_list.size == 0 %}
      Back in stock: {{ out_of_stock_email_list.size }} products
    {% else %}
      Stock update: {{ out_of_stock_email_list.size }} out of stock, {{ back_in_stock_email_list.size }} back in stock
    {% endif %}
  {% endcapture %}

  {% capture email_body %}
    Hi there,
    <br><br>

    {% if out_of_stock_email_list.size > 0 %}
      We found <b>{{ out_of_stock_email_list.size }} out of stock {{ out_of_stock_email_list.size | pluralize: 'product', 'products' }}</b>:
      <br>
      <ul>{{ out_of_stock_email_list | join: "" }}</ul>
      <br>
    {% endif %}

    {% if back_in_stock_email_list.size > 0 %}
      We found <b>{{ back_in_stock_email_list.size }} back in stock {{ back_in_stock_email_list.size | pluralize: 'product', 'products' }}</b>:
      <br>
      <ul>{{ back_in_stock_email_list | join: "" }}</ul>
      <br>
    {% endif %}

    Thanks,
    <br>Mechanic (for {{ shop.name }})
  {% endcapture %}

  {% action "email" %}
    {
      "to": {{ options.stock_update_email_recipients__email_array_required | join: ", " | json }},
      "subject": {{ email_subject | strip | json }},
      "body": {{ email_body | unindent | strip | json }},
      "reply_to": {{ shop.customer_email | json }},
      "from_display_name": {{ shop.name | json }}
    }
  {% endaction %}
{% endif %}

{% for mutation in mutations %}
  {% action "shopify" mutation %}
{% endfor %}
Task code is written in Mechanic Liquid, an extension of open-source Liquid enhanced for automation. Learn more
Defaults
Out of stock inventory quantity
10
Only monitor products matching this search query
shirt
Send email for out of stock products
true
Send email for back in stock products
true