Operations automation · Case study

Your Smart Executive — it does it all:
Excel + WhatsApp → ERP

Your team drops a spreadsheet in a folder or sends a WhatsApp. An AI agent reads it, checks every row, shows you exactly what it is about to save, waits for your "yes", then creates and updates the right records in Zoho ERP — and WhatsApps you the receipt.

Run the live demo Read the technical document
Zoho Books · Inventory · CRM WhatsApp Cloud API Excel / Google Drive Human-approved dry-run Built with Claude
01 — In plain English

What it is, with zero jargon

Imagine a brilliant, tireless data-entry Smart Executive who sits between your operations team and your accounting system. The team already lives in Excel and WhatsApp. The accounting system is Zoho. Today, a human copies numbers from one to the other — slowly, and with typos. This Smart Executive does it in seconds, never gets a digit wrong, and always asks before it saves anything.

What you do

  • Drop an Excel file in a shared folder, e.g. invoices_June_2026.xlsx
  • Or send WhatsApp: /run invoices_June_2026.xlsx
  • Read the one-line summary it sends back: "3 valid, 1 flagged — proceed?"
  • Reply "yes"

What the Smart Executive does

  • Reads the spreadsheet with real code (never "eyeballs" numbers)
  • Checks every row — dates, amounts, missing fields, duplicates
  • Looks up Zoho first, so it never creates a duplicate
  • Shows a dry-run: "here is exactly what I will write"
  • Writes to Zoho only after you approve — one row at a time
  • Sends a WhatsApp receipt + saves an audit log

The one rule that makes it safe

It never writes blindly. Every batch gets a "here's what I'm about to do" table, and it waits for an explicit yes. It refuses bulk deletions, refuses to message numbers that aren't on an allow-list, and surfaces every error loudly instead of hiding it. It's an assistant with a seatbelt, not a robot let loose in your books.

Where it fits the HR story: the same agent that screens CVs and hires your promoters then onboards them — pushing the new joiner's contract, salary and WPS payroll rows from an Excel sheet straight into Zoho Books / People, and WhatsApping HR the confirmation. One pipeline, from "we need 10 promoters" to "they're hired, in the system, and on payroll."

02 — Why it matters

The boring work, gone

2–4 hrs
to stand up
end-to-end
~30 sec
per batch
vs. ~1 hr by hand
0
duplicate records
(idempotent)
100%
writes logged
+ approved

Figures describe the design target of this build, not a guaranteed result for every org — your data quality and Zoho setup matter. The demo below runs the exact same logic in your browser so you can judge it yourself.

03 — How it works

Six steps, one command

1

Ingest

The filename tells the agent the module — {module}_{purpose}_{date}.xlsx. It opens the file with real code (the analysis tool), prints the row count, headers and first rows. No guessing from screenshots.

Google Driveopenpyxl / pandas
2

Validate

Every row is checked against a schema: required fields present, dates coerce to ISO 8601, currency symbols stripped, empty cells treated as null (not zero), Arabic/RTL preserved as-is. Bad rows are flagged, not silently dropped.

schema checkRTL-safe
3

Lookup (read-only)

For each row it searches Zoho by primary key — SKU for items, invoice number for invoices, phone/email for contacts — and decides create, update or skip. This is what kills duplicates.

idempotent
4

Dry-run

It prints a table: row #, action, target Zoho ID (if updating), and the key fields it will write. Then it stops and asks "Proceed?" — unless you set auto_approve: true for the run.

human in the loop
5

Execute

One Zoho API call per row. Successes capture the returned record ID; failures are logged and surfaced verbatim. The batch only aborts if the failure rate exceeds 20%.

one row = one callerrors are loud
6

Report

