Multi-agent outbound system: build write-up
Steve Light, SteveCV.com
Summary
I built a multi-agent outreach system for an arts venue launch. It found businesses, pulled their published contact details, cleaned them against suppression lists, and only let a human-approved email go out once every claim in it was checked. It crawled 3,710 businesses from five sources and ended with 622 clean contacts, each with the source URL it came from.
The main point of this write-up is how it handles data you can trust. One version of the pipeline produced made-up email addresses. A checker caught them before anything was sent, and I redesigned the system so that kind of error can't happen.
What the system does
- Discovery from five sources: OpenStreetMap, Companies House, sector-specific agents, privacy-policy pages and alternate hostnames.
- Contact extraction: keeps only email addresses a business actually publishes on its own pages.
- De-duplication and suppression: checks every contact against six suppression lists (opt-outs, bounces, past sends and others).
- Independent re-check: fetches every source page again to confirm the address is really there.
- Opt-out gate: an opt-out scan runs first and stops the whole build if any opted-out contact has not been suppressed.
- Human-approved sending: a person approves every send.
Funnel: where contacts dropped out
| Stage | Contacts | Dropped at this stage | Kept from previous stage |
|---|---|---|---|
| 1. Businesses crawled (5 sources) | 3,710 | — | — |
| 2. Published an email address | 1,602 | 2,108 (no published email) | 43% |
| 3. Clean contacts after suppression (6 lists) and page re-check, each with its source URL | 622 | 980 (suppressed, duplicate or not confirmed on re-check) | 39% |
Overall, 17% of crawled businesses became a clean, sendable contact.
3,710 businesses crawled | - 2,108 published no email address 1,602 businesses with a published email | - 980 removed: duplicates, 6 suppression lists, failed page re-check 622 clean contacts, each with its source URL
The biggest drop is at stage 2: more than half of the businesses don't publish an email address at all. In a sales pipeline, that's the gap an enrichment tool would fill (see below).
What went wrong, and how I fixed it
The problem: one agent's page-summarising step invented plausible-looking info@domain addresses. They looked right, but they weren't on the page.
How it was caught: a checker (verify-addresses.py) re-fetched every source page and looked for the exact address, including emails hidden by Cloudflare's email protection. 71 of 939 addresses (about 8%) failed. They were held back and nothing was sent to them.
The redesign: the agents now only find businesses and their websites. A plain crawler (crawl.py) records only text it actually fetched and puts the source URL on every row. Because no language model writes the addresses any more, invented addresses can't get in at all. Every contact can be traced back to a page.
Safeguards before anything is sent
- Opt-out scan first: the build stops if anything that should be suppressed hasn't been.
- attribution-check.py: blocks any outgoing email that makes a claim that hasn't been verified.
- Duplicate check at send time: stops anyone being emailed twice.
- Human approval: a person approves every send.
Stack
- Python (crawler, checkers, pipeline scripts)
- LLM agents (Claude) for discovery
- Self-hosted n8n in Docker for orchestration
- Postgres for contacts, suppression lists and send history
- A Linux VPS
A note on results
This was a launch campaign for an event, not a sales pipeline, so there's no closed-revenue ROI to report. What I can show is the funnel above: how many contacts entered, where they dropped out at each stage, and why every remaining contact can be trusted.
How I'd apply this to your pipeline
- Clay / Apollo enrichment: use enrichment to fill the biggest gap in the funnel (businesses with no published email), while keeping the same rule: every field carries its source, and anything that can't be verified is held back, not guessed.
- GoHighLevel stages: map the funnel onto pipeline stages (Sourced, Enriched, Verified, Approved, Sent, Replied) so you can see where leads drop out at each step.
- Make webhook reply agent with human review: replies come in through a Make webhook, an agent sorts them and drafts a response, and a person approves it before it goes out. Opt-outs go straight onto the suppression lists.
- The same guardrails: opt-out gate before every build, a duplicate check at send time, and a claim check on every outgoing message.