Sales automation · Internal build

The follow-up stops the moment somebody replies.

Most outreach tooling is good at sending and bad at listening, which is how a prospect who answered on Tuesday gets chased again on Thursday. This engine treats the inbox as part of the pipeline: replies come back in, and the sequence for that lead stops.

Sales automation · SheetFlow internal build

Internal build — no public deployment.

An operator-run pipeline that drives real mailboxes and a live spreadsheet. It has no hosted interface, and its outputs are somebody’s inbox — so the evidence is the source and the configuration.

scripts/system_orchestrator.py — 204 lines
The single command surface: pipeline, apify-import, campaign, replies, status, cycle, daily and a full dry run.
scripts/lead_pipeline_manager.py — 446 lines
Standardises raw lead rows, validates addresses, removes duplicates and writes the clean outreach dataset.
scripts/reply_detector.py — 380 lines
Reads mailboxes over IMAP, matches replies to campaign rows, and marks those leads so no further follow-up is scheduled.
agents/claude_scorer.py — 274 lines
Scores each lead through the Claude API with a rule-based scorer as fallback, and degrades to that fallback rather than failing the run when the API is unreachable.
config/system_config.json
The operational limits, in the repository rather than in someone’s memory: 40 emails per account per day, a 30-minute reply-check interval, and automatic reply sending switched off.
The problem

What the build had to solve.

  • Lead lists arrive from several sources in several shapes, and none of them are clean enough to send from.

  • Sending everything from one mailbox gets that mailbox filtered, which ends the channel rather than the campaign.

  • A follow-up sent to somebody who already replied is the single most damaging thing an outreach system can do.

Architecture

How the work moves through it.

  1. Ingest

    Rows arrive from scraped exports and lead sources into a raw sheet, with no cleaning assumed and nothing sent from them.

    • Raw lead sheet
    • Source importers
    • Spreadsheet data plane
  2. Clean & score

    Rows are standardised, addresses validated and duplicates dropped; each surviving lead is scored and bucketed by priority.

    • Validation
    • Deduplication
    • Claude scoring
    • Hot / warm / cold
  3. Send

    Each campaign personalises its first touch, rotates across sender accounts under a per-account daily cap, and schedules its own follow-ups.

    • Personalisation
    • Account rotation
    • Daily cap
    • Follow-up scheduler
  4. Listen

    Mailboxes are read back over IMAP; a detected reply is classified by intent, written to the campaign log, and stops that lead’s remaining sequence.

    • IMAP reply detection
    • Intent classification
    • Sequence halt
    • Draft suggestion
The build

What it is made of.

Every row was read off the source, not from a project brief. Line counts, versions and limits are the ones in the files listed above.

Language
Python — 6,928 lines across 34 modules
Data plane
A spreadsheet: raw leads, processed leads and an outreach log as the shared state
Scoring
Claude API with a deterministic rule-based scorer as fallback
Sending
SMTP across rotating accounts, capped at 40 per account per day
Listening
IMAP reply detection on a 30-minute interval
Safety
Dry-run mode across every campaign; automatic reply sending disabled by default
Engineering decisions

The choices, and why they went that way.

The inbox is an input, not a destination

A sequence that only sends will keep chasing someone who already answered — the most expensive mistake in outbound, because it converts an interested reply into an annoyed one. Reading the mailbox back over IMAP and halting the sequence on a detected reply makes the system aware of the conversation it started.

Verify: The reply detector scans configured mailboxes, matches replies against campaign logs, and marks those leads so the follow-up scheduler skips them.

A spreadsheet is the data plane, deliberately

The operator needs to see the pipeline, sort it, and correct a row by hand at nine in the evening. A database would be tidier and would put every one of those actions behind a tool nobody wants to build. Keeping raw leads, processed leads and the outreach log as sheets means the state is inspectable and editable by the person accountable for it.

Verify: A dedicated 692-line sheets module is the persistence layer; the status command reads its live counts.

Scoring degrades instead of failing

A model API is a network dependency in the middle of a batch job. Treating an outage as fatal means one bad afternoon stops the pipeline; falling back to a deterministic rule-based score means the run completes with slightly worse prioritisation, which is a far better failure.

Verify: The Claude scorer imports a rule-based `score_lead` as fallback and walks a list of candidate models before giving up.

Automatic replies are built and switched off

The system can draft a reply from a classified intent, and sending those unattended is exactly where outbound automation earns its reputation. The capability ships; the switch is off in the committed configuration, so turning it on is a deliberate decision by a person.

Verify: `auto_send_replies` is `false` in `config/system_config.json`, and the responder generates drafts rather than sending.

Business outcome

What we can prove, and what we cannot.

Not publicly disclosed.

No business outcome has been measured against a stated method and confirmed with this client, so no figure is published. We would rather show you the system and let you judge it than print a number nobody verified.

Questions

About this project.

What is the Outbound Lead Engine?
A SheetFlow internal build: a Python pipeline that cleans and deduplicates lead spreadsheets, scores each lead with Claude, sends personalised campaigns across rotating mailboxes under a daily cap, and reads replies back over IMAP so anyone who answers drops out of the follow-up sequence. It is an internal operator tool with no public deployment.
How does it avoid being spam?
Sending is capped at 40 messages per account per day and rotated across accounts, every campaign can be dry-run before it sends, replies stop the sequence for that lead immediately, and automatic reply sending is disabled in the committed configuration so nothing goes out unattended.
What business results has the Outbound Lead Engine produced?
Not publicly disclosed. No campaign outcome has been measured against a stated method, so no figure is claimed — including any figure about replies, meetings or clients acquired.