Each claim ships with concrete evidence — env vars, table names, cadence chips. No marketing fluff.
◆DesignSame visual language, much more surface area
We kept the navy-and-periwinkle palette, the Poppins typography, the drone-captured aerial heroes, and the corporate-calm voice — these were already correct for a contractor talking to fire-trauma families. The rebuild adds a typed forms layer, a 6-step '01–06' phase timeline on /services, distinct /free-consultation and /contact forms (the old site dumped both into the same Fluent Forms endpoint), three city-specific SEO landings, and 20 SSG insight posts. Hero copy is preserved verbatim where it worked.
- Before
- 5 pages, one form, no blog, every CTA hit the same generic contact endpoint.
- After
- 27+ routes, typed Zod-validated forms, dedicated free-consultation flow, /projects gallery, /insights with 20 long-tail posts, three city landings (/service-areas/pacific-palisades, /altadena, /malibu).
△Agent backboneOutreach drafter on a 4-hour Vercel Cron
Every 4 hours the cron hits GET /api/agents/outreach (auto-injected Bearer CRON_SECRET). It pulls new leads that have no outreach_attempts yet, calls Anthropic claude-sonnet-4-5 with a system prompt that anchors voice ('corporate, calm, competent — never WALK-INS WELCOME energy, never imitate insurance or FEMA'), drafts personalized subject + body per channel (email or SMS), and writes the draft to outreach_attempts with status='queued'. Brendon reviews at /admin/outreach and approves with one click — which dispatches via the verified-identity SMTP path (email) or the Twilio raw API (SMS).
- Before
- No outreach — only the family who happened to find the contact form ever heard back.
- After
- Every new lead gets a Claude-drafted intro queued for human approval within 4 hours.
- ▸Schedule: 0 */4 * * * in vercel.json
- ▸Model: claude-sonnet-4-5
- ▸Throttle on send: 250ms between recipients
- ▸Bounce + complaint path: SES Configuration Set with SNS topics → Stalwart suppression list (Hetzner) — currently transitioning per 2026-05-28 audit
△Agent backboneAnthropic ChatWidget with RAG over the 20 insights posts
Floating widget at bottom-right of every public page (hidden on /admin/* and /client/*). POST /api/chat does keyword-overlap RAG over the posts table — lowercased ≥4-char terms, regex match on title or body_md, top 4 hits with their excerpts get injected into the system prompt. Streams Anthropic Messages API SSE through plain text/plain to the browser. System prompt anchors voice + always closes with 'Want a 30-min call? Book at /free-consultation — no charge, no obligation.'
- ▸Source: src/app/api/chat/route.ts + src/components/ChatWidget.tsx
- ▸Stop list: this, that, with, from, what, when, where, which, there, …
- ▸Render hook: usePathname() hides widget on /admin* and /client*
◉RetentionHomeowner client portal — timeline · docs · budget · messages
Each family gets /client/[slug] gated to project.ownerUserId. They see: an ordered status timeline (lead → design → permitting → insurance coordination → construction → final inspection → delivered), a document vault (insurance dec pages, permits, contracts — private R2 with presigned-GET, 5-min URLs), a budget tracker (received / paid / remaining with category breakdowns — the #1 question every family asks), a message thread with photo attachments (R2 public bucket), and a drone footage gallery. Internal-only entries flagged via visibility='internal' don't render to clients. 11 new DB tables backed by Drizzle + Neon.
- ▸13 tables total in src/db/schema.ts including project_documents, project_messages, project_budget_entries
- ▸Budget = headline cards (Received / Paid / Remaining + % spent) + entries table with kind badges (RECEIVED green / PAID red / COMMITMENT)
- ▸Document vault: 100 MB cap, content-type whitelist, scoped key prefix documents/<projectId>/<random>-<safeName>
▲Real-time pushTwilio inbound-SMS reply matcher on +1 213 526 0198
Inbound SMS to the Palisades subaccount number POSTs to /api/webhooks/twilio. We normalize the From phone (strip everything but digits + +), join outreach_attempts ↔ leads on digit-matching phone, mark the most-recent sent SMS attempt as status='replied', persist replyBody + replyReceivedAt, return empty TwiML 200. The admin /outreach review page surfaces replies inline under the original draft.
- ▸Twilio subaccount AC0373…30208 (under Life Candles LLC parent)
- ▸Emergency address registered (87 N Raymond Ave Suite 526, Pasadena CA 91103) — avoids $75/emergency-call fee
- ▸X-Twilio-Signature validation: TODO marker in the route handler
◆DesignMailers + EDDM print pipeline (no Cmd+P required)
/admin/mailers renders three print-ready EDDM flyer variants (Palisades / Altadena / Malibu) via Puppeteer + qrcode-generator. Each variant has a UTM-tagged QR code that opens /free-consultation?utm_source=eddm&utm_campaign={variant}. Click 'Print' → PDF download → ship to USPS EDDM bulk-mail vendor. Saves 10–15 minutes per print run versus rebuilding the Adobe artwork each campaign.