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 Neil dashboard actions (updated 2026-07-21)

Code is deployed. Two features wait on Cloudflare/Resend dashboard steps Neil must do (no safe CLI/API path for a git-deployed Pages project). All code fails safe until then. After Neil confirms each, Claude verifies.

A) Activate lead capture (v1.2.2) — REQUIRED for leads to store:

  1. Bind D1: Workers & Pages → reiss-builds-html → Settings → Bindings → Add → D1 database. Variable name DB (exact), database reiss-builds-leads. Save (Production).
  2. Redeploy (Deployments → latest → Retry).

B) Turn on /admin/leads viewer (v1.3.0 — BUILT + DEPLOYED, returns 403 until configured):

  1. Create a Cloudflare Access app (Zero Trust → Access → Applications → Add, Self-hosted): domain reissbuilds.com, path /admin*. Add a policy allowing the right emails (e.g. jeff@ + neil). Copy the Application Audience (AUD) tag and your team domain (<team>.cloudflareaccess.com).
  2. Set two Pages env vars (Settings → Variables and Secrets → Production): CF_ACCESS_TEAM_DOMAIN = <team> (or full <team>.cloudflareaccess.com), CF_ACCESS_AUD = the AUD tag. Save → Redeploy.

C) Security hygiene (non-blocking — form works today): rotate exposed Resend key. resend.com/api-keys → create reissbuilds-pages (Sending, domain reissbuilds.com) → copy → delete old key re_d9yZYtrP_… → Pages → Variables and Secrets → Production → edit RESEND_API_KEY → paste → Save → Redeploy.

Phase B: /admin/leads viewer — BUILT (v1.3.0, 2026-07-21)

functions/admin/leads.ts — server-rendered Pages Function. Verifies the Cloudflare Access JWT (RS256 sig vs team JWKS + iss/aud/exp), fails closed (403) if the JWT or the CF_ACCESS_* env vars are missing/invalid. Renders a newest-first table of D1 submissions and exports CSV via ?format=csv. Read-only (no delete in v1). PII responses are no-store + noindex; all output HTML-escaped; CSV guarded against formula injection. Activate via RESUME step B above.

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).