A consumer brand acquisition firm was finding partner brands by hand — one search at a time, scored on instinct, tracked in spreadsheets. Evan's List replaced that with an automated engine that discovers, ranks, and reaches out to thousands of e-commerce brands every month — with a human approving every send.
How Evan's List built a serverless brand discovery, scoring, and outreach pipeline for a consumer brand acquisition firm — marketplace-data ingestion, config-driven segmentation, a contact-enrichment waterfall, and gated cold-email sequencing, orchestrated on trigger.dev and deployed at the edge on Cloudflare.
You're reading the plain-English edition. Engineers: switch to the Technical view →
You're reading the Technical edition. ← Switch back to plain English
Finding acquisition candidates was manual, inconsistent, and impossible to scale.
From market data all the way to a warm conversation in the CRM — with exactly one human checkpoint, right where judgment matters.
Seven serverless stages from marketplace ingestion to CRM sync — every stage live, every stage idempotent and re-runnable.
The whole flow runs itself on a monthly schedule — retries, error handling, and alerts included. No servers to maintain, no engineer on call. Orchestrated by trigger.dev — durable background tasks handle fan-out, retries with exponential backoff, and the monthly cron. State lives in Cloudflare D1; the client-facing tools and API run on Cloudflare Pages + Functions behind Zero Trust.
The challenges that required real architectural thinking — not just plugging in tools.
The client's long-term plan was an enterprise data warehouse — but provisioning it would take months. Instead of waiting, we stood up a lightweight serverless database as a stand-in, designed so the eventual warehouse can slot in later without rebuilding anything.
The target warehouse was Snowflake — but access wasn't provisioned and wouldn't be for months. Blocking the build on vendor onboarding wasn't acceptable, so we made Cloudflare D1 the interim state store, written against a swappable interface shaped like the eventual warehouse client.
In most systems, the rules for what makes a "good lead" live in code only an engineer can change. We built a live web tool where the commercial team defines those rules themselves — which criteria are must-haves, which are dealbreakers, which are nice-to-haves — and instantly sees how real brands re-rank as they adjust.
The scoring model is fully config-driven: each of the 6 segments is defined by must-have gates, dealbreakers, and nice-to-have criteria, edited in an authenticated web configurator with a live preview of real brands re-scoring as you type. Saves are versioned rows in the database — the pipeline picks up the active config on its next run. No deploy required.
config_version set of score rows, so a tweaked config can be compared against the same
cohort without re-ingesting — the "Why suppressed?" tooltip traces every filter decision.
Scoring and enriching tens of thousands of brands can't happen in one click. Normally that means servers, job queues, and someone on call. Instead, the pipeline runs as managed background jobs that retry themselves when a vendor hiccups and fire automatically every month.
Enriching hundreds of brands against rate-limited vendor APIs can't happen synchronously.
trigger.dev provides durable task execution: the monthly cron fires the first weekend of each
month, fans out ingestion → scoring → enrichment tasks with maxAttempts: 3 and
exponential backoff.
A system that can email thousands of prospects or rack up vendor charges needs hard brakes. We built two independent safety switches — one for spending on contact data, one for sending email — and both default to "off." Going live was a deliberate human decision, twice.
Two independent environment gates protect the blast radius: ENRICH_MODE gates paid
enrichment API spend, PIPELINE_MODE gates live sends. Unless a human explicitly sets
production in the deployment config, tasks return early with a logged
{skipped: true} — dry-run is the default everywhere.
Brands rename themselves, storefronts get rebranded, and data refreshes monthly. The system recognizes the same brand across all of it — so a prospect who already replied, or asked to be left alone, can never accidentally be emailed again. That protection is built into the database itself, not a checklist.
Brand identity is a content hash of an immutable natural key —
sha256(storefront_url)[:12] — so records survive display-name changes across monthly
runs. Outreach state is a strict truth table: replied and unsubscribed brands are permanently
suppressed, enforced by unique indexes rather than application logic.
The same scoring rules run in two places: the monthly pipeline, and the live preview the business team sees in their browser. If those ever drifted apart, the team would be approving one thing and sending another. Automated checks compare both against the same examples on every change — they cannot silently disagree.
The scoring engine exists in Python (pipeline) and JavaScript (live preview + API). Dual implementations of one business rule is a drift bug waiting to happen — so two parity test suites run both engines against shared fixtures and fail the build on any divergence, down to segment assignment and suppression reasons.
Every component is deployed, documented, and running in production — in daily use by the client's team.
Automatically pulls the latest marketplace data every month, filters out brands below the revenue floor, removes anyone already known or previously contacted, and files the rest for scoring.
Paginated SmartScout ingestion with a $1M+ revenue floor, SHA-256 content-hash deduplication, and suppression-list cross-reference on every run. Fires on a monthly cron; long pulls run as resumable, checkpointed jobs.
A live web tool where the commercial team defines what a great prospect looks like — for each of 6 target segments — and watches real brands re-rank instantly. No engineer required to change strategy.
Authenticated configurator for the criteria-based scoring model: must-have gates, dealbreakers, and nice-to-have bands per segment, with a live preview scoring real brands client-side and versioned config saves the pipeline reads at runtime.
One source of truth for everything — every brand, every score, every email sent, every reply — served globally with effectively zero infrastructure cost.
Cloudflare D1 as the pipeline's source of truth: 13 tables covering brands, score versions, runs, outreach history, review queue, contacts, and config — evolved through 9 schema migrations, fronted by ~30 serverless API endpoints on Pages Functions.
For every promising brand, the system finds the actual decision-maker — name, title, and verified email — trying a second data provider automatically when the first comes up empty.
Apollo.io decision-maker discovery with a BetterContact email-reveal fallback, persona-mapped titles, full audit logging per enrichment attempt, and multi-contact support per brand. Spend-gated behind its own environment switch.
Humans stay in charge: a review dashboard where the team approves or rejects each prospect before any email goes out, plus an editor for writing the outreach copy for each segment — all in the browser.
Human-in-the-loop review UI with per-brand notes, segment overrides, and approval state feeding the send queue — plus a segment-tabbed sequence editor whose copy is versioned in the database and wired into the sending platform on approval.
Approved prospects get personalized email from warmed-up sending addresses. Replies flow straight into the client's CRM, and when a prospect becomes a partner, the system spots it and marks the win automatically — so results are measured, not guessed.
Six Smartlead campaigns (one per segment) across 6 mailboxes / 3 domains; reply, bounce, and unsubscribe webhooks write back to the state store; warm replies sync to HubSpot; automatic win detection flags converted brands. Live dashboard reports funnel stats end-to-end.
Real figures from the live system at handoff.
A phase-by-phase breakdown of the engagement. Click any phase for details.
Audited the client's existing assets — brand lists, a partially populated CRM, and their market-data subscription. Defined the revenue floor, the brand identity scheme, and the full database design before any pipeline code existed.
sha256(storefront_url)[:12] — stable across name changes, portable across future data storesBuilt the complete ingestion layer: paginated market-data pulls with revenue filtering, deduplication, and full run logging — deployed as scheduled background jobs with the database live at the edge.
discover (audit-only) and ingest (upsert) modes — safe to inspect before writingstart_run()/finish_run()Built the browser tools that put strategy in the business team's hands: the live scoring configurator and the email sequence editor — both deployed behind enterprise access control, both in daily use since.
sendBeacon fallback saves on page closeThe first scoring model used abstract weights — mathematically sound, but not how the client reasoned about prospects. We rebuilt it around their real language: must-haves, dealbreakers, and nice-to-haves per segment — then calibrated it with leadership through 43 saved iterations to sign-off.
50 + 50 × (nice-to-haves hit ÷ known); brands match to their best-fit segment A–FWired up the two-provider contact enrichment waterfall and the human review queue — 620+ decision-maker contacts resolved across the hot tier, every one routed through the review dashboard before becoming eligible for outreach.
ENRICH_MODE) kept enrichment in dry-run until unit economics were confirmedFlipped the switch — deliberately. Six segment-specific campaigns launched across six warmed mailboxes on three dedicated domains, with reply, bounce, and unsubscribe events flowing back into the system in real time.
outreach_history; merge-field integrity covered by testsWarm replies sync into the client's CRM automatically, converted brands are detected and marked as wins, and a live dashboard reports the full funnel. Delivered with complete documentation, runbooks, and a walkthrough for the client's team.
Every tool chosen for a reason — reliability, fit, and zero unnecessary infrastructure.
After launch, we ran a formal review of our own architecture — what earned its keep, and what we'd sharpen. That honesty is the point: the client got a written evolution plan, not a black box.
Post-launch, we ran a formal architecture retrospective and designed the v2 path. The verdict: the data model was the right investment; the seams between runtimes are where the cost lived. Here's the evolution plan the client received.
The enterprise warehouse finally arrives — but as an analytics layer, not a replacement. The fast operational database keeps running the day-to-day pipeline; the warehouse receives a copy of everything for the client's analysts to slice. Each system does what it's best at.
Snowflake is a columnar analytics warehouse, not a transactional store — so the design splits responsibilities rather than migrating wholesale. The SQLite-class store keeps the hot path; Snowflake is batch-fed cohort history, score versions, campaign events, and conversions after each run. The pipeline never blocks on a warehouse query.
The system was built in three programming languages — each fine on its own, but every translation point between them added friction. Our retrospective's biggest lesson: next time, one language everywhere. Simpler to maintain, cheaper to extend, easier for any future engineer to pick up.
Python pipeline, TypeScript orchestration wrappers, JS API — every meaningful pain point traced to a boundary between languages, not to any one of them: results parsed off stdout via a sentinel line, and a pipeline that reached its own database through its own HTTP API because the runtimes couldn't share bindings. The v2 default: TypeScript end-to-end with native database bindings — or, if the data work demands Python, a Python-reachable store so the hop disappears.
Not everything changes. The foundation — how brands are identified, how every version of the scoring rules is preserved, how the system protects prospects from double-contact — proved itself and carries into v2 untouched. Getting the data foundation right early is what made everything else fixable.
The retrospective's other half: the data model earned its keep. Content-hash IDs from immutable natural keys, config-versioned score rows enabling cohort-level A/B of scoring changes, and idempotent runs with best-effort side-effects all carry forward as standing patterns for every future build.
The best automation systems aren't the ones where the engineer did everything — they're the ones where the business team can change what matters without calling an engineer. Scoring criteria, email copy, and campaign approval all live in tools the commercial team owns. Engineering owns the infrastructure. Strategy stays where it belongs.
— The Evan's List Approach
Scoped in phases with a fixed cap, billed against actuals. From first discovery call to production handoff — including a mid-project CRM switch absorbed without a change order.
Every script, task, function, and UI delivered to the client's own repository. No vendor lock-in on the intelligence layer — they own the logic.
Runbooks, troubleshooting guides, a changelog, and a full handoff document — maintained in the repository and kept current through the final week, not written after the fact.
A written post-launch retrospective and a costed evolution plan — what to build next, what to leave alone, and what we'd do differently. Clients deserve the real answer.
Evan's List pairs businesses with senior engineers who ship production systems — not slide decks, not prototypes.