EBI
Evolve Builder Intelligence turns Florida homebuilder marketing emails into an immutable, diffable incentive history and a daily agent-ready brief, running entirely on a Claude…
- 153/153
- passing pytest tests, mypy clean
- 117 builders / 339 aliases
- in the builder registry
- 57 communities / 67 aliases
- in the community registry, harvested from stored mail
- 7:00 AM Eastern
- daily brief send time to three recipients
- FastAPI
- arq (Redis-backed task queue)
- SQLAlchemy Core (raw SQL, no ORM)
- Gmail REST API (readonly)
- PostgreSQL with pg_trgm fuzzy ma…
- Redis
- Resend (transactional email)
- Claude CLI (keyless, subscriptio…
- pytest + mypy
- asyncpg + Alembic (sync migratio…
EBI holds live data, so this shows the verified technology stack by layer rather than a screenshot. Hosts, ports and topology are deliberately absent.
Problem
Florida homebuilders send incentive offers (rate buydowns, closing-cost credits, price drops) through scattered marketing emails and newsletters with no structured record. An agent trying to quote a current incentive has no reliable, sourced way to know what is still active, what changed since last week, or which builder/community an unstructured email actually refers to. Without a longitudinal record, every incentive claim is anecdotal and unverifiable, and a publicly-facing incentives page was considered and explicitly rejected because stale advertised incentives create fair-housing/FREC compliance exposure. Getting builders into the pipeline at all was its own problem: most homebuilders have no public API or feed, so coverage depends on manually subscribing to dozens of individual builder mailing lists, filling out captcha-gated signup forms one builder at a time, and in some cases directly emailing a sales rep — none of which existed as a tracked, systematic process before this project, so coverage of any given builder or region was essentially arbitrary.
What was built
EBI is a backend service that watches a dedicated inbox for builder marketing email, extracts incentive terms with AI, and stores every extraction as an immutable, versioned record rather than overwriting prior state. A resolver matches loosely-formatted builder and community names against a maintained registry, auto-accepting high-confidence matches and routing ambiguous ones to a human review queue rather than guessing. Each morning a digest email goes out to three people, built from the confirmed incentive ledger plus the open review queue, filtered down to offers that carry an actual quotable figure, with a verification stamp on each one. A small internal API exposes the ledger, its change history, and the builder/community registries for anything downstream that needs it. Coverage acquisition is tracked as its own workstream: a maintained registry of over a hundred builders with their subscription status, a coverage matrix scored against known market presence, and a ready-to-send batch of outreach emails for large national builders that have no public sign-up form at all, so growing the data source is a deliberate, auditable process rather than an afterthought.
Technical approach
The service is a deliberate sibling of the company's main CRM backend: raw SQL via SQLAlchemy Core with hand-written Alembic migrations, no ORM, single-tenant with no row-level security since there is only one tenant. The pipeline is a five-stage chain run through an arq task queue: a cron job syncs Gmail via a stored history cursor into an immutable raw-email table, which enqueues extraction, which calls the AI client, which feeds a resolver (normalize + pg_trgm fuzzy match, auto-accept at 0.85+ confidence, human review between 0.5 and 0.85), which finally applies the result to either the incentive tables or a review queue row. An unknown builder produces exactly one review row rather than failing silently, and date fields returned as free-form strings by the model are coerced against the DATE columns explicitly. The most consequential architecture decision is the AI backend: extraction runs through the local claude CLI with the API key stripped from the child process environment, so calls authenticate via the Claude Code subscription's OAuth session instead of the Anthropic API, avoiding per-call billing entirely (bounded by a small daily spend cap as a safety net, tracked via the CLI's own reported cost). This trades roughly 4x the latency per call (13-20s vs ~3s on the raw API) and drags along the CLI's own system-prompt overhead, but the tradeoff is irrelevant at this email volume and the dollar cost is zero. A second production correctness bug was traced to arq's cron scheduler evaluating trigger hours in the host's local timezone rather than UTC, which had been undocumented and caused the digest to fire two hours later than intended for weeks before being diagnosed and documented as a permanent constraint rather than "fixed" back to a UTC assumption. The community registry is intentionally never populated by a cron: a harvesting script supports two independently-verified admission sources (a newsletter parser with an AI-driven floor-plan filter, and a builder-email parser that needs no AI at all) and every run requires a human review of a dry-run before any write, because a wrong builder-to-community attribution is undetectable downstream and would silently mis-resolve every future incentive for that community.
Creative approach
Craft
The product's single external surface is a daily email, so most of the design effort went into that one artifact: it leads with a quotable figure rather than narrative summary, attaches the conditions and expiry to each figure, stamps each offer with when it was last verified, and was explicitly tuned to render correctly in Gmail's dark mode. An empty-state digest still sends on a day with nothing new, functioning as a heartbeat rather than going silent, which distinguishes "nothing changed" from "the pipeline is broken." The digest was rebuilt once already after its first version proved less useful in practice, moving from a general activity summary to a strict figure-first format assembled specifically from offers that carry a real number, on the reasoning that an agent needs something they can say out loud to a buyer, not a report to read.
Reframe
The stated thesis is that the moat is the longitudinal incentive history, not any single incentive in it — which reframed the build from "scrape today's offers" into "never overwrite a record," hence every extraction is versioned and immutable rather than the more obvious upsert-in-place design. The second reframe was refusing to build a public incentives page despite it being an obvious next feature: stale advertised incentives create real fair-housing/licensing liability, so the system stays internal-only and the public-facing idea was formally shelved with its own compliance notes rather than quietly dropped. A third reframe governs the community registry specifically: because a wrong builder-to-community mapping silently corrupts every future incentive attached to that community with no way to detect it downstream, that one part of the pipeline is deliberately kept out of automation entirely — a human reviews every batch before it writes, even though everything else in the pipeline runs unattended.
Process and what failed
The AI billing model was revisited mid-build: an initial design against the metered Anthropic API was replaced with the keyless subscription-CLI approach once the cost tradeoff became clear, keeping the API path only as a config-gated fallback. A four-day silent failure was traced to one recipient's email address being on the sending provider's suppression list from an old bounce — every send returned success (200) while that recipient's brief was silently dropped, which is why the operating rule became "a 200 status is not proof of delivery," and delivery has to be checked in the sending provider's own log. A related gotcha found during build: builder-name aliases in the registry have to be stored in the same normalized form the resolver produces at match time, or short/punctuated builder names fail to match even though the alias technically exists in the table. Both are documented as standing traps rather than one-off bugs because the failure mode is silent in each case.
Outcome
The system is described as live and operational with no blockers: the ingestion-to-digest pipeline runs on schedule, the daily brief has been rebuilt once already to lead with quotable figures instead of narrative summaries, and the AI cost model has been converted from metered API spend to the flat-rate subscription path. Registry coverage (builders and communities) is populated from real harvested mail rather than left empty, and the incentive ledger holds real (if still small, 14-incentive) history. Remaining work is explicitly framed as optional acquisition-lever tasks for the business owner (confirming a key vendor's email toggle, sending a batch of manual builder rep outreach emails) rather than engineering gaps.