A self-serve marketplace where roofing contractors order qualified leads and pay upfront via Stripe. Built end-to-end on Express + Netlify Functions + Supabase, with Twilio SMS notifications, Meta Pixel + CAPI deduplication, and a custom server-authoritative coupon engine.
Local roofing contractors waste thousands a month on lead-gen platforms that bundle stale, oversold leads under opaque pricing. The economics break for the buyer, the experience breaks for the homeowner, and nobody owns the relationship.
RoofRoof is built around a simple inversion: flat-price leads, exclusive on first-touch, billed upfront, no per-night invoice surprise. A contractor logs in, chooses a service area, drops a credit card, and gets a clean stream of phone-verified intent leads from homeowners who filled out the LP form and consented to outreach.
Single deployment surface (Netlify), single domain (roofroof.solutions), single Postgres (Supabase). Functions handle the API; the rest is static. Every secret lives in Netlify env, every external write is wrapped in a try/catch so a Twilio outage never 500s the lead form.
[ Visitor / Contractor ] │ ┌─────────────────────┴─────────────────────┐ │ │ /rr-lp.html /dashboard (intake form) (buyer portal) │ │ └─────────────────────┬─────────────────────┘ ▼ Netlify Functions (Express) ┌──────────────┬──────────────┬──────────────┬──────────────┐ ▼ ▼ ▼ ▼ ▼ leads orders stripe coupons admin /intake /checkout /webhook /validate CRUD + refund │ │ │ │ │ └──────────────┴──────┬───────┴──────────────┴──────────────┘ ▼ Supabase Postgres (transaction pooler · RLS) tenant-isolated tables · service vs anon roles │ ┌──────────────────────┼──────────────────────┐ ▼ ▼ ▼ Stripe (live) Twilio (toll-free) Resend (verified) payments + webhook SMS lead-blast transactional
Promo handling lives entirely server-side. The client never sees the rules, the codes, or the discount math — it gets a final price and a yes/no on whether a code applied. Per-user usage caps, max-quantity limits, and per-code constraints are all enforced on the server before any payment intent is created. Codes that aren't meant to be public are kept out of the rendered HTML so view-source can't enumerate them.
Browser Pixel + server-side Conversions API (via Stape gateway) both fire Lead when a homeowner completes the intake form. To prevent Meta from double-counting, every browser/server pair shares a deterministic event_id tied to the new lead's record. Meta dedups on collision and the attribution stays clean.
Short-lived serverless functions cold-starting fresh DB connections on every invocation = pool exhaustion at scale. Solution: route Postgres through a transaction pooler with prepared-statement caching disabled on the client. Skip the latter and you get a stream of duplicate-prepared-statement errors on every cold start. (Saved as a permanent note in personal memory — too painful to relearn.)
This is not a portfolio toy — it processes real card payments and sends SMS to real numbers. That changes the engineering bar.
~28 seconds from git push to production. Push-before-deploy is the law — no rogue CLI deploys allowed.
RLS enabled on every database table. Service role for server, anon role for client, never the other way around.
Live Stripe; webhook signature-verifies every event and reconciles order state. Everything else assumes the webhook is the source of truth, not the client.
Verified toll-free number for outbound notifications. Lead-capture SMS blast fans out to a recipient list held in a single env var — editable without redeploying.
Scheduled trigger pings Twilio once per day; pushes an ntfy alert if the toll-free verification status changes.
One restoreSiteDeploy call rolls live back to any prior bundle in seconds — free, no rebuild credits, no DNS churn. Tested in anger; see incident below.
One night earlier this year, admin lead rows stopped being clickable in production. Black-box smoke-test passed (200s everywhere), but interaction logic was missing from the live admin bundle. Worth telling because the response shape is the differentiator.
Auto-deploy chain looked clean on GitHub, but the host's build history showed a CLI deploy fired earlier in the night with no commit reference and no committer — fingerprint of someone running deploy --prod off a stale local checkout.
An external collaborator's fork had pushed an old branch directly to production via CLI. The deploy carried no commit hash and silently overwrote a chunk of admin-UI handlers.
restoreSiteDeploy against the last clean auto-deploy bundle. Live restored in seconds, no rebuild, no credits charged. Wrote a permanent recipe so this fix is one paste away next time.
Drafted a periodic Python daemon that polls public collaborator activity + deploy hooks and pushes ntfy on anything that looks rogue. Build deferred — but the visibility gap is closed in spec form.
Git-before-deploy is now non-negotiable. Any change goes to source control first; anything that lands on prod without tracing back to a commit on the active production branch gets rolled back on sight.
First contracted client engagement — Next.js 16 multi-brand rebuild, federal-compliant Liberty Press launch.