REISS BUILDS — Project Guide (for Claude Code)

South Florida construction/remodeling site (client: Jeffrey Reiss). Static marketing site + one serverless contact form.

Stack & deploy

Local dev / git notes

Contact form architecture (as of v1.2.2)

Flow: /contact (Turnstile widget) → POST functions/api/contact.ts → honeypot + length/email validation → Turnstile verify → D1 insert (capture the lead, source of truth) → Resend email (best-effort notification) → record email_status on the row → redirect /thank-you/.

⏳ RESUME HERE — pending actions (2026-07-20)

The contact form is FIXED and deployed. Lead capture code is deployed but inactive until the D1 binding is added. Neil needs to do these in the Cloudflare dashboard, then Claude verifies:

  1. Bind D1: Workers & Pages → reiss-builds-html → Settings → Bindings → Add → D1 database. Variable name DB (exact), database reiss-builds-leads. Save (Production).
  2. Rotate Resend key: resend.com/api-keys → create reissbuilds-pages (Sending, domain reissbuilds.com) → copy → delete old key → Settings → Variables and Secrets → Production → edit RESEND_API_KEY → paste → Save. (Old key re_d9yZYtrP_… is exposed in git history — must rotate.)
  3. Redeploy once (Deployments → latest → Retry) to activate the binding + new key.

After Neil confirms, Claude should: submit a test at /contact, then
npx wrangler d1 execute reiss-builds-leads --remote --command "SELECT id, created_at, first_name, last_name, email, email_status FROM submissions ORDER BY id DESC LIMIT 10"
— confirm the row exists with email_status = sent.

Next feature — Phase B: /admin/leads viewer

Build a page to browse + CSV-export captured leads from D1. Must sit behind Cloudflare Access (free Zero Trust) because it exposes customer PII — gate the function to refuse serving unless an Access JWT (Cf-Access-Jwt-Assertion) is present. This also fixes the long-broken DecapCMS /admin auth.

History / context

Contact form silently lost leads for ~a year. Root cause was three stacked failures: (1) Turnstile TURNSTILE_SECRET didn't match the site widget (invalid-input-secret) — rejected every submit at the spam gate; (2) Resend was on the onboarding@resend.dev sandbox (only delivers to account owner dev+github@agilemedia.agency) → 403 on every lead; (3) Resend domain unverified. All fixed 2026-07-20 (v1.2.1). Lead capture (v1.2.2) added so this can never silently happen again. Full detail in project memory (session_2026-07-18_email_diagnosis.md).