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

Mechanic is an automation development platform for Shopify. :)

Get email alerts for out of stock products

by Mechanic Team (team@usemechanic.com)

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 when an inventory level is updated and when a user 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

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, Gurus, 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.

Events
when an inventory level is updated (shopify/inventory_levels/update)
when a user triggers the task (mechanic/user/trigger)
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)
Script
{% 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 mutations = array %}

{% for product_id in newly_out_of_stock_product_ids %}
  {% capture mutation %}
    mutation {
      productUpdate(
        input: {
          id: {{ product_id | json }}
          metafields: [
            {
              namespace: "mechanic"
              key: {{ metafield_key | json }}
              valueType: INTEGER
              value: "1"
            }
          ]
        }
      ) {
        userErrors {
          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 %}
Mechanic tasks are written in Liquid, which makes them easy to write and easy to modify. Learn more about our platform.
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