Most Indian e-commerce sellers are managing their stores from a phone, coordinating with suppliers on WhatsApp, and tracking inventory in Excel. The automation tools that enterprise brands take for granted — real-time stock alerts, competitor price monitoring, 24/7 customer support — feel out of reach.
They're not anymore. Three AI agents can do the work of a small operations team, and the total cost at Indian traffic scale is ₹500–2,000/month per agent.
This post builds all three: an inventory monitor, a competitor price tracker, and a customer query handler. Each is designed for a Meesho/Flipkart/Amazon India/Shopify seller who's doing ₹5–50L/month in GMV and wants to stop fighting fires manually.
Before you build: two rules that prevent expensive mistakes
Always use SKU codes, not product names. Product names change, have typos, and aren't unique. SKU codes are your source of truth across systems. If your Shopify store, your supplier spreadsheet, and your WhatsApp messages all use different identifiers for the same product, fix that first. Every agent below assumes you have clean, consistent SKU codes.
Always work in paise (integers), never in rupees (floats). ₹99.50 becomes 9950 paise. Floating-point comparisons on currency cause silent bugs. Store, compare, and pass all amounts as integers. Format for display only at the UI layer.
Agent 1: Inventory monitor
This agent checks stock levels every 6 hours. When any SKU drops below its reorder threshold, it drafts a WhatsApp message to the supplier — with the SKU, current stock, and a suggested reorder quantity based on recent sales velocity.
n8n workflow (no-code path)
If you're on n8n, this is four nodes:
- Schedule Trigger — every 6 hours
- Shopify node —
GET /products.json?fields=id,variantsto pull current inventory - Code node — filter to SKUs below threshold
- Claude node — draft the supplier message
- WhatsApp Business node — send to supplier number
The Claude node prompt:
You are an inventory manager for an Indian e-commerce business.
Below is a list of SKUs that have dropped below their reorder threshold. For each SKU, draft a WhatsApp message to the supplier asking for restocking. Include:
- The SKU code (not the product name)
- Current stock level
- Suggested order quantity (use 30-day sales velocity × 1.5 as the formula)
- Requested delivery date (10 days from today: {{$today.plus(10, 'days').format('DD MMM YYYY')}})
Keep each message under 100 words. Formal but not stiff — this is WhatsApp.
Low stock SKUs:
{{$json.low_stock_items}}
Python path (for Shopify + custom logic)
import anthropic
import requests
from datetime import datetime, timedelta
SHOPIFY_TOKEN = "your_token"
SHOPIFY_DOMAIN = "yourstore.myshopify.com"
REORDER_THRESHOLD = 10 # units
def get_low_stock_skus():
url = f"https://{SHOPIFY_DOMAIN}/admin/api/2024-01/products.json"
headers = {"X-Shopify-Access-Token": SHOPIFY_TOKEN}
products = []
page_info = None
while True:
params = {"limit": 250, "fields": "id,variants,title"}
if page_info:
params["page_info"] = page_info
r = requests.get(url, headers=headers, params=params)
data = r.json()
products.extend(data["products"])
link = r.headers.get("Link", "")
if 'rel="next"' not in link:
break
# Parse page_info from Link header for next page
page_info = link.split('page_info=')[1].split('>')[0]
low_stock = []
for product in products:
for variant in product["variants"]:
if variant["inventory_quantity"] < REORDER_THRESHOLD and variant["sku"]:
low_stock.append({
"sku": variant["sku"],
"title": product["title"],
"variant_title": variant["title"],
"current_stock": variant["inventory_quantity"],
"reorder_threshold": REORDER_THRESHOLD,
})
return low_stock
def get_30_day_velocity(sku: str) -> int:
"""Pull 30-day units sold from Shopify Orders API."""
since = (datetime.now() - timedelta(days=30)).isoformat()
url = f"https://{SHOPIFY_DOMAIN}/admin/api/2024-01/orders.json"
headers = {"X-Shopify-Access-Token": SHOPIFY_TOKEN}
params = {"status": "any", "created_at_min": since, "limit": 250, "fields": "line_items"}
r = requests.get(url, headers=headers, params=params)
orders = r.json().get("orders", [])
units_sold = 0
for order in orders:
for item in order["line_items"]:
if item.get("sku") == sku:
units_sold += item["quantity"]
return units_sold
def draft_reorder_messages(low_stock_items: list) -> str:
client = anthropic.Anthropic()
# Enrich with velocity data
for item in low_stock_items:
item["velocity_30d"] = get_30_day_velocity(item["sku"])
item["suggested_order"] = max(int(item["velocity_30d"] * 1.5), 20)
delivery_date = (datetime.now() + timedelta(days=10)).strftime("%d %b %Y")
prompt = f"""These SKUs are below reorder threshold. Draft a WhatsApp message per SKU to the supplier.
Include: SKU code, current stock, suggested order quantity, delivery date ({delivery_date}).
Keep each message under 100 words. Use WhatsApp-appropriate tone.
Items:
{low_stock_items}"""
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=800,
messages=[{"role": "user", "content": prompt}],
)
return response.content[0].text
def run_inventory_agent():
low_stock = get_low_stock_skus()
if not low_stock:
print("All SKUs above threshold.")
return
print(f"Found {len(low_stock)} low-stock SKUs:")
for item in low_stock:
print(f" {item['sku']}: {item['current_stock']} units remaining")
messages = draft_reorder_messages(low_stock)
print("\nDrafted supplier messages:")
print(messages)
# Send via WhatsApp Business API here
if __name__ == "__main__":
run_inventory_agent()
The agent drafts messages — you review and send. Never auto-send to suppliers. One bad reorder quantity costs more than the time you saved.
Agent 2: Competitor price monitor
This agent scrapes competitor product pages daily using Firecrawl, compares prices, and sends a pricing brief to Slack. It flags items where you're more than 15% higher (you're losing price-sensitive buyers) or more than 15% lower (you might be leaving margin on the table).
No auto-price-changes. The agent informs; you decide.
import anthropic
import requests
import json
from datetime import date
FIRECRAWL_API_KEY = "your_firecrawl_key"
SLACK_WEBHOOK = "your_slack_webhook"
# Your top 20 SKUs mapped to your price and competitor URLs
TRACKED_SKUS = [
{
"sku": "SKU-001",
"name": "Cotton Kurta - Blue XL",
"your_price_paise": 89900, # ₹899 — always in paise
"competitor_urls": [
"https://competitor1.com/product/cotton-kurta-blue",
"https://www.amazon.in/dp/B0XXXXXXX",
],
},
# ... more SKUs
]
def scrape_price(url: str) -> dict:
"""Use Firecrawl to extract price from a product page."""
r = requests.post(
"https://api.firecrawl.dev/v1/scrape",
headers={"Authorization": f"Bearer {FIRECRAWL_API_KEY}"},
json={
"url": url,
"formats": ["extract"],
"extract": {
"schema": {
"type": "object",
"properties": {
"price": {"type": "number"},
"currency": {"type": "string"},
"in_stock": {"type": "boolean"},
"product_name": {"type": "string"},
}
}
}
},
)
if r.ok:
return r.json().get("extract", {})
return {}
def generate_pricing_brief(comparison_data: list) -> str:
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=600,
messages=[{
"role": "user",
"content": f"""Generate a daily pricing brief for this e-commerce store.
Data: {json.dumps(comparison_data, indent=2)}
Format the brief as:
1. Summary: X SKUs checked, Y need attention
2. Overpriced (>15% above competitor): list with specific prices in ₹
3. Underpriced (>15% below competitor): list with potential margin gain
4. Competitive (within 15%): just the count
Keep it under 200 words. Use ₹ amounts, not paise."""
}],
)
return response.content[0].text
def run_price_monitor():
comparison_data = []
for sku in TRACKED_SKUS:
competitor_prices = []
for url in sku["competitor_urls"]:
scraped = scrape_price(url)
if scraped.get("price"):
competitor_prices.append({
"url": url,
"price_paise": int(scraped["price"] * 100), # convert to paise
"in_stock": scraped.get("in_stock", True),
})
if competitor_prices:
avg_competitor_paise = sum(p["price_paise"] for p in competitor_prices) // len(competitor_prices)
diff_pct = ((sku["your_price_paise"] - avg_competitor_paise) / avg_competitor_paise) * 100
comparison_data.append({
"sku": sku["sku"],
"name": sku["name"],
"your_price_inr": sku["your_price_paise"] / 100,
"avg_competitor_price_inr": avg_competitor_paise / 100,
"diff_pct": round(diff_pct, 1),
"status": "overpriced" if diff_pct > 15 else "underpriced" if diff_pct < -15 else "competitive",
})
brief = generate_pricing_brief(comparison_data)
# Post to Slack
requests.post(SLACK_WEBHOOK, json={
"text": f"*Daily Pricing Brief — {date.today().strftime('%d %b %Y')}*\n\n{brief}"
})
print("Pricing brief sent to Slack.")
if __name__ == "__main__":
run_price_monitor()
Run this once daily (early morning, before business hours). Firecrawl costs roughly ₹0.05–0.10 per URL scraped. For 20 SKUs × 2 competitor URLs each = 40 scrapes/day = ~₹120/month.
Agent 3: Customer query handler
This agent handles the three most common e-commerce support queries: order tracking, return eligibility, and product availability. It connects to Delhivery/Shiprocket for real shipment status and escalates anything that needs a human — refunds above ₹2,000, damaged goods, fraud claims.
import anthropic
import requests
import json
DELHIVERY_TOKEN = "your_delhivery_token"
SHOPIFY_TOKEN = "your_shopify_token"
SHOPIFY_DOMAIN = "yourstore.myshopify.com"
ESCALATION_THRESHOLD_PAISE = 200000 # ₹2,000
SYSTEM_PROMPT = """You are a customer support agent for an Indian e-commerce store. You have access to tools to check order status, shipment tracking, and product availability.
Rules:
- Always address the customer respectfully. Use "Ji" if the customer writes in Hindi-English mix.
- For order tracking, provide the exact status and last location from Delhivery, not generic phrases.
- For returns: our return window is 7 days from delivery. Only accept returns for wrong item, damaged item, or size exchange. No returns for change of mind.
- For refunds above ₹2,000, product damage claims, or suspected fraud: respond that a senior team member will contact them within 2 hours and set needs_escalation=true.
- Keep responses under 100 words unless the query is complex.
- Don't promise delivery dates — only state what tracking shows.
Always respond in the same language/mix the customer used."""
client = anthropic.Anthropic()
tools = [
{
"name": "get_order_status",
"description": "Look up an order by order ID or the customer's phone number. Returns order items, amounts, current status, and shipment tracking number.",
"input_schema": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "Shopify order ID or order number (e.g. #1234)"},
"phone": {"type": "string", "description": "Customer's phone number if order ID not provided"},
},
},
},
{
"name": "get_shipment_tracking",
"description": "Get real-time shipment tracking from Delhivery or Shiprocket using a tracking number.",
"input_schema": {
"type": "object",
"properties": {
"tracking_number": {"type": "string"},
"carrier": {"type": "string", "enum": ["delhivery", "shiprocket", "bluedart", "ecomexpress"]},
},
"required": ["tracking_number"],
},
},
{
"name": "check_product_availability",
"description": "Check if a product SKU is in stock, and if not, get expected restock date if available.",
"input_schema": {
"type": "object",
"properties": {
"sku": {"type": "string"},
"size": {"type": "string", "description": "Size variant if applicable (S/M/L/XL or numeric)"},
},
"required": ["sku"],
},
},
]
def get_order_status(order_id: str = None, phone: str = None) -> dict:
headers = {"X-Shopify-Access-Token": SHOPIFY_TOKEN}
if order_id:
order_id_clean = order_id.lstrip("#")
url = f"https://{SHOPIFY_DOMAIN}/admin/api/2024-01/orders/{order_id_clean}.json"
r = requests.get(url, headers=headers)
elif phone:
url = f"https://{SHOPIFY_DOMAIN}/admin/api/2024-01/orders.json"
r = requests.get(url, headers=headers, params={"phone": phone, "limit": 5})
else:
return {"error": "Provide either order_id or phone number"}
if not r.ok:
return {"error": "Order not found"}
data = r.json()
order = data.get("order") or (data.get("orders") or [None])[0]
if not order:
return {"error": "No order found for that customer"}
return {
"order_number": order["order_number"],
"status": order["fulfillment_status"] or "unfulfilled",
"total_paise": int(float(order["total_price"]) * 100),
"items": [{"sku": li["sku"], "title": li["title"], "qty": li["quantity"]} for li in order["line_items"]],
"tracking_number": order["fulfillments"][0]["tracking_number"] if order.get("fulfillments") else None,
"carrier": order["fulfillments"][0]["tracking_company"] if order.get("fulfillments") else None,
}
def get_shipment_tracking(tracking_number: str, carrier: str = "delhivery") -> dict:
if carrier == "delhivery":
r = requests.get(
"https://track.delhivery.com/api/v1/packages/json/",
params={"waybill": tracking_number, "token": DELHIVERY_TOKEN},
)
if r.ok:
data = r.json()
pkg = (data.get("ShipmentData") or [{}])[0].get("Shipment", {})
return {
"status": pkg.get("Status"),
"last_location": pkg.get("Destination"),
"expected_delivery": pkg.get("PromisedDeliveryDate"),
"scans": [s["ScanDetail"] for s in (pkg.get("Scans") or [])[-3:]],
}
return {"error": f"Tracking unavailable for carrier: {carrier}"}
def handle_query(customer_query: str, customer_phone: str = None) -> dict:
messages = [{"role": "user", "content": customer_query}]
needs_escalation = False
for _ in range(5): # max 5 tool rounds
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=400,
system=SYSTEM_PROMPT,
tools=tools,
messages=messages,
)
if response.stop_reason == "end_turn":
reply_text = response.content[0].text if response.content else ""
return {
"reply": reply_text,
"needs_escalation": needs_escalation,
}
# Process tool calls
tool_results = []
for block in response.content:
if block.type == "tool_use":
if block.name == "get_order_status":
result = get_order_status(**block.input)
# Check escalation threshold
if result.get("total_paise", 0) > ESCALATION_THRESHOLD_PAISE:
needs_escalation = True
elif block.name == "get_shipment_tracking":
result = get_shipment_tracking(**block.input)
elif block.name == "check_product_availability":
result = {"in_stock": True, "sku": block.input["sku"]} # replace with real lookup
else:
result = {"error": "Unknown tool"}
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result),
})
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
return {"reply": "Please contact our support team for help with this.", "needs_escalation": True}
Escalation queries get routed to a human via WhatsApp or your helpdesk. The agent handles ~80% of volume; humans handle the rest.
Cost estimate
| Agent | Queries/day | Model | Cost/month |
|---|---|---|---|
| Inventory monitor | 4 runs/day | Haiku | ~₹150 |
| Price monitor | 1 run/day, 40 scrapes | Haiku + Firecrawl | ~₹400 |
| Customer queries | 50 queries/day | Haiku | ~₹600 |
| Total | ~₹1,150/month |
At 1,000+ queries/day, costs scale linearly. Budget ₹15–20 per 1,000 customer interactions.
Starting path
If you're new to this: start with n8n's free tier. It handles all three agents if your order volume is under 1,000/day. The n8n automation guide covers the setup — the Claude node configuration is the same.
The inventory monitor is the highest-ROI starting point. A single stockout of a fast-moving SKU costs more than months of automation. Build that one first, prove the value, then add pricing and support.
API rate limits during sale events (Big Billion Day, End of Season Sale) are real. Add retry logic with exponential backoff on every external API call. Shopify's burst limit is 40 requests/second — you won't hit it with these agents, but Delhivery has a lower limit that you will hit during a sale if you're polling frequently.