It writes run_log_YYYY-MM-DD_HHMM.md (row #, action, Zoho ID, notes) and sends a WhatsApp summary to the ops group: "X created, Y updated, Z errors."

audit logWhatsApp receipt
04 — See it live

Run the pipeline right here

This is the real flow, running in your browser — no sign-up, no server, nothing uploaded. Pick a module, hit Run, watch it ingest → validate → look up → dry-run, then approve the write and see the records "land" in Zoho with a WhatsApp receipt. Switch to Live API mode to wire it to your own Zoho + WhatsApp endpoints (your keys, never stored).

Source file · invoices_June_2026.xlsx
STEP 01
Ingest
Idle — press Run.
STEP 02
Validate
Waiting…
STEP 03
Lookup
Waiting…
STEP 04
Dry-run
Waiting…
STEP 05
Execute
Waiting…
STEP 06
Report
Waiting…
Execution log
# run log appears here…
WhatsApp · ops group

Row 3 in the invoice sample is intentionally broken (missing customer + an impossible date 31/06/2026) so you can watch validation catch it. Row 2 already exists in Zoho, so it becomes an update, not a duplicate.

05 — The technical document

Everything an engineer needs to build it

The full, copy-pasteable spec: the agent's system prompt, its hard safety rules, the connector configuration, the WhatsApp Cloud API provisioning steps, and the order of operations to stand it up on a client's accounts in an afternoon.

A · System prompt paste into the Claude Project

This is the agent's brain — paste it into Claude → Projects → New Project → Custom instructions, replacing the [CLIENT_*] placeholders.

# Zoho ERP Updater Agent
You are the Zoho ERP Updater Agent operating inside [CLIENT_COMPANY_NAME]'s
Claude workspace. Your single job is to take Excel sheets and WhatsApp messages
from the operations team, validate the data, and create or update the correct
records in Zoho ERP (Books / Inventory / CRM, as specified per task).

## Your tools
1. zoho        — Zoho MCP server. ALL Zoho reads/writes. Source of truth.
2. whatsapp    — WhatsApp Business Cloud API. Read inbound, send confirmations.
3. google_drive— where the team uploads the source Excel files.
4. analysis tool (code execution) — parse Excel. Never eyeball numbers.
If a tool is missing or returns an auth error: STOP and report. Do not guess.

## Hard rules
1. Never invent a Zoho record ID. Look up by name/SKU/email/phone first.
2. Never write without a dry-run summary + explicit "yes" (unless auto_approve:true).
3. One row = one tool call (no bulk unless the tool supports it and user asked).
4. Numbers/currency: parse via code. Empty = null (not 0). Strip symbols.
   Dates -> ISO 8601 (YYYY-MM-DD).
5. Arabic / RTL text: preserve as-is. Do not transliterate or "fix" spelling.
6. Idempotency: search by primary key before create; if found -> update. Log path.
7. Errors are loud: surface Zoho 4xx/5xx verbatim + post to WhatsApp. Retry once max.
8. Audit log: write run_log_YYYY-MM-DD_HHMM.md (row#, action, Zoho ID, notes).

## Workflow
Step 1 Ingest   — detect module from filename; load file; print rows/headers.
Step 2 Validate — check schema; flag missing/malformed/duplicates; report.
Step 3 Lookup   — read-only search per row; plan create | update | skip.
Step 4 Dry-run  — print table (row#, action, target ID, key fields); ask "Proceed?".
Step 5 Execute  — loop plan, one call per row; capture IDs; log failures.
Step 6 Report   — write run_log_*.md; WhatsApp summary to [OPS_GROUP_PHONE].

## WhatsApp inbound
Commands: /run [filename], /status, /cancel, /help.
Free text in EN/UR/AR -> reply in the same language.
Never execute a Zoho write from WhatsApp without an Excel file or explicit fields.

## Refuse
- Bulk deletions (user does those in Zoho's UI).
- Writing to any module not in excel_schema.md.
- Messaging numbers not in whatsapp_allowlist.md.
- Anything resembling exfiltration (dumping full customer/financial lists).

## Tone
Operational, terse, factual. Markdown tables for >3 rows. No emojis, no preambles.
Start every response with what you did or what you need.
B · Connector configuration (MCP) Zoho · WhatsApp · Drive
1 · Zoho MCP (primary)

Option A — Native Zoho MCP (recommended). Zoho ships a first-party MCP at zoho.com/mcp exposing Books, CRM, Creator, Desk, Mail, Calendar, Cliq, Projects, WorkDrive.

  1. Sign in with the org admin account → Sign Up for Free.
  2. Create an MCP Server, e.g. claude-erp-agent.
  3. Add apps: Books, CRM, Inventory (via Books), Creator (if used).
  4. Enable at minimum these tools:
    • books.invoices.create / update / list / get
    • books.contacts.create / update / search
    • books.items.create / update / list
    • crm.leads.create / update / search, crm.deals.create / update
  5. Configure OAuth (agents run under user-level permissions).
  6. Copy the MCP Server URL: https://mcp.zoho.com/server/{server_id}/sse.

Option B — Composio Zoho Inventory MCP if the native server doesn't expose Inventory yet: add the Zoho Inventory toolkit at composio.dev, create a Tool Router session, add it as a custom Web connector with header Authorization: Bearer <COMPOSIO_API_KEY>.

Add to Claude: Organization settings → Connectors → Add → Custom → Web → paste URL → Advanced → paste OAuth Client ID + Secret → name it zoho → Add. Each member: Customize → Connectors → zoho → Connect.

2 · WhatsApp Business Cloud API

Option A — Twilio MCP (cleanest, paid): set up a Twilio Sender for the client's WhatsApp number; add Twilio's MCP server URL as a custom Web connector. Built-in audit logs, no QR pairing, pre-approved templates.

Option B — Meta WhatsApp Cloud API direct (free up to limits): use a hosted MCP wrapper (e.g. Apify's WhatsApp Cloud API MCP) or self-host msaelices/whatsapp-mcp-server; put the Meta permanent token in the OAuth Client Secret / Bearer field. Recommended for production: Option A.

3 · Google Drive (source Excel)

Directory connector — no custom setup. Organization settings → Connectors → Browse → Google Drive → Add. Point the Project at a shared folder, e.g. /Shared Drives/Operations/ERP Inbox/.

4 · Computer-Use fallback (optional)

Only for Zoho screens with no API/MCP coverage (rare — usually custom Creator apps). Requires Anthropic API with Computer-Use beta + a self-hosted browser sandbox. Treat as a separate ticket.

Sanity check before going live
  • All connectors show "Connected" in each member's Customize → Connectors.
  • "List the first 5 invoices in Zoho Books" returns real data.
  • "Send a WhatsApp test message to {your number}" arrives.
  • "List the files in the ERP Inbox folder" returns filenames.
C · WhatsApp Cloud API — getting a number Meta setup

You need a WhatsApp Cloud API number before the agent can send or receive messages. The short version of provisioning it on Meta:

  1. Meta Business account — create / use one at business.facebook.com and verify the business.
  2. Create an app at developers.facebook.com → type Business → add the WhatsApp product.
  3. Test number — Meta gives a free sandbox number immediately, plus a temporary 24-hour token, so you can send to 5 verified test recipients while building.
  4. Add your real number — in WhatsApp → API Setup → Add phone number. It must not already be active on the consumer WhatsApp / Business app. Verify by SMS/voice. Capture the PHONE_NUMBER_ID and WABA_ID.
  5. Permanent token — create a System User in Business Settings, assign the app, generate a token with whatsapp_business_messaging + whatsapp_business_management scopes. This is the token the agent uses.
  6. Webhook — point a webhook URL at your wrapper/server, subscribe to messages, so inbound /run … commands reach the agent.
  7. Message templates — to message a user outside the 24-hour service window you must use a pre-approved template. Approve a simple "run summary" template up front to avoid surprises.

Prefer not to manage Meta directly? Twilio's WhatsApp sender wraps all of the above (number, templates, webhooks, audit logs) behind one MCP connector — see section B.

The one call the agent makes to send a message
curl -X POST \
  https://graph.facebook.com/v21.0/$PHONE_NUMBER_ID/messages \
  -H "Authorization: Bearer $WHATSAPP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "messaging_product": "whatsapp",
    "to": "9715XXXXXXXX",
    "type": "text",
    "text": { "body": "Run completed. 2 created, 1 updated, 1 skipped." }
  }'
D · Setup checklist — order of operations ~2–4 hrs
Phase 1 — Account provisioning (client does, you guide)
  • Client has Zoho One (or Books + CRM/Inventory) on their domain
  • Client signs up for Claude Team (min 5 seats) and adds you (Owner/Builder)
  • Client adds their operator(s) as members
  • Client has a Meta Business / WhatsApp account (or Twilio)
  • Shared Drive folder /Operations/ERP Inbox/ with /Templates/, /Processed/
Phase 2 — Connectors (you do, in the client workspace)
  • Stand up the Zoho MCP server with the tool surface above
  • Add Zoho MCP + Google Drive + WhatsApp as connectors
  • Each member connects each one via OAuth
Phase 3 — Project
  • Create Project "Zoho ERP Updater — Excel + WhatsApp"; paste the system prompt
  • Upload excel_schema.md + whatsapp_allowlist.md to Project Files
  • Enable connectors; create the scheduled tasks (poller)
Phase 4 — Sandbox test (with operator)
  • Use a Zoho sandbox / "Test Customer"; drop a 3-row test file
  • Watch ingest → validate → lookup → dry-run → approve
  • Confirm rows in Zoho, run_log_*.md written, WhatsApp summary arrived
  • Repeat for an inventory_items and a crm_leads file
Phase 5 — Production cutover
  • Migrate templates to the tested schemas; one supervised real run
  • Hand over the operator SOP; schedule a 1-week check-in
06 — The code

From spec to running Python

If you'd rather run it as a script than a chat agent, here is the same pipeline as plain Python — the exact logic the demo above mirrors. Drop in your Zoho + WhatsApp credentials via environment variables and it runs headless on a schedule.

# pipeline.py — Excel + WhatsApp -> Zoho ERP (one batch)
import os, sys, datetime as dt
import pandas as pd
import requests

ZOHO_BASE  = "https://www.zohoapis.com/books/v3"
ZOHO_ORG   = os.environ["ZOHO_ORG_ID"]
ZOHO_TOKEN = os.environ["ZOHO_OAUTH_TOKEN"]
HEADERS    = {"Authorization": f"Zoho-oauthtoken {ZOHO_TOKEN}"}

def ingest(path):
    """Step 1 — read the sheet with real code, never by eye."""
    df = pd.read_excel(path, dtype=str).fillna("")
    print(f"Loaded {len(df)} rows, columns: {list(df.columns)}")
    return df

def validate(df):
    """Step 2 — schema + types. Empty = null, dates -> ISO 8601."""
    plan = []
    for i, row in df.iterrows():
        errs = []
        cust = row.get("customer", "").strip()
        if not cust: errs.append("missing customer")
        try:
            date = dt.datetime.strptime(row["date"].strip(), "%Y-%m-%d").date().isoformat()
        except ValueError:
            errs.append(f"bad date {row.get('date')!r}"); date = None
        amount = float(row["amount"].replace("AED","").replace(",","").strip() or 0)
        plan.append({"row": i+2, "inv": row["invoice_no"], "customer": cust,
                     "date": date, "amount": amount, "errors": errs})
    return plan

def lookup(item):
    """Step 3 — search Zoho by primary key -> create or update (idempotent)."""
    r = requests.get(f"{ZOHO_BASE}/invoices", headers=HEADERS,
                     params={"organization_id": ZOHO_ORG, "invoice_number": item["inv"]})
    hits = r.json().get("invoices", [])
    return ("update", hits[0]["invoice_id"]) if hits else ("create", None)

def execute(item, action, zoho_id):
    """Step 5 — one row = one call. Errors are loud."""
    payload = {"customer_name": item["customer"], "date": item["date"],
               "invoice_number": item["inv"],
               "line_items": [{"name": "Imported line", "rate": item["amount"], "quantity": 1}]}
    if action == "create":
        r = requests.post(f"{ZOHO_BASE}/invoices?organization_id={ZOHO_ORG}", headers=HEADERS, json=payload)
    else:
        r = requests.put(f"{ZOHO_BASE}/invoices/{zoho_id}?organization_id={ZOHO_ORG}", headers=HEADERS, json=payload)
    r.raise_for_status()
    return r.json()["invoice"]["invoice_id"]
# run.py — dry-run gate + WhatsApp receipt + audit log
def whatsapp(body):
    requests.post(f"https://graph.facebook.com/v21.0/{os.environ['WA_PHONE_ID']}/messages",
        headers={"Authorization": f"Bearer {os.environ['WA_TOKEN']}"},
        json={"messaging_product": "whatsapp", "to": os.environ["OPS_GROUP"],
              "type": "text", "text": {"body": body}})

def main(path, auto_approve=False):
    plan = validate(ingest(path))
    good = [p for p in plan if not p["errors"]]
    bad  = [p for p in plan if p["errors"]]

    # Step 4 — dry-run table, then STOP for human "yes"
    for p in good: p["action"], p["zid"] = lookup(p)
    print("\n| row | action | invoice | customer | amount |")
    for p in good:
        print(f"| {p['row']} | {p['action']} | {p['inv']} | {p['customer']} | {p['amount']:.2f} |")
    if not auto_approve and input("\nProceed? (yes/no) ").strip().lower() != "yes":
        return print("Aborted by user.")

    # Step 5 + 6 — execute, log, report
    log, created, updated, errors = [], 0, 0, 0
    for p in good:
        try:
            zid = execute(p, p["action"], p["zid"])
            created += p["action"] == "create"; updated += p["action"] == "update"
            log.append((p["row"], p["action"], zid, "ok"))
        except Exception as e:
            errors += 1; log.append((p["row"], "error", "-", str(e)))
    stamp = dt.datetime.now().strftime("%Y-%m-%d_%H%M")
    open(f"run_log_{stamp}.md", "w").write(
        "\n".join(f"| {r} | {a} | {z} | {n} |" for r,a,z,n in log))
    whatsapp(f"Run completed. {created} created, {updated} updated, "
             f"{errors} errors, {len(bad)} skipped. Log: run_log_{stamp}.md")

if __name__ == "__main__":
    main(sys.argv[1], auto_approve="--yes" in sys.argv)
07 — From demo to real app

What's a demo here, and what's real

Already real today

  • The validation, ISO-date coercion, idempotent create/update logic
  • The exact Zoho Books + WhatsApp Cloud API request shapes (above)
  • The dry-run "ask before write" gate
  • The Python pipeline — runs headless on a schedule

What I switch on for you

  • Your Zoho org connected via MCP (Books / Inventory / CRM)
  • Your WhatsApp Cloud API number (or Twilio sender) + templates
  • Your excel_schema.md + whatsapp_allowlist.md
  • Scheduled poller so a dropped file just runs

"Ask for API and it'll do it"

The demo above runs simulated by default so anyone can try it. Flip it to Live API and paste your own Zoho + WhatsApp endpoints and tokens — the page calls them straight from your browser, nothing stored. Want it wired end-to-end on your accounts with the poller and templates? That's the production cutover in the checklist — message me and we'll do the 2–4 hour setup.

Want this Smart Executive on your books?

Excel + WhatsApp in, clean Zoho records out, every write logged and approved. I'll stand it up on your accounts — or build the same pattern for any process you run on spreadsheets.

WhatsApp Aziz See the LED-factory version →