Fixing a live CRM
A sales CRM had grown to 21 pipelines and about 250 stages, 131 fields on a deal, 191 tags and 17 external webhooks, with 41 user accounts of which 10 were active. Of 193 automation triggers, 34 were running, and half the stages asked for no action at all.
A target setup of five pipelines built around the client lifecycle, with stage limits that close out when they pass, technical stages hidden from the reps, and five task types where there had been 23.
I read the whole system through the API without changing anything: every pipeline, stage, field, tag, webhook and trigger. Then I worked out what each one was actually used for, and designed the replacement from that.
Deals were dying at stages without anyone noticing, and the “last modified” field could not be trusted: bots and bulk edits kept updating it while nobody had written to the client.
The watcher runs in report mode today, with every action logged and its reason recorded. The three escalation steps are the part I am finishing now.
I built a watcher that keeps two clocks, when the record was touched and when someone last wrote to the client, and runs the stage limits off the second one. The rules live in a config table by stage type, so a new pipeline is a new row.
Team statistics and the review lists came from different scripts and never matched. The manager wanted an honest answer to “what on this rep’s list needs action right now”, and the reps wanted a list without junk in it.
Both views now come from the same rules, and a selection scans about 3,000 cards for one rep instead of 23,000.
I put both behind one tool. Along the way the system dates turned out to be useless here: for one rep, 12 of 34 open deals would have read as abandoned because the tasks sat on the contact, and 15 of 22 showed the same last-modified date while the real touches ran across nineteen months.
Out of the spreadsheets
An assistant ran 21 calls before an industry event, each between 27 seconds and four minutes, and the results landed only in a shared spreadsheet: nothing in the CRM, no notes, no next step.
The cards and the link are in place. The call recordings are not in the CRM yet.
I rebuilt a card for every call from the sheet, with where the lead sat, when and how long the call was, and how it ended, sorted into five outcomes. Then I linked the sheet to the CRM, so adding a person to a rep’s working list moves the deal, the contact and the tasks to that rep, with a way back for every move.
Before a rebuild we needed to know what the data really contained, and the built-in reports did not show it.
Stages came down to one dictionary of won, lost and open, after which conversion could be counted.
I exported 22,787 deals with 205 columns and 26,782 contacts into DuckDB and found three traps: the same “lost” stage in dozens of spellings and more than one language, holding 1,776 records; fields that looked filled but held zeros and empty strings; and a phone number on about half the deals, an email on just over half.
Mailing to the company’s own base from the CRM ran into the system’s limits: no segments, tag search broken in the API, the same people entered twice, and greetings in several languages set at random.
One run produced 333 records across eight reps, each tagged with a rollback file. The pipeline cannot send, on purpose: emails go out from the reps’ own mailboxes.
The pipeline builds the list from a description in plain language, applies exclusions, removes duplicates by email and phone tail, sets the greeting by language, and splits the list by rep into segments and templates.
Getting an enquiry to the right desk
Incoming requests were sorted by hand: somebody checked whether the request fitted, picked a manager, and tried to remember when to follow up.
Two stages run this way today, and the link to the analysis stage is the next step.
I first drew the process as a BPMN diagram, with its checks, its waiting timers and a separate reason for every way a request can close. Then I automated it on the CRM: a script checks each request against four criteria, closes the ones that do not fit with the reason, and sends the rest to the right manager with a welcome message ready and follow-up tasks at one hour, one day and seven days.
Enquiries from the chat widget reached the CRM mixed together: real requests, stray clicks on the bot’s buttons with no message, and requests with no contact at all. The text of the request often stayed in the chat and never made it to the card.
Each route now has its own ending, and the point of every request is read from the chat itself through the API.
Requests with no contact are closed at once with the reason, along with the conversation. For a request with a contact a reply is drafted and goes out only after a person approves it. Large or urgent ones go straight to the head of sales.
The biggest item in the team’s time was rebuilding context: every reply started with reading the whole chat history, because the CRM did not hold the facts they needed. Sometimes that ended in work on the wrong request.
Most of the routing now happens in the form, and confirmations and reminders switch on only after the brief.
I designed two levels: five gating questions before the request, a full brief sent automatically after it. Every field survived only if the answer changed what we would do, and answers write straight into CRM fields.
Asking the CRM in plain language
To see what was urgent that day, reps opened dozens of cards across the CRM, email and messengers.
The managers took to the terminal interface. The reps did not, and for them the same layer had to be built into the CRM itself, where they already were.
For a team of nine on eleven active pipelines I built commands an AI agent runs in plain language: a morning brief with overdue items, a client card in one command, a stage move dictated as a thought, and no change to the CRM without a confirmation.
Reps answered client messages in WhatsApp by hand, and every reply meant rereading the whole conversation. A fully automatic bot was out of the question, since one wrong phrase can cost a deal.
It works only in conversations the rep has tagged, and nothing reaches the client without a person approving it.
The service drafts a reply to each incoming message from the full conversation, in that rep’s own voice, and sends it to Telegram with buttons to send, edit, check grammar or translate.
For an AI agent to work with a team’s data it has to be connected to the systems the team uses, and a copied export goes stale the day it is made.
The agent reads from the live systems at the moment it is asked.
I built two connectors on the Model Context Protocol. One picks up unread email and turns the messages that matter into pages of the knowledge base. The other gives the agent the CRM’s API reference, so that when it writes an integration it relies on methods that exist.
Outside the services
Two of these come from running a sales team. The third is open source.
A team had no shared view of its numbers, so I built dashboards for it.
The managers accepted the tool, part of the team refused it.
The numbers in the dashboards were right. They were also awkward and did not fit how people already worked: using them meant changing a routine, and the size of that change put people off more than the screens did. Since then I start by looking at where people already spend their time, and I put the report there.
There was a revenue target and no department to reach it.
A team working remotely across several time zones, with levels and lead handover between them.
A forecast on historical data first, then the unit economics: what margin, how many people, what each stage had to produce. The pay plan came out of those targets, so it matched the economics of the department. I hired the people myself and wrote the onboarding, the scripts and the working rules.
Obsidian has no built-in way to keep a vault in step across desktop, iOS and Android without a paid subscription and somebody else’s cloud.
Listed in the Obsidian community catalogue after their review, with the source on GitHub. Everything above sits under NDA. This one you can open and check yourself.
Syncs a vault between desktop, iOS and Android through a server you run yourself. The contents are encrypted before they leave the device.
Certificates and statuses
Links go to where each one can be checked.
If one of these looks like your system, book a call and tell me which.