The collections system I built for a team I diagnosed through conversation
I was never assigned to the collections team. I never watched them work. Everything I knew about how they ran their process came from talking to them. I built a complete picture of their workflow from those conversations, designed a system around it, and shipped it. The code is just what the diagnosis happened to need.
- Requirements gathered through stakeholder interviews, not a handed-off spec
- Current-state to future-state workflow design for a team outside my reporting line
- Near-real-time Salesforce report data turned into a living AR ledger
- Secure project-director portal that turns scattered status requests into one controlled channel
Outstanding invoices lived in a spreadsheet nobody could keep current
The collections team needed to track every open invoice and get project directors to weigh in on the ones that were stuck. A director might be sitting on the one piece of information that would move an invoice toward payment. The question was a phone call or a hallway conversation, an email if someone remembered to send it. The answer, when it came, had to be typed back into the tracker by hand.
So the tracker was always a little behind reality. Someone would update one place and forget another. A status that mattered to cash flow sat in someone's inbox instead of in the system. The team was not short on effort. They were short on a single place where the truth lived and a clean way to ask the people who held the missing pieces.
Two teams pulling on the same thread from opposite ends
On one end, the collections team kept a spreadsheet alive by force of will. On the other, project directors got pinged for updates with no easy way to give them.
The cost was not loud. It accumulated. Invoices that should have closed stayed open a little longer than they needed to, because the loop between the question and the answer ran through too many manual hands. Cash that the company had already earned sat further out than it had to.
One ledger was missing, and the people who held the answers had no clean way in
Underneath the symptoms were three connected problems. There was no single source of truth, so the same invoice could say different things in different places. There was no real-time visibility, so the collections team was always reacting to a snapshot that had already aged. And the project directors, the people who actually held the answers, were being asked to update information through whatever channel happened to be handy that day, which meant updates were redundant, easy to lose, and never landed in one consistent place.
Diagnosing a process you are not inside, accurately enough to build for it, is the core of business analysis.
A living ledger, automatic outreach, and a portal that closes the loop
I built it in the order the problem demanded. First, the ledger: a Salesforce report export, refreshed on a schedule, that pulls near-real-time data, deduplicates against the existing tracker, and maintains one master record of every open invoice across both offices, through each stage of collections to the point of payment. One place where the truth lives.
Then the outreach. Instead of the collections team composing individual demands from scratch, the system generates a personalized email draft for each project director, built automatically from the latest data. The director gets an email, clicks a button, and lands on a page showing only their jobs. They type their updates, submit, and the updates write straight back to the tracker. They never touch the spreadsheet. I run the refreshes and generate the drafts. The loop that used to run through hallway conversations and forwarded emails now runs through one clean channel.
During testing I rejected an early link model because it leaned too heavily on visible URL values, and rebuilt the access model around signed tokens instead. The first design was not the right one, and that is the point: I found the weaker approach during development and closed it before the system was ever in use.
What replaced it was a signed-token model. Each director's link carries a cryptographic signature the system verifies before showing any invoice data, and the secret that creates those signatures lives only on the server. Editing the URL cannot expose someone else's invoices, because the link cannot be forged without the key. I also built the comparison logic so the system would not leak information through timing behavior. Financial data in a multi-user system deserved more than a working link. It deserved a real access model.
Each of the 31 versions closed a real edge case the previous one revealed.
The proof here is operational, not a number on a chart
There is no clean stopwatch metric for this one, so I would not force one. The proof is operational: the collections team went from a spreadsheet that was always slightly out of date to a single ledger tracking every open invoice across two offices through every stage of collections. Redundant updates across multiple systems went away. Status requests that used to scatter across inboxes and hallways now route to project directors through one secure portal and come back to one place.
And the financial data the system handles is protected by a real security model, built proactively, for a team whose problems were never formally mine to solve.
It does not depend on me being in the room
The portal is the handoff. Project directors enter their own invoice status updates directly, on their own time, through their own links. The information flows back into the tracker without me brokering it. The remaining operator step is narrow and deliberate: refresh the data and generate the outreach drafts. The actual update loop runs between the project directors and the system, not through me.
That was the goal from the start. Not to make myself the person every update runs through, but to build the clean channel that lets the people who hold the answers put them where they belong.
The code was never the point of this. The point was a process I did not own, a ledger nobody could keep current, and the people who held the answers having no clean way to give them. I solve that kind of problem with whatever it actually takes. This time it took a system. Next time it might take a conversation.