Skip to content

/work · In daily use

Outreach Engine

An outreach engine on its own hardware: over 20,000 records from an open data feed, a two-stage ranking with an embedding rerank, personalized delivery, replies auto-filed.

TypeScript · Node.js 26 · SQLite/FTS5 · SMTP/IMAP · Embeddings

Problem

Outreach at any serious scale usually means renting: a SaaS tool priced per contact, a scoring nobody can inspect, and prospect data sitting on someone else's infrastructure. Anyone working through hundreds of organizations systematically rents an opinion about whom to approach next.

The second trap is operations. A five-stage pipeline rarely breaks loudly: a feed shifts, a contact bounces, a reply drowns in the inbox. Delivery is the easy part; the question is whether you still know where you stand three weeks later.

Architecture

The core is a pipeline of five stages: ingestion, matching, enrichment, delivery, triage. Every stage reads from SQLite and writes back to SQLite, in WAL mode, with an FTS5 full-text index on trigram tokenization. No message broker sits between the stages, and no cloud service either.

The ranking starts with a gate: a taxonomic filter over the dataset's categories lets only fitting records through. What survives goes through the rerank: a blended score of BM25 and cosine similarity over embeddings reorders the corpus. 37,059 scored pairs sit in the database, groupable by organization, each pair explainable on request.

Delivery works from 55 message variants; the pick falls by hash over the record ID (FNV-1a), deterministic and reproducible, with no model call. Sends go out through a hand-written SMTP client with jitter between messages; every message is archived as an .eml file and placed in the Sent folder via IMAP APPEND.

The whole thing is a Node CLI in TypeScript with zero runtime dependencies: SMTP, IMAP, ZIP, DOCX and XLSX are hand-rolled, and the tests run offline. Install, configure, run; no container, no dashboard, no subscription.

Data & numbers (measured)

As of 6 October 2026: 20,180 records on file, plus 5,153 organizations and 48,967 geocoded addresses across 2,335 cities. The database weighs in at 628 MB, holding two weeks of ingestion across 222 runs.

The operation has run: 100 deliveries in the first 48 hours, 173 enriched contacts, 30 replies filed by the IMAP reader into four intents (acknowledgment, invitation, decline, pool). None of these numbers is an estimate; they all live in the same SQLite file the pipeline itself fills.

Data sovereignty & auditability

The dataset stays on its own machine. What leaves is the text snippet for the embeddings, and only while an API key is set; without a key, the ranking keeps running on the full-text index alone. No second service ever sees a single record.

Every outbound message is reconstructible: an archive copy on disk, an APPEND in the Sent folder, a ledger entry with validated status transitions. The IMAP reader files incoming replies into folders and writes every classification down. The yearly Excel export needs no library, because the XLSX writer is hand-built.

An operation you cannot reconstruct never happened. That is why auditability is not a feature here; it is the precondition.

What companies can take from it

The five stages are the classic SDR funnel: define a target profile, rank candidates, enrich contacts, deliver a personalized first touch, triage the replies. That is exactly the funnel companies rent today, usually per contact per month.

This build is not bound to one source type: any structured source runs on the same architecture, from a proprietary dataset to an open data feed. It has proven itself at personal scale. The architecture is the part that transfers.