Part 5 of 7 · Lead enricher series ~7 min read

How junk gets filtered out

Not every lead is a lead. Some are bots filling in the form with gibberish. Some are disposable email addresses that exist for ten minutes. Some are sales pitches dressed up as enquiries, and some are just test submissions someone left behind. If those reach the CRM they cost you enrichment lookups, clutter the pipeline, and waste a rep’s morning. This post is about the junk filter: how the enricher decides, cheaply and defensibly, that a lead isn’t worth writing — and how it drops it without ever silently losing something real.

Key takeaways

  • The junk filter drops obvious spam and bot submissions before anything is written to the CRM — the last gate before push.
  • It runs in layers: cheap deterministic checks first (disposable domains, gibberish, role addresses, honeypots), then a bounded model judgement only for the borderline.
  • A cheap spam pre-screen also runs at intake, so the most obvious junk never even reaches the paid enrichment lookup.
  • Every dropped lead is written to a junk log with the reason — dropped, never silently lost, and always reversible.
  • The bar is deliberately conservative: when a lead is genuinely borderline, it’s kept and flagged, not dropped. A missed real lead costs more than a kept junk one.

Not every lead is a lead

An open web form is an open invitation, and not just to customers. Bots fill it with gibberish to test it. Spammers use it to deliver sales pitches dressed up as enquiries. People submit disposable email addresses that exist for ten minutes. Someone on your own team leaves a test submission behind. If any of those reach the CRM, they cost you an enrichment lookup, clutter the pipeline, and waste a rep’s time chasing a ghost. The junk filter’s job is to decide, cheaply and defensibly, that a lead isn’t worth writing — and to do it without ever quietly losing something real.

The filter is the last gate before the push stage. By the time a lead reaches it, the lead has been normalised, enriched, and deduped, so the filter has full context: a parsed email and domain, a company profile (or a note that there isn’t one), and the message text. It uses all of that, in layers, cheapest and most certain first.

The cheap, deterministic layer

Most junk is obvious, and obvious junk should be caught by a rule, not a model. The deterministic layer runs a set of plain checks:

  • Disposable and throwaway domains. The email domain is checked against a maintained list of disposable-mail providers. A ten-minute mailbox is almost never a real enquiry.
  • Gibberish. A name like “asdfgh qwerty” or a message that’s a wall of random characters fails simple plausibility checks — vowel ratios, repeated characters, dictionary hits.
  • Honeypot and timing signals. If the form carried a hidden honeypot field that a human never sees but a bot fills in, or the submission arrived implausibly fast, that’s a strong bot tell, passed through from intake.
  • Role and abuse addresses. Submissions from postmaster@, abuse@, or obvious test addresses are flagged — not always junk, but rarely a buying customer.
  • Known spam patterns. Messages matching well-worn spam templates (link-stuffed SEO pitches, cryptocurrency come-ons) are caught by pattern.

Each check produces a signal, not an instant verdict. A single weak signal doesn’t drop a lead; the signals are weighed together. A lead that trips several — disposable domain and gibberish name and a honeypot hit — is dropped with high confidence and no model call. A lead that trips none sails through. Only the leads in between go to the next layer.

The junk filter: deterministic checks first, a bounded model judgement only for the borderline A normalised, enriched, deduped lead enters a dotted AWS account container holding the ler-filter Lambda. First, a Deterministic checks box runs disposable-domain, gibberish, honeypot, role-address, and spam-pattern checks, each contributing a signal. A decision diamond asks: clearly junk or clearly fine? Two confident branches leave it: clearly fine goes to an outcome box, Push to CRM; clearly junk goes to a Junk log box (S3 with the reason). A third branch, borderline, goes to a Bedrock Haiku 4.5 box that makes a bounded keep-or-drop judgement on the borderline lead; its answer routes to either Push to CRM or the Junk log. Off to the left, a smaller box labelled Intake pre-screen shows that the cheapest spam checks also run at intake, before the paid enrichment lookup, so the most obvious junk is dropped early. A note reads: the bar is conservative — a genuinely borderline lead is kept and flagged, not dropped, and every drop is logged and reversible. Intake pre-screen cheapest checks, early drops before enrichment cost Lead clean & enriched ler-filter — AWS Deterministic checks domain, gibberish, honeypot, patterns clear verdict? fine Push to CRM junk Junk log S3 · with the reason borderline Bedrock Haiku 4.5 bounded keep-or-drop call keep drop Conservative bar: a borderline lead is kept and flagged, not dropped. Every drop is logged and reversible.
Fig 5. The junk filter weighs cheap deterministic signals first and sends only borderline leads to a bounded model judgement. A cheap pre-screen at intake drops the worst junk before it costs an enrichment lookup.

The bounded model layer

Some junk is subtle. A sales pitch can be written in fluent, plausible English with a real company domain — it passes every deterministic check and is still not a customer. A real enquiry can look thin and odd — a one-word message from a personal email — and still be genuine. These borderline cases are where a model adds real value, so the borderline slice goes to one Bedrock Haiku 4.5 call.

The call is given the message text and the surrounding context — the enriched company, the domain, the deterministic signals — and asked a single bounded question: is this a genuine business enquiry, or unsolicited / spam / test? It returns a verdict and a short reason. The verdict is advisory, not absolute: it routes the lead to push or to the junk log, but it can’t do anything else, and its reason is logged alongside the lead so a person can review the call later. The model judges; it never reaches past this one decision.

Dropped, never lost — and a conservative bar

Nothing is ever silently discarded. Every dropped lead is written to a junk log in S3 with the full record and the exact reason it was dropped — “disposable domain + honeypot hit,” or “model: unsolicited SEO pitch.” That log is the safety net: if the filter ever drops something it shouldn’t, you can find it, see why, and replay it through the push stage. A daily digest email (via SES) summarises what was dropped and why, so a person actually looks at the boundary occasionally instead of trusting it blindly.

And the bar is deliberately tilted. The cost of dropping a real lead — a customer you never call back — is far higher than the cost of letting a junk one through, which just gets cleaned up later. So when a lead is genuinely on the line, the filter keeps it and flags it for a human glance rather than dropping it. The filter is aggressive about obvious junk and cautious about everything else. That asymmetry is the whole design: catch the clear waste, never gamble with a real enquiry.

Why this shape

  • Layers, cheapest first. Plain rules catch obvious junk; the model only ever sees the genuinely borderline slice.
  • Pre-screen at intake. The cheapest spam checks run before enrichment, so the worst junk never costs a paid lookup.
  • The model is fenced to one question. It returns keep-or-drop with a reason and can do nothing else; the reason is always logged.
  • Dropped, never lost. Every drop goes to a junk log with its reason, and a daily digest keeps a human watching the boundary.
  • A conservative bar. Borderline leads are kept and flagged, not dropped — a missed real lead costs far more than a kept junk one.
All posts