Research note
Routing a lead in under a second
Most pipeline is lost in the first hour, not the last mile. What it takes to give every inbound lead an owner before it goes cold.
Ask a sales manager where deals are lost and you will hear about pricing, competition, or the last conversation before the close. Look at the data in an inbound business and the answer is almost always earlier and duller than that: a lead came in, nobody owned it for four hours, and by the time someone called, the customer had already spoken to two other companies.
This is a routing problem dressed up as a sales problem, and it is the part of Astrosia CRM the lab spends the most time on.
Assignment is the whole game
A lead with no owner is not in a pipeline, it is in a queue that nobody is watching. So the first requirement is unambiguous: every lead gets an owner at creation time, synchronously, before the write is acknowledged. Not a background job, not a nightly rebalance. If assignment can fail independently of creation, you will eventually have leads that exist and belong to no one, and nothing in the interface will make them visible.
That constraint sounds obvious and it has a sharp consequence: the assignment decision has to be fast enough to sit inside the request. Which rules out most of the interesting things you might want to do, such as scoring against history, checking availability across channels, or load-balancing on live capacity, unless you can do them on data you already hold in memory.
What we compute at write time
- The routing pool the lead belongs to, from source and any campaign identifiers on the payload.
- Current assignable members of that pool, from a small cached view rather than a live query.
- A next-owner pick within the pool, weighted by open load rather than strict round robin.
Everything else, including enrichment, scoring, and deduplication against older records, happens afterwards and is allowed to be slow, because it can change the lead without changing whether somebody owns it.
Webhooks arrive more than once
Lead sources retry. Ad platforms retry aggressively, form integrations retry on any non-2xx, and a few will send the same lead twice for no discernible reason. If a retry produces a second lead record, two reps call the same person within a minute of each other, which is worse than not calling at all.
The fix is not clever, it just has to be applied everywhere: every inbound write carries an idempotency key derived from the source and its own identifier for the submission, and that key has a uniqueness constraint in the database. A duplicate delivery hits the constraint and returns the original record's result. The important detail is that the guarantee lives in the schema, not in application code. Application-level checks have a race window between the read and the write, and at the volumes where duplicates matter, that window is not theoretical.
The subtle part: a retry must return success, not a conflict error. A source that receives an error will keep retrying, and you have converted a duplicate into an infinite loop.
WhatsApp has a clock on it
WhatsApp business messaging has a session window: after a customer messages you, there is a bounded period in which you can reply freely, and outside it you are restricted to pre-approved templates. This turns a messaging channel into a system with a deadline, and the deadline is invisible in most CRM interfaces.
We surface it directly. A conversation shows the remaining window, and automation that would send a free-form message outside it is blocked before it is queued rather than failing at the provider. The alternative, discovering the window closed when the send fails, means the customer gets silence, and the rep finds out later.
This also shapes what automations we allow. A follow-up rule that fires "in 48 hours" is, on WhatsApp, a rule that fires outside the window. So follow-up timing is expressed relative to the window rather than in absolute hours, and the interface says so.
Automations should fire on evidence
The most common automation in any CRM is a timer: no reply in N hours, send a nudge. It is easy to build and it is responsible for a large share of the messages customers describe as spam, because a timer does not know that the customer replied on a different channel, or that a rep called them, or that they already bought.
We moved our automation triggers from elapsed time to observed state. The rule fires when the lead has had no inbound activity of any kind for a period, where activity is unified across form, WhatsApp, call, and email. The rule looks similar in the interface. The behaviour is very different, because it stops firing at people who are already engaged.
The engineering cost is that "activity" has to be one concept with one timeline, rather than four channel-specific logs that happen to sit next to each other. Getting to that unified timeline was most of the work, and it is the same substrate the conversation ownership paper argues for.
What is still open
Attribution. A lead touched by a form, a WhatsApp thread, a Voiceplix agent, and then a human rep has no single owner of the outcome, and every model we have tried is either too simple for the sales manager to believe or too complicated for them to explain to their team. It is on the open problems list and we do not have an answer yet.