How to Automate Shopify Reports: Complete Python Guide

·7 min read·Ecommerce

Set up fully automatic Shopify reports — daily sales summaries, inventory alerts, and customer analytics — using Python and the Shopify Admin API with scheduling and email delivery.

How to Automate Shopify Reports: Complete Python GuideAI Generated Image

You should not be making Shopify reports by hand. Not daily ones, not weekly ones, not any of them.

Every piece of data in those reports already exists in the Shopify Admin API. The problem is not access — it is that nobody has wired the API output to a formatted report that sends itself. This guide does exactly that: fetch the data, shape it into the metrics that matter, generate a formatted report, and deliver it on a schedule.

By the end you will have a Python script that runs daily, pulls your store data, and emails a polished sales report to your team.

# Who This Is For

  • Store owners who log into Shopify every morning to check yesterday's numbers
  • Ecommerce managers who build weekly reports in Google Sheets or Excel from exported CSV files
  • Vibe coders who want to build a reporting pipeline that runs itself
  • Operations teams who need consistent metrics delivered to stakeholders without manual work

If you can read a spreadsheet, you can follow this guide. Python knowledge helps but the concepts are straightforward.

# The Automation Architecture

flowchart TD
  CRON["Scheduler\n(cron / Prefect)"] --> FETCH
  FETCH["Fetch from\nShopify API"] --> CLEAN["Clean &\nValidate"]
  CLEAN --> AGG["Aggregate\nMetrics"]
  AGG --> REPORT["Generate\nFormatted Report"]
  AGG --> DB[(Store\nHistory)]
  REPORT --> EMAIL["Email to\nTeam"]
  DB --> TREND["Week-over-Week\nComparisons"]

# What You Will Need

bash
pip install requests pandas openpyxl jinja2
  • A Shopify store with a custom app (Admin API access token with read_orders, read_products, read_customers scopes)
  • An SMTP-capable email account for delivery (Gmail app password works for small-scale use)

# Step 1: Set Up the Shopify API Client

The foundation is a clean API client that handles pagination and rate limiting.

python
import requests
import time
from datetime import datetime, timedelta

class ShopifyReporter:
    """Fetch and aggregate Shopify data for automated reporting."""

    def __init__(self, shop_url: str, api_token: str):
        """Initialise the Shopify API connection."""
        self.base_url = f"https://{shop_url}/admin/api/2026-01"
        self.session = requests.Session()
        self.session.headers["X-Shopify-Access-Token"] = api_token
        self.session.headers["Content-Type"] = "application/json"

    def _get_paginated(self, endpoint: str, params: dict = None) -> list:
        """Fetch all pages from a Shopify API endpoint."""
        url = f"{self.base_url}/{endpoint}"
        params = params or {}
        results = []

        while url:
            response = self.session.get(url, params=params)

            # Handle rate limiting
            if response.status_code == 429:
                retry_after = float(response.headers.get("Retry-After", 2))
                print(f"Rate limited. Waiting {retry_after}s...")
                time.sleep(retry_after)
                continue

            response.raise_for_status()
            data = response.json()

            # Extract items from response
            key = list(data.keys())[0]
            results.extend(data[key])

            # Check for next page via Link header
            link = response.headers.get("Link", "")
            url = None
            params = {}
            if 'rel="next"' in link:
                for part in link.split(","):
                    if 'rel="next"' in part:
                        url = part.split("<")[1].split(">")[0]

        return results

# Step 2: Fetch and Aggregate Order Data

The report needs three views: daily totals, product performance, and customer metrics.

python
import pandas as pd

