🚀 Executive Summary

TL;DR: This blog post addresses the inefficiency of manually monitoring Shopify inventory for low stock by presenting a Python script. The solution automates the process, leveraging Shopify’s GraphQL API to fetch inventory levels and dispatching email alerts when product stock falls below a predefined threshold.

🎯 Key Takeaways

  • Utilizing Shopify’s GraphQL API is more efficient for inventory checks than the REST API, enabling single, precise requests to conserve bandwidth and manage API rate limits.
  • Secure credential management is crucial, advocating for `config.env` and `python-dotenv` to store sensitive Shopify API and SMTP details, preventing hardcoding.
  • Automation scheduling can be achieved with `cron` jobs for basic setups, or for production environments, serverless platforms like AWS Lambda with CloudWatch triggers offer enhanced resilience and cost-efficiency.

Syncing Shopify Inventory Low Stock to Email Alert

Syncing Shopify Inventory Low Stock to Email Alert

Hey there, Darian here. As a Senior DevOps Engineer at TechResolve, I’m always looking for ways to automate the tedious parts of our workflows. One of the biggest time sinks used to be manually checking our Shopify store for low-stock items. I’d spend a couple of hours every week pulling reports and cross-referencing spreadsheets. It was a chore, and frankly, a waste of engineering time. That’s why I built this simple Python script. It automates the entire process, sending a clean email alert only when an item’s inventory drops below a set threshold. This little piece of automation gave me back my Monday mornings. Let’s get you set up so you can reclaim yours.

Prerequisites

Before we dive in, make sure you have the following ready to go:

  • Shopify Private App: You’ll need credentials from a private app in your Shopify store with at least read_products and read_inventory permissions.
  • Python 3 Environment: A working Python 3 installation.
  • Email Server Access: SMTP credentials (host, port, user, password) for an email account that can send alerts. You could use a service like SendGrid or just a standard Gmail account with an “App Password”.
  • A Scheduler: A way to run the script automatically, like a cron job on a server or a serverless function (e.g., AWS Lambda).

The Step-by-Step Guide

Alright, let’s get to the good stuff. I’ll skip the standard project and virtual environment setup since you likely have your own workflow for that. We’ll jump straight into the logic and code.

Step 1: Secure Your Credentials

First things first, we never hardcode secrets. I use a simple configuration file for this. In your project directory, create a file named config.env. This is where we’ll store all our sensitive information.

# Shopify API Credentials
SHOPIFY_STORE_URL='your-store-name.myshopify.com'
SHOPIFY_API_VERSION='2023-10'
SHOPIFY_API_PASSWORD='your-private-app-password'

# Alerting Configuration
STOCK_THRESHOLD=10
ALERT_RECIPIENT_EMAIL='your-team@example.com'

# SMTP Email Credentials
SMTP_HOST='smtp.example.com'
SMTP_PORT=587
SMTP_USER='your-sending-email@example.com'
SMTP_PASSWORD='your-email-app-password'

Step 2: The Python Script – Setup and Dependencies

Now, let’s create our Python script, let’s call it check_inventory.py. We’ll need a few libraries to handle API requests, environment variables, and sending emails. You can install them with pip: python-dotenv for handling our config file, and requests for making HTTP calls. I’m assuming you have Python’s built-in `os` and `smtplib` ready to go.

Here’s how we’ll start our script by importing the necessary modules and loading our configuration.

import os
import requests
import smtplib
from email.mime.text import MIMEText
from dotenv import load_dotenv

# Load environment variables from config.env
load_dotenv('config.env')

# Configuration constants
SHOPIFY_STORE_URL = os.getenv('SHOPIFY_STORE_URL')
SHOPIFY_API_VERSION = os.getenv('SHOPIFY_API_VERSION')
SHOPIFY_API_PASSWORD = os.getenv('SHOPIFY_API_PASSWORD')
STOCK_THRESHOLD = int(os.getenv('STOCK_THRESHOLD', 10))
ALERT_RECIPIENT_EMAIL = os.getenv('ALERT_RECIPIENT_EMAIL')
SMTP_HOST = os.getenv('SMTP_HOST')
SMTP_PORT = int(os.getenv('SMTP_PORT', 587))
SMTP_USER = os.getenv('SMTP_USER')
SMTP_PASSWORD = os.getenv('SMTP_PASSWORD')

Step 3: Fetching Shopify Inventory Levels

Next, we need a function to connect to Shopify and pull the data. I prefer using the GraphQL API for this because it lets us request exactly the data we need, nothing more. We’ll query for product variants and their inventory quantity.

Pro Tip: Using GraphQL is much more efficient than the REST API for this task. With REST, you often have to make multiple calls to get products and then their inventory levels. With GraphQL, it’s a single, precise request. It saves bandwidth and helps you stay under API rate limits.

This function constructs the GraphQL query, sends it to your Shopify store’s API endpoint, and then filters the results to find products below our defined STOCK_THRESHOLD.

def get_low_stock_products():
    """Fetches product inventory from Shopify and returns items below the threshold."""
    api_url = f"https://{SHOPIFY_STORE_URL}/admin/api/{SHOPIFY_API_VERSION}/graphql.json"
    headers = {
        "Content-Type": "application/json",
        "X-Shopify-Access-Token": SHOPIFY_API_PASSWORD
    }
    
    # GraphQL query to get the first 100 product variants and their inventory
    query = """
    {
      productVariants(first: 100, query: "inventory_quantity:<=PLACEHOLDER") {
        edges {
          node {
            displayName
            inventoryQuantity
            product {
              title
            }
          }
        }
      }
    }
    """.replace("PLACEHOLDER", str(STOCK_THRESHOLD))

    try:
        response = requests.post(api_url, headers=headers, json={'query': query})
        response.raise_for_status() # Raise an exception for bad status codes (4xx or 5xx)
        data = response.json()
        
        low_stock_items = []
        variants = data.get('data', {}).get('productVariants', {}).get('edges', [])
        
        for edge in variants:
            node = edge['node']
            item_details = {
                'title': node['product']['title'],
                'variant': node['displayName'],
                'stock': node['inventoryQuantity']
            }
            low_stock_items.append(item_details)
            
        return low_stock_items
        
    except requests.exceptions.RequestException as e:
        print(f"Error fetching from Shopify API: {e}")
        return None

