01The headline
A 5-person BI team was spending most of every morning hand-triaging Salesforce cases - pulling the new ones, assigning owners, summarizing for routing, posting to Slack so everyone knew what they had. The work was routine but not skippable. I replaced the morning ritual with a Python service that does it automatically. Returns ~1,500 analyst hours per year, redirected toward the cases that actually need human judgment. Two-week build in Cursor with Claude CLI, on top of the Salesforce REST API - what used to be a six-month Apex/Flow project.
02Context & constraints
The team I lead supports all CRM Analytics dashboards and central analytics data globally - five people, growing scope, no headcount on the horizon. Case triage was eating the morning before anyone could touch real work.
A few constraints shaped the build:
- Auditability. Every automated decision had to be reviewable and reversible. No silent moves on customer-touching records.
- Don't touch the data model. I could build flows on top of the case object, but the schema itself stayed exactly as-is. No new fields, no migrations.
- Smooth the load. Assignment logic couldn't just round-robin. It had to factor in current load per analyst so one person didn't get five new cases on a Monday morning.
- Two weeks. I didn't want to spend a quarter on this. If I couldn't ship in two weeks, the right answer was probably not to ship at all.
03The decision
I built three small pieces, each replaceable:
- An auto-assigner. Rules-based, with a small ML-assisted bucket for cases that fit a known pattern. Smooths analyst load by checking each person's current case count before routing.
- An AI summarizer. For each newly-assigned case, generates a 4-sentence resolution starter and appends it to the internal notes field. The analyst reads it as a prompt, then does the real work.
- A Slack notifier. Posts every morning to the team channel: who got what, with the AI summary inlined. Replaces the manual "I'm taking this one" round-robin we'd been doing in standup.
The whole thing is a Python service on the Salesforce REST API, written in Cursor with Claude CLI as the pair-programmer. The AI-augmented workflow is what made the 2-week timeline real - same scope, an Apex/Flow build the year before would have been a quarter of work.
Auto-respond or auto-close cases. The AI summaries are good. They are not yet good enough to send to a customer. The cost of one bad auto-response on a real case is significantly worse than the cost of every analyst reading a one-paragraph internal summary. The human stays in the loop on anything outbound until the resolution model has been pressure-tested over a full quarter of confidence-bucket data.
04Process
Two weeks of build, two weeks of supervised operation, then a cron job.
- Week 1: auto-assignment logic. Pulled case data via the Salesforce REST API, wrote the routing rules, built the load-smoothing logic. Validated against a week of historical assignments to check the script would have made the same calls the team did manually.
- Week 2: AI summary + Slack notifier. Wrote and iterated on the prompt that turns a raw case into a 4-sentence resolution starter. Wired the Slack webhook. Ran end-to-end against a day of cases.
- Weeks 3–4: supervised runs. Manually triggered the script every morning, read everything it produced. Caught two prompt-shape issues in the first three days - quick fix, push, run again.
- Week 5+: automated. Scheduled the run as a daily cron job. Output to Slack, occasional spot-checks. It's been running unattended since.
Almost killed it. The first auth flow used a Salesforce session ID - easy to set up, instantly broken. Session IDs expire on the order of hours and the script was harvesting them manually from a browser session. Fine for a prototype run; fatal for a daily automated job. The fix was a pivot to OAuth 2.0 with a refresh token - the script trades the long-lived refresh token for a fresh access token on every run, no manual step. That pivot is the difference between "neat script I demo'd once" and "cron job that's been running unattended for months."
In hindsight, the SID-first approach was the right pragmatic call - get something working, then worry about scaling it. The mistake wasn't choosing SID; it was not budgeting for the auth rewrite upfront.
05Results
The chart on the work-index card shows the cumulative version of this - hours returned to the team, climbing from zero at launch to ~1,500 by the end of the first year.
06Tradeoffs & what I'd do differently
- Would do again - AI summaries to internal notes, not to the customer. The summaries are a tool for the human analyst, not a replacement for them. Pasting them into the internal notes field gave the team a starting point without ever risking a bad customer-facing message. Same call next time without question.
- Would revisit - the state-tracking layer. The script needs a place to log per-run state. I built that on local files initially; in hindsight a shared Google Sheet (or a small DB) would have made the daily output reviewable by anyone on the team without me sending updates. Friction on visibility costs you adoption.
- Would do differently - n8n turned out to be unnecessary. I lost 2 days figuring out what I needed and waiting on credentials, then realized I didn't need it for this iteration. Pure Python was lower friction for the scope. Definitely something to keep in mind for the future, just not the right tool for this build.
- Open question - automated responses. The AI summaries keep getting better. The honest question is whether they're good enough to auto-respond on the lowest-stakes case types. I'd want a quarter of confidence-bucket data and a peer review of the resolution model before I'd flip that switch.
07Artifacts
- Anonymized walkthrough · available on request
- Architecture overview (Python + Salesforce REST API) · available on request
- Sanitized auto-assignment logic · code excerpts on request