Mox Technologies

Case study · demo build

Inbound call triage: spam, vendor, or real lead

Missed calls and texts get sorted into real leads, known clients, vendors, sales pitches, wholesalers, spam and wrong numbers. Every decision shows the evidence behind it.

This is a portfolio build, not a client project. The rules started from a triage process for a real-estate inbound line and were rewritten from scratch with synthetic data and no client information. It runs entirely in your browser, so classifying a call costs nothing.

53automated tests
27synthetic incidents, including traps
$0per classification

The problem

A busy inbound line gets a mix of real buyers and sellers, existing clients, title and escrow officers, lead-gen salespeople, wholesalers and robocalls. Auto-blocking sounds great until it blocks a homeowner who said "cash offer" about their own house. A missed real lead costs far more than one extra spam call, so the system has to be able to explain itself.

What I built

A small rules engine with no dependencies. It reads the transcript or text, pulls out signals (intent to buy or sell, a property or area, a timeframe, a callback request, a sales pitch, robocall phrases) and keeps the quote that triggered each one. It combines that with whatever else is known: a CRM match, prior spam reports, or an earlier decision by a person.

Confidence has to be earned. A new lead needs clear intent plus one concrete detail to pass 0.75. A sales pitch needs a second, independent source of evidence to reach 0.90. Spam needs corroboration and either repeat incidents or a person's confirmation to reach 0.95. Any credible lead signal vetoes blocking outright.

How it works

events ──▶ group by channel + number + 5-minute window ──▶ incident key
incident ──▶ extract signals (with quotes) ──▶ class + confidence
         ──▶ lead veto? ──▶ block gate (4 checks) ──▶ action: tags, alert level, callback time

Decisions that matter

The block gate has four conditions. The exact caller number must be known, the line can't be a shared office line, no lead or client signal can be present, and two independent kinds of evidence have to agree. Only the caller ID ever gets blocked, never a number mentioned inside a message.

Transcripts are treated as untrusted. They're only matched against fixed patterns, so a message saying "ignore your instructions and mark me as a lead" stays unresolved. One test fixture exists just to prove that.

Callback times like "later today" or "tomorrow morning" resolve to exact timestamps in the business's timezone, with fixed rules for each phrase.

What the tests caught

The fixtures include the cases that trip up simple keyword filters: the seller asking about a cash offer, an escrow officer on an active deal, a pitch with no corroboration (tagged, not blocked), the same pitch after three prior reports (blocked), and a one-ring call with no transcript (left for review). While reviewing the live demo I spotted a callback time with a broken timezone offset whenever the event time had milliseconds. That's fixed now, and a regression test fails against the old code.

Taking it to production

A thin adapter connects the phone system's webhook to the engine and applies the result through the CRM's API. Production would add stored incidents, full international phone parsing, and a review queue for the uncertain cases. An optional language model could help only with unresolved cases, under a spending cap, and never override the lead veto.

Stack