Step 4: Building and Sending the Email Alert

Once we have a list of low-stock items, we need to format it into a clean email and send it off. This function handles that. It takes the list of products, builds an HTML-formatted email body for readability, and uses `smtplib` to dispatch it.

def send_email_alert(products):
    """Sends an email alert with the list of low stock products."""
    if not products:
        print("No low stock items to report.")
        return

    # Create the email body
    subject = f"Low Stock Alert for {SHOPIFY_STORE_URL}"
    body_html = "<h2>The following items are running low on stock:</h2>"
    body_html += "<table border='1' cellpadding='5' cellspacing='0'>"
    body_html += "<tr><th>Product Title</th><th>Variant</th><th>Stock Left</th></tr>"
    for product in products:
        body_html += f"<tr><td>{product['title']}</td><td>{product['variant']}</td><td>{product['stock']}</td></tr>"
    body_html += "</table>"

    msg = MIMEText(body_html, 'html')
    msg['Subject'] = subject
    msg['From'] = SMTP_USER
    msg['To'] = ALERT_RECIPIENT_EMAIL

    try:
        with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
            server.starttls()
            server.login(SMTP_USER, SMTP_PASSWORD)
            server.sendmail(SMTP_USER, [ALERT_RECIPIENT_EMAIL], msg.as_string())
        print(f"Successfully sent low stock alert to {ALERT_RECIPIENT_EMAIL}")
    except smtplib.SMTPException as e:
        print(f"Failed to send email: {e}")

Step 5: Tying It All Together

Finally, we create a main execution block. This is the part of the script that runs when you call it from the command line. It calls our function to get the products and, if any are found, passes them to the email function.

if __name__ == "__main__":
    print("Checking Shopify for low stock items...")
    low_stock_products = get_low_stock_products()
    
    if low_stock_products is not None and len(low_stock_products) > 0:
        print(f"Found {len(low_stock_products)} low stock items. Sending alert...")
        send_email_alert(low_stock_products)
    elif low_stock_products is not None:
        print("Inventory levels are healthy. No alert needed.")
    else:
        print("Script failed to retrieve inventory data.")

Step 6: Schedule the Automation

A script isn’t an automation until it runs on its own. For a simple setup, a cron job is perfect. You can set it to run once a day, for example. To run this script every morning at 8 AM, your cron entry would look like this:

0 8 * * * python3 your_full_path_to/check_inventory.py

Pro Tip: For my production setups, I prefer running this kind of task on a serverless platform like AWS Lambda with a CloudWatch trigger. It’s more resilient than a single cron job, scales perfectly (not that we need it here), and you only pay for the few seconds it runs each day.

Where I Usually Mess Up (Common Pitfalls)

Even a simple script can have its quirks. Here are a few things that have tripped me up in the past:

  • Shopify API Rate Limits: If you have thousands of products, you might need to implement pagination in the GraphQL query. My example pulls the first 100, which is fine for most small-to-medium stores, but be mindful of Shopify’s limits.
  • Incorrect App Permissions: Double-check that your private app really does have read_products and read_inventory scopes. If not, you’ll just get an authentication error.
  • SMTP Server Blocks: Some email providers (especially corporate ones) have strict firewall rules. Your server might not be able to connect to the SMTP host. Also, using services like Gmail for this might require you to generate an “App Password” instead of using your main login password.

Conclusion

And that’s it. With a simple configuration file and a single Python script, you’ve automated a tedious but critical e-commerce task. This is the kind of bread-and-butter automation that makes a DevOps practice so valuable—it frees up human time for more complex problem-solving. Now you can rest easy knowing you’ll get a proactive alert before a bestseller goes out of stock.

Happy automating,
Darian Vance

Darian Vance - Lead Cloud Architect

Darian Vance

Lead Cloud Architect & DevOps Strategist

With over 12 years in system architecture and automation, Darian specializes in simplifying complex cloud infrastructures. An advocate for open-source solutions, he founded TechResolve to provide engineers with actionable, battle-tested troubleshooting guides and robust software alternatives.


🤖 Frequently Asked Questions

âť“ How does the script identify low-stock items in Shopify?

The script identifies low-stock items by querying Shopify’s GraphQL API for product variants, specifically filtering for `inventory_quantity` less than or equal to the `STOCK_THRESHOLD` defined in the `config.env` file.

âť“ How does this automated script compare to manual inventory checks or third-party Shopify apps?

This custom Python script automates a tedious manual task, freeing up engineering time. Compared to third-party Shopify apps, it provides a lightweight, cost-effective solution with complete control over the logic and data flow, avoiding recurring subscription fees while offering tailored functionality.

âť“ What are common pitfalls when setting up this Shopify inventory alert system?

Common pitfalls include encountering Shopify API rate limits for large inventories, misconfiguring private app permissions (e.g., missing `read_products` or `read_inventory` scopes), and issues with SMTP server connectivity or incorrect email credentials, often requiring ‘App Passwords’ for services like Gmail.

Leave a Reply

Discover more from TechResolve - SaaS Troubleshooting & Software Alternatives

Subscribe now to keep reading and get access to the full archive.

Continue reading