Case study · Automation & Sales

The lead you never called

I built an automated qualification system to find out whether there was a business in it. Then I found a flaw in my own scoring logic that would have quietly buried the best leads — the exact ones I was building it to catch.

Role
Design, build, testing
Built with
Make, Google Sheets, webhooks
Timeline
A few days
Status
Working prototype

Why I built it

This started as market research, not as a technical exercise. I was looking for automation problems small businesses would actually pay to solve, and lead qualification kept surfacing — founders collecting form submissions they had no time to sort through.

So rather than write another proposal about it, I built the thing. Two reasons: to find out whether the problem was as real as it looked, and to have something concrete to show instead of a promise.

The problem

An inbound form produces a flat list. In an inbox, a founder with a €5,000 budget and an urgent deadline looks exactly like someone who clicked out of curiosity. Same subject line, same weight.

That leaves a business owner two bad options: call everyone, which does not scale, or call nobody, which loses deals. The real cost isn't the sorting time — it's the serious buyer sitting at position 40 who goes cold while you work through the list in the order it arrived.

The system had to answer one question, automatically, the moment a form was submitted: is this person worth calling today?

Constraints

  • A non-technical owner had to be able to maintain it after handover
  • No infrastructure budget, and nothing to host or monitor
  • It had to plug into tools a small business already uses
  • Build time measured in days, not weeks — this was validation, not a product launch

Approach

Why no-code, deliberately

I built it in Make rather than writing it from scratch, and that was a decision rather than a limitation. Given the constraints, code would have been the worse answer: something to host, something to monitor, and a client who has to call me every time a threshold needs adjusting. In Make, the owner opens a visual scenario and changes a number.

Speed mattered too. The point was to test whether the problem was worth solving. Spending two weeks on infrastructure before knowing that would have been the wrong order of operations.

Three signals

A form submission arrives via webhook. Three things get evaluated:

SignalWeightWhat it actually measures
Budget above threshold50Ability to pay
Keyword match in message35Stated intent
Professional email domain15Weak proxy for seriousness

Budget carries the most weight because it is the hardest constraint to change. Enthusiasm can be created in a sales call; a budget that doesn't exist cannot. Intent comes second — someone who describes the problem in their own words has already done part of the qualifying.

The email domain is intentionally the weakest of the three, and getting that weighting wrong is what caused the flaw described below.

Where the output goes

Straight into a Google Sheet, not a CRM. A small business owner already has the Sheet open; a CRM is one more login and one more subscription for a problem that doesn't need one yet.

Two columns get written: score and tier. The tier tells you what to do, the score tells you why. Without the raw number you cannot debug a classification you disagree with — and you will disagree with some of them.

The flaw

The first version used weights of 40, 30 and 20, with anything at or above 80 marked hot. That looked reasonable. It wasn't.

With three independent signals there are only eight possible outcomes, so I enumerated every score the model could actually produce:

0 · 20 · 30 · 40 · 50 · 60 · 70 · 90

There is nothing between 71 and 89. So a threshold of "80 or above" did not mean what it appeared to mean — it meant exactly 90, which required all three signals at once.

The consequence

The weakest signal had silently become mandatory. A founder with the budget and a clearly stated need scored 70 and was filed as warm — for no reason other than writing from a Gmail address.

That is not a rounding error, it is an inverted priority. The criterion I had weighted lowest ended up with more blocking power than budget itself.

And it targets precisely the wrong people. Independent founders, freelancers and small teams overwhelmingly use Gmail as their working address — in Morocco and West Africa especially, where I work. The model was systematically demoting my actual target market for not owning a domain name.

The fix

Rebalanced onto a clean 100-point scale, with the email signal cut to a genuine bonus:

budget ≥ threshold     50
keyword match          35
professional domain    15
                      ───
                      100

hot ≥ 80 · warm 40–79 · low < 40

Budget plus intent now reaches 85 on its own. The email domain can lift a lead to 100, but it can no longer keep one out.

BudgetIntentPro emailScoreTier
yesyesyes100hot
yesyesno85hot
yesnoyes65warm
noyesyes50warm
yesnono50warm
noyesno35low
nonoyes15low
nonono0low

The highlighted row is the one that was broken.

Rows four and five both land on 50, which is a deliberate collision rather than an oversight. "Money but no stated need" and "clear need but no money" are different profiles that warrant the same action: worth a call, not worth calling first. Separating them would require a fourth signal, and three signals were doing enough work.

Testing

Three binary signals produce eight states, which is few enough to test exhaustively rather than sample. I wrote ten payloads: eight covering every reachable state, plus two for the input formats that break this kind of formula in practice — a budget landing exactly on the threshold, and a budget arriving as the string "3 000" instead of a number.

Then I wrote a small script that recalculates all ten expected scores independently of the scenario.

Worth admitting

That verification script immediately caught an error — in my own expected values, not in the system. I had written 75 for a case that should have scored 85, having forgotten the email bonus while typing the file by hand. The same class of mistake as the original flaw: reasoning about a model in my head instead of enumerating it.

Result

A working system that scores and files an inbound lead in seconds, with all eight classification states verified against expected output.

To be precise about what this does and does not prove: it is a validated prototype, not a deployed product. It has not yet run on live traffic, so there is no "40% more conversions" to report here, and I am not going to invent one. What it demonstrates is the reasoning — how the signals were chosen and weighted, how the failure was found, and how the correction was verified.

For a client, that is the transferable part. The scoring model of any business will differ. The method for building and stress-testing one will not.

What I'd do differently

  1. Enumerate the outputs before setting the thresholds. The flaw existed from the first version and was invisible until I listed all eight results. On any scoring model, that enumeration now comes first.
  2. Widen the keyword list. It ran on a single term during testing. Someone writing "I keep losing hours on repetitive tasks" is describing the service exactly and would have scored zero on intent. A signal worth 35 points cannot hinge on one word.
  3. Add an urgency signal. A "when do you need this?" field predicts closing far better than an email domain does. I left it out of this version to keep the model to three signals, but it is the first thing I would add.
  4. Version the scoring. Rows scored under the old weights are not comparable with new ones. A scoring_version column costs nothing and prevents a silent analysis error months later.

When Make stops being the right answer

No-code was correct for this build. It will not stay correct forever, and knowing where the line sits matters more than preferring one side of it.

It breaks down on per-operation cost at volume, on branching logic that becomes unreadable on a canvas, on the absence of version control and automated tests, and on data passing through a third party. Any one of those turning up in a client's requirements is the signal to port it — a webhook endpoint, an isolated scoring module, a Sheets write. That version is the next step for this project, and the tests written here transfer to it directly.