If you've ever had a customer ask for a product you thought you had in stock—only to discover you're completely out—you know the pain.
I run a small e-commerce operation, and for the first two years I tracked inventory in a spreadsheet. I'd forget to check it, run out of things I didn't realize I was running out of, and lose sales.
Then I spent an afternoon writing this Python script. Now I get an email every morning showing me what's low, and another alert the moment anything hits my reorder threshold.
Zero stockouts in the last 8 months.
The Script
import smtplib
import csv
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from datetime import datetime
# Configure these
EMAIL_FROM = "your-email@gmail.com"
EMAIL_TO = "your-email@gmail.com"
EMAIL_PASSWORD = "your-app-password"
REORDER_THRESHOLD = 5 # Alert when quantity drops below this
def load_inventory(filename="inventory.csv"):
"""Load inventory from CSV file."""
inventory = []
try:
with open(filename, "r") as f:
reader = csv.DictReader(f)
for row in reader:
inventory.append({
"sku": row["sku"],
"name": row["name"],
"quantity": int(row["quantity"]),
"reorder_point": int(row.get("reorder_point", REORDER_THRESHOLD)),
"supplier": row.get("supplier", "Unknown"),
})
except FileNotFoundError:
print(f"Inventory file '{filename}' not found. Creating sample...")
create_sample_inventory(filename)
return load_inventory(filename)
return inventory
def create_sample_inventory(filename):
"""Create a sample inventory CSV."""
sample = [
{"sku": "PROD001", "name": "Widget A", "quantity": 50, "reorder_point": 10, "supplier": "Supplier Co"},
{"sku": "PROD002", "name": "Widget B", "quantity": 3, "reorder_point": 5, "supplier": "Supplier Co"},
{"sku": "PROD003", "name": "Widget C", "quantity": 0, "reorder_point": 5, "supplier": "Other Supplier"},
]
with open(filename, "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["sku", "name", "quantity", "reorder_point", "supplier"])
writer.writeheader()
writer.writerows(sample)
def check_low_stock(inventory):
"""Return items at or below reorder point."""
return [item for item in inventory if item["quantity"] <= item["reorder_point"]]
def send_alert_email(low_stock_items):
"""Send an email alert for low stock items."""
if not low_stock_items:
print("No low stock items. No email sent.")
return
subject = f"🚨 Low Stock Alert - {len(low_stock_items)} item(s) need reordering"
rows = ""
for item in low_stock_items:
status = "OUT OF STOCK" if item["quantity"] == 0 else f"Low ({item['quantity']} left)"
rows += f"""
<tr style="{'background: #ffe0e0;' if item['quantity'] == 0 else ''}">
<td style="padding: 8px; border: 1px solid #ddd;">{item['sku']}</td>
<td style="padding: 8px; border: 1px solid #ddd;">{item['name']}</td>
<td style="padding: 8px; border: 1px solid #ddd; font-weight: bold;">{item['quantity']}</td>
<td style="padding: 8px; border: 1px solid #ddd;">{item['reorder_point']}</td>
<td style="padding: 8px; border: 1px solid #ddd; color: red; font-weight: bold;">{status}</td>
<td style="padding: 8px; border: 1px solid #ddd;">{item['supplier']}</td>
</tr>"""
html = f"""
<html><body>
<h2>⚠️ Inventory Alert - {datetime.now().strftime('%B %d, %Y')}</h2>
<p>The following items need to be reordered:</p>
<table style="border-collapse: collapse; width: 100%;">
<tr style="background: #f0f0f0;">
<th style="padding: 8px; border: 1px solid #ddd;">SKU</th>
<th style="padding: 8px; border: 1px solid #ddd;">Name</th>
<th style="padding: 8px; border: 1px solid #ddd;">In Stock</th>
<th style="padding: 8px; border: 1px solid #ddd;">Reorder Point</th>
<th style="padding: 8px; border: 1px solid #ddd;">Status</th>
<th style="padding: 8px; border: 1px solid #ddd;">Supplier</th>
</tr>
{rows}
</table>
<br>
<p>Take action now to avoid stockouts!</p>
</body></html>
"""
msg = MIMEMultipart("alternative")
msg["Subject"] = subject
msg["From"] = EMAIL_FROM
msg["To"] = EMAIL_TO
msg.attach(MIMEText(html, "html"))
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
server.login(EMAIL_FROM, EMAIL_PASSWORD)
server.sendmail(EMAIL_FROM, EMAIL_TO, msg.as_string())
print(f"✅ Alert sent for {len(low_stock_items)} item(s)")
def main():
print(f"🔍 Checking inventory at {datetime.now().strftime('%Y-%m-%d %H:%M')}...")
inventory = load_inventory()
print(f"Loaded {len(inventory)} items")
low_stock = check_low_stock(inventory)
print(f"Found {len(low_stock)} low/out-of-stock items")
for item in low_stock:
print(f" ⚠️ {item['name']} ({item['sku']}): {item['quantity']} left (reorder at {item['reorder_point']})")
send_alert_email(low_stock)
if __name__ == "__main__":
main()
Setting It Up
- Save as
inventory_alert.py - Create your
inventory.csvwith columns:sku, name, quantity, reorder_point, supplier - Set up a Gmail App Password (Google Account → Security → App Passwords)
- Run it:
python inventory_alert.py - Schedule it with cron:
0 8 * * * python /path/to/inventory_alert.py
The Full Picture
This script is part of a collection I built over two years of running my small business. I automated:
- ✅ Invoice generation and sending
- ✅ Expense categorization
- ✅ Inventory monitoring (this script)
- ✅ Customer follow-up emails
- ✅ Weekly report generation
- ✅ Payment reminders
If you want the full toolkit (12 scripts total, ready to run), I packaged everything up at Python Business Automation Toolkit for $29. It includes the code, setup instructions, and my cron schedule templates.
The inventory script alone has saved me from at least 3 stockouts. At $29, it paid for itself the first time it prevented an out-of-stock situation.
Quick Wins From Here
-
Add Slack notifications: Replace the email with a
requests.postto a Slack webhook - Connect to your e-commerce platform: Shopify has an API, WooCommerce too — swap the CSV load for an API call
- Track reorder history: Log when you reordered and auto-calculate your average consumption
The 80/20 here: the CSV version works immediately, today, for free. Start there.
What's the most painful manual business task you're still doing? Drop it in the comments — I might have already automated it.
Top comments (0)