A lead in real estate is a fragile thing. It arrives warm, and it cools fast. The difference between a signed client and a missed one is often measured in minutes: who picked it up, how fast they reached out, and whether anyone can later prove the loop actually closed.
When that routing happens by hand on WhatsApp, it breaks down in predictable ways. Leads sit unread while three people each assume someone else has it. Two staff chase the same prospect, or neither does. "Did anyone call them back?" becomes an unanswerable question, because the answer is scattered across personal phones and memory.
This was the problem I was brought in to solve for a real-estate team: take the chaos of inbound WhatsApp leads and turn it into a system that routes, reminds, and records, without ever pretending a human did something they didn't.
Automate the busywork, keep the commitment human
One decision shaped everything else. A lead is a relationship, not a ticket, so the system would never auto-close one. Automation would own intake, routing, reminders, and the audit trail. A real person would own the moment of commitment, the part where you actually contact a prospect or consciously pass.
That sounds like a limitation, and it is exactly what lets the rest of the design work. The automation can be aggressive about speed and follow-up without ever overstepping, because every meaningful state change still passes through a human who chose to make it.
How a lead moves through it
I built the system as three coordinated workflows rather than one tangled one:
-
Intake and separation. Inbound messages get classified as customer or staff, and each takes a different path. A customer reply is never mistaken for a staff action, and a staff reply is never mistaken for a customer.
-
The human-gated loop. Each lead is held in a clear state until a person acts on it, with reminders nudging the owner. Nothing silently resolves.
-
Handoff and logging. Every claim, defer, and contact attempt writes a lead-agent event, so the question "who touched this lead and what did they do" always has a real answer.
The detail I'm proudest of: reading intent, honestly
The whole loop depends on parsing staff replies. I built claim and defer detection around normalized text and explicit payloads, so people could answer naturally and still be understood.
Then I made one decision that mattered more than the rest: replies like "Ok" and "Okay" are not recognized as claims.
They are the most common thing anyone types, and they prove nothing. "Ok" does not mean "I have contacted this lead." If the system treated it as a claim, it would quietly mark leads as handled that nobody had touched, and the records would start lying to the team in precisely the way the manual process already did.
So the system demands an unambiguous signal. That single boundary is the difference between an automation you can trust and one that slowly erodes it. Building systems that refuse to misreport human intent is, in my experience, one of the most underrated skills in automation work, because the failure is silent: everything looks fine on the surface while leads slip through the cracks underneath.
The craft that makes it hold up
Interesting decisions are worthless if the plumbing leaks. A few things I got right on the unglamorous side:
-
WhatsApp template correctness. Templates break delivery silently when placeholders are wrong, so I moved every message to proper positional variables like {{1}} and {{2}}, and wired quick-reply labels such as "Already contacted" straight into the handoff. Staff respond with a tap instead of typing a command.
-
Taking a drifted mess and making it canonical. The workstream had sprawled across several versions and exports. I demoted the old ones to reference-only, settled on the v3 line as the source of truth, and wrote an audit documenting what each workflow actually did, so nobody had to guess anymore.
-
Import hygiene. The SeaTable package shipped a clean staff table and an events table with a single throwaway sample row, there only to create the structure and explicitly deleted after import. Reproducible setup, with no fake data seeping into production.
Import-ready is not live
The last principle is the least flashy and the one I would defend hardest. An import-ready package and a running, sending system are different things, and the space between them is where automation projects quietly die. "It works on my machine" is tolerable in a side project and corrosive in a lead system, because the failure is invisible: you believe leads are being handled, and they aren't.
So I held a strict line. Nothing counted as live until it cleared an explicit gate: workflow import behavior, provider connection checks, disabled delete nodes, template approval and payload behavior, outbound webhook compatibility, and the live property table schema. The point was never a checklist for its own sake. It was one rule, applied without exception: never let the system claim a lead is being handled unless you can prove the system is actually running.
What I take from it
This is the kind of work I like most. A messy, high-stakes human process, a real cost when it goes wrong, and a system that has to earn its operators' trust before it is worth anything. The interesting part was never sending the messages. It was designing a loop where humans and automation each do the part they are actually good at, and where the system tells the truth about what happened.