Case study 02

A pipeline that builds, reviews and sends demo websites for local businesses.

Role
Solo engineer
Year
2026, live
Stack
FastAPI, Next 16, n8n, PostgreSQL, Jinja2, Cloudflare Pages
Links
535commits on main1
18locale-driven site looks2
10nodes in the daily n8n pipeline3
6architecture decision records4
  1. git rev-list --count origin/main in the quicksite repo
  2. directories under templates/looks, excluding the two shared ones (_photos, _shared)
  3. n8n_workflows/daily_pipeline.json
  4. docs/adr in the quicksite repo

The problem

A small local business with a weak or missing website is a real lead, but only if someone shows them what a better site would look like. Doing that by hand, one business at a time, does not scale past a few a day.

QuickSite automates the whole path: find the business, check it is worth contacting, build a demo site from its real details, review that site, deploy it, and reach out.

What I built

A daily pipeline plus a dashboard to supervise it.

  • Find and qualify: scrape Google Maps, enrich each result from Google Places, then score and filter the leads.
  • Generate: Claude writes the site copy into a strict schema, which a Jinja2 look turns into a static site. There are 18 looks, driven by locale rather than hard-coded to one country.
  • Review: rule-based checks cover content, data and pipeline state. Only when they flag something does an AI reviewer run.
  • Deploy and reach out: the demo goes to Cloudflare Pages, and an SMS goes out with a suppression list so nobody is texted twice or after opting out.

Architecture

n8n daily run10 nodesFastAPIrouter, service, repoLead sourcesMaps, PlacesClaudecopy, reviewSemaphoreSMS outreachCloudflarePagesPostgreSQLDashboardNext 16

AI design

  • Strict schema out: Claude returns site content as JSON validated against a Pydantic schema. A bad shape is retried, and a shared call budget caps the worst case at 7 model calls per site.
  • Fallback: Claude is primary and Gemini is the fallback, behind one small protocol.
  • Cheap reviewer, only when needed: the review model is the smallest Claude, and it runs only when the rule-based checks flag something.
  • Honesty checks: fields that would invent facts are stripped, and long verbatim copying from the source listing is detected.
  • Untrusted input: scraped listings and reviews are treated as untrusted personal data. They never go into the system prompt and are never logged.
  • Spend cap: a monthly AI cap is checked before each call, and a skipped review is logged with which review it was.

How it was built

Specs first, then plans, then parallel implementers per task with a reviewer. A hook blocks destructive database and git operations, so those stay human-only. Technology and model choices are recorded as ADRs.

Tradeoffs

  • No queue worker. Background work runs through FastAPI BackgroundTasks. It is simple and enough at this volume, and it means a crash mid-run is not resumed automatically.
  • Deploy is narrower than the generator. The generator handles any business type in any country, but the deploy step still accepts dental leads only.
  • Manual deploys. GitHub Actions is off for this repo, so merging to main does not deploy. Deploys are manual and need my explicit go-ahead.
WorkHire me