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…
- 235
- passing pytest tests, mypy clean
- 117 builders / 339 aliases
- in the builder registry
- 74 communities
- in the community registry, harvested from stored mail
- 7:00 AM Eastern
- daily brief send time to three recipients
A headless service. It watches an inbox and writes briefs; the only interface is the email that arrives, and those name real builders.
- 01Application
- FastAPI
- arq (Redis-backed task queue)
- SQLAlchemy Core (raw SQL, no ORM)
- Gmail REST API (readonly)
- 02Data
- asyncpg + Alembic (sync migratio…
- PostgreSQL with pg_trgm fuzzy ma…
- Redis
- 03Intelligence
- Claude CLI (keyless, subscriptio…
- 04Integrations
- Resend (transactional email)
- 05Operations
- pytest + mypy
What is shown instead is the verified stack by layer. Layers, not connections — which service calls which is not something a dependency list can prove. 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. 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 that 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 instead of 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 instead of 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. Growing the data source is a deliberate, auditable process, not 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 instead of 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. That avoids 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 instead of UTC. That 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 instead of "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. 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 instead of a 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 instead of 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. The reasoning: 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. That reframed the build from "scrape today's offers" into "never overwrite a record," so every extraction is versioned and immutable instead of following 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 instead of quietly dropped.
A third reframe governs the community registry specifically. A wrong builder-to-community mapping silently corrupts every future incentive attached to that community with no way to detect it downstream, so 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" — 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, not 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 instead of being left empty, and the incentive ledger holds real, versioned history (59 records as of 2026-09-23).
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 — not engineering gaps.
Since August the service has recovered from and hardened against two production failure modes. A Gmail sync outage lasting several hours traced to a deleted message pinning the ingestion cursor; such messages are now skipped instead of blocking the queue. The health check now returns 503 whenever Gmail sync, pending extraction or digest processing goes stale, rather than reporting healthy while quietly falling behind. The daily digest gained a review-queue triage list naming each open item's source email, loan-program eligibility labels (FHA-only, for example) on individual offers, and a section for communities an operator marks to watch. A one-time repair reconciled 40 legacy incentive records against the newer per-entry tracking scheme.
Related work
- Live2026Saturday Open HausA full-stack event platform for a recurring house-music brunch that doubles as an agent-attraction funnel — RSVP intake, Turnstile-gated admin approval, QR door check-in, a…
- Internal2026RepurposerA standalone content-repurposing microservice that turns one source transcript into voice-compliant, publish-ready drafts across eight channel formats, gated by a deterministic…
- Prototype2026Evolve AI StudioA campaign-production pipeline that turns one brief into a validated multi-shot creative spec and assembled multi-format exports, built end to end as a walking skeleton with…