class DailyReportBuilder:
    """Build a daily Shopify report from raw API data."""

    def __init__(self, shopify: ShopifyReporter):
        """Initialise with a connected Shopify client."""
        self.shopify = shopify

    def fetch_orders(self, days: int = 7) -> pd.DataFrame:
        """Fetch orders from the last N days."""
        since = (datetime.now() - timedelta(days=days)).isoformat()
        raw = self.shopify._get_paginated("orders.json", {
            "created_at_min": since,
            "status": "any",
            "limit": 250,
        })
        print(f"Fetched {len(raw)} orders from last {days} days")

        orders = []
        for o in raw:
            orders.append({
                "order_id": o["id"],
                "created_at": pd.to_datetime(o["created_at"]),
                "total": float(o["total_price"]),
                "subtotal": float(o.get("subtotal_price", 0)),
                "currency": o["currency"],
                "items": len(o.get("line_items", [])),
                "status": o.get("financial_status", "unknown"),
                "source": o.get("source_name", "unknown"),
                "customer_email": o.get("email", ""),
            })

        df = pd.DataFrame(orders)
        df["date"] = df["created_at"].dt.date
        return df

    def daily_summary(self, df: pd.DataFrame) -> pd.DataFrame:
        """Aggregate orders into daily summary metrics."""
        return (
            df.groupby("date")
            .agg(
                orders=("order_id", "count"),
                revenue=("total", "sum"),
                avg_order_value=("total", "mean"),
                items_sold=("items", "sum"),
                unique_customers=("customer_email", "nunique"),
            )
            .round(2)
            .reset_index()
            .sort_values("date")
        )

    def product_performance(self, days: int = 7) -> pd.DataFrame:
        """Get top-selling products by revenue."""
        since = (datetime.now() - timedelta(days=days)).isoformat()
        raw = self.shopify._get_paginated("orders.json", {
            "created_at_min": since,
            "status": "any",
            "limit": 250,
        })

        items = []
        for order in raw:
            for item in order.get("line_items", []):
                items.append({
                    "product": item["title"],
                    "variant": item.get("variant_title", "default"),
                    "quantity": item["quantity"],
                    "revenue": float(item["price"]) * item["quantity"],
                })

        df = pd.DataFrame(items)
        return (
            df.groupby("product")
            .agg(units=("quantity", "sum"), revenue=("revenue", "sum"))
            .sort_values("revenue", ascending=False)
            .head(20)
            .round(2)
            .reset_index()
        )

# Sample Output

text
Daily Sales Summary (Last 7 Days)
──────────────────────────────────
Date        │ Orders │ Revenue    │ AOV    │ Items │ Customers
2026-05-03  │ 42     │ £2,940.00  │ £70.00 │ 68    │ 38
2026-05-04  │ 55     │ £4,125.00  │ £75.00 │ 89    │ 51
2026-05-05  │ 38     │ £2,660.00  │ £70.00 │ 57    │ 34
2026-05-06  │ 61     │ £4,575.00  │ £75.00 │ 95    │ 54
2026-05-07  │ 47     │ £3,525.00  │ £75.00 │ 72    │ 42
2026-05-08  │ 53     │ £3,975.00  │ £75.00 │ 84    │ 49
2026-05-09  │ 49     │ £3,430.00  │ £70.00 │ 76    │ 44

# Step 3: Generate a Formatted Report

Raw data is not a report. Use Jinja2 templates to produce a polished HTML email.

python
from jinja2 import Template

REPORT_TEMPLATE = """
<html>
<body style="font-family: Arial, sans-serif; max-width: 700px; margin: 0 auto;">
<h2>Daily Sales Report — {{ date }}</h2>

<h3>Summary</h3>
<table style="border-collapse: collapse; width: 100%;">
<tr style="background: #f3f4f6;">
  <th style="padding: 8px; text-align: left;">Metric</th>
  <th style="padding: 8px; text-align: right;">Value</th>
  <th style="padding: 8px; text-align: right;">vs Last Week</th>
</tr>
{% for row in summary %}
<tr>
  <td style="padding: 8px;">{{ row.metric }}</td>
  <td style="padding: 8px; text-align: right;">{{ row.value }}</td>
  <td style="padding: 8px; text-align: right; color: {{ row.color }};">{{ row.change }}</td>
</tr>
{% endfor %}
</table>

<h3>Top Products</h3>
<table style="border-collapse: collapse; width: 100%;">
<tr style="background: #f3f4f6;">
  <th style="padding: 8px; text-align: left;">Product</th>
  <th style="padding: 8px; text-align: right;">Units</th>
  <th style="padding: 8px; text-align: right;">Revenue</th>
</tr>
{% for p in products %}
<tr>
  <td style="padding: 8px;">{{ p.product }}</td>
  <td style="padding: 8px; text-align: right;">{{ p.units }}</td>
  <td style="padding: 8px; text-align: right;">£{{ "%.2f"|format(p.revenue) }}</td>
</tr>
{% endfor %}
</table>
</body>
</html>
"""

class ReportFormatter:
    """Format aggregated data into a polished HTML report."""

    def __init__(self, daily: pd.DataFrame, products: pd.DataFrame):
        """Initialise with aggregated data."""
        self.daily = daily
        self.products = products

    def render_html(self) -> str:
        """Render the report as HTML."""
        today = self.daily.iloc[-1] if len(self.daily) > 0 else {}
        last_week = self.daily.iloc[-8] if len(self.daily) > 7 else today

        summary = [
            {
                "metric": "Revenue",
                "value": f{today.get('revenue', 0):,.2f}",
                "change": self._pct_change(today.get("revenue", 0), last_week.get("revenue", 1)),
                "color": "#22c55e" if today.get("revenue", 0) >= last_week.get("revenue", 0) else "#ef4444",
            },
            {
                "metric": "Orders",
                "value": str(int(today.get("orders", 0))),
                "change": self._pct_change(today.get("orders", 0), last_week.get("orders", 1)),
                "color": "#22c55e" if today.get("orders", 0) >= last_week.get("orders", 0) else "#ef4444",
            },
            {
                "metric": "Avg Order Value",
                "value": f{today.get('avg_order_value', 0):,.2f}",
                "change": self._pct_change(today.get("avg_order_value", 0), last_week.get("avg_order_value", 1)),
                "color": "#22c55e",
            },
        ]

        template = Template(REPORT_TEMPLATE)
        return template.render(
            date=today.get("date", ""),
            summary=summary,
            products=self.products.head(10).to_dict("records"),
        )

    def _pct_change(self, current, previous) -> str:
        """Calculate percentage change as a formatted string."""
        if previous == 0:
            return ""
        change = ((current - previous) / previous) * 100
        prefix = "+" if change > 0 else ""
        return f"{prefix}{change:.1f}%"

# Step 4: Automate Email Delivery

Send the formatted report via SMTP. This works with Gmail, Amazon SES, or any SMTP provider.

python
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import os

def send_report(html_content: str, recipients: list[str]):
    """Send the HTML report via email."""
    smtp_host = os.environ["SMTP_HOST"]
    smtp_port = int(os.environ.get("SMTP_PORT", 587))
    smtp_user = os.environ["SMTP_USER"]
    smtp_pass = os.environ["SMTP_PASS"]

    msg = MIMEMultipart("alternative")
    msg["Subject"] = f"Daily Sales Report — {datetime.now().strftime('%Y-%m-%d')}"
    msg["From"] = smtp_user
    msg["To"] = ", ".join(recipients)
    msg.attach(MIMEText(html_content, "html"))

    with smtplib.SMTP(smtp_host, smtp_port) as server:
        server.starttls()
        server.login(smtp_user, smtp_pass)
        server.send_message(msg)

    print(f"Report sent to {len(recipients)} recipients")

# Step 5: Schedule the Full Pipeline

Wire everything together and run it on a schedule. Use cron for simplicity or Prefect for monitoring.

python
def run_daily_report():
    """Execute the full daily reporting pipeline."""
    shop_url = os.environ["SHOPIFY_SHOP_URL"]
    api_token = os.environ["SHOPIFY_API_TOKEN"]

    shopify = ShopifyReporter(shop_url, api_token)
    builder = DailyReportBuilder(shopify)

    # Fetch and aggregate
    orders = builder.fetch_orders(days=7)
    daily = builder.daily_summary(orders)
    products = builder.product_performance(days=7)

    # Format and send
    formatter = ReportFormatter(daily, products)
    html = formatter.render_html()

    recipients = os.environ.get("REPORT_RECIPIENTS", "").split(",")
    send_report(html, recipients)

    print("Daily report complete")

if __name__ == "__main__":
    run_daily_report()

# Cron Schedule

bash
# Run daily at 8am
0 8 * * * cd /app && python shopify_report.py

# What This Replaces

Manual Process Automated Equivalent
Log into Shopify admin every morning API fetches data automatically
Export CSV, open in Excel Data loaded directly into pandas
Calculate daily totals manually Aggregation runs in seconds
Format a spreadsheet for stakeholders HTML template generates polished report
Copy-paste into email SMTP sends automatically
Compare to last week from memory Historical data enables precise comparisons

# Next Steps

For building a unified reporting layer across multiple ecommerce platforms, see Ecommerce Reporting API: Automate Store Data Collection. To pull more granular data from the Shopify API, see Shopify Reporting API: Pull Sales, Inventory, and Customer Data.

If your store also has performance issues, see How to Fix Slow Shopify Stores. For scheduling this pipeline with retries and monitoring, see Schedule and Orchestrate Workflows with Prefect.

For securing the API tokens used in this pipeline, see Secure Python Automation: Managing Secrets and Keys. Once reports are running reliably, set up Shopify performance monitoring with automated alerts to catch revenue drops or speed regressions the moment they occur.

Ecommerce optimisation services include building automated reporting pipelines for Shopify and other platforms.

Get in touch to discuss automating your Shopify reports.

Enjoyed this article?

Get notified when I publish new articles on automation, ecommerce, and data engineering.

automate shopify reportshow to automate shopify reportssetup automatic shopify reportsshopify report automation pythonautomatic shopify sales reportsshopify daily report scriptshopify api report generationautomated shopify analyticsshopify report email automationshopify kpi dashboard automation