← All Writing
February 28, 20267 min read

How I Replaced Google Forms with a Custom Contact Form Using Claude Code

Building a Supabase-backed contact form with email notifications in 2 hours — 4x faster than my first database project

YieldOne custom contact form with database storage and email notifications at joseandgoose.com/contact
DifficultyBeginner-Intermediate (second time setting up Supabase, first time using Resend)
Total Cook Time~2 hours in one evening session (vs 8–10 hours for my first Supabase project)

Ingredients

The Problem: Google Forms Look Like Google Forms

The contact page had a Google Form embedded in an iframe. It worked, but it looked like every other Google Form on the internet — white background, blue submit button, Google branding at the bottom. I wanted a form that matched the site’s design (cream background, forest green buttons) and sent me instant email notifications without opening the Google Forms dashboard.

The requirements: replace the iframe with a custom form, store submissions in Supabase, send email notifications via Resend, and make it feel native to the site. Having built Numerator two weeks earlier (8–10 hours, also using Supabase), I knew the database setup would be familiar. The question: how much faster with Claude Code in the terminal instead of Claude.ai in the browser?

The Build: One Evening, Four Phases

Evening, February 27 — ~2 hours total

Pace: Database setup fast (learned from Numerator). Design iteration slow (6 rounds of layout changes). Terminal workflow 4x faster than browser copy-paste.

Phase 1: Database and API (15 minutes)

Every contact form submission needs somewhere to live. The first step was creating a database table — essentially a spreadsheet in the cloud — to capture each submission’s name, email, message, and timestamp. Claude generated a setup script that I ran in Supabase’s web dashboard; no software installation needed on my end.

🔧 Developer section: Database schema and API route

I ran the SQL in the Supabase SQL Editor, verified the submissions table appeared in Table Editor, and the database was ready. Same flow as Numerator, but this time I knew where to find the SQL Editor and how RLS worked — no re-learning the dashboard.

Speed advantage

Claude Code writes files directly to your project via terminal. No downloading code blocks from the browser, no copy-paste into VS Code, no "did I save that file?" checks. It just edits app/api/contact/route.ts and it’s there.

Phase 2: Form Component (30 minutes)

With the database ready, Claude built the visible form — the fields a visitor sees, the submit button, the loading animation while it sends, and the success message when it’s done. Replacing the Google Form meant swapping one line of code (the embedded iframe) with the new component, which Claude handled directly in the terminal.

🔧 Developer section: Form component features

Updated app/contact/page.tsx to replace the Google Form iframe with <ContactForm />. Tested locally at localhost:3000/contact — form loaded, submitted a test, checked Supabase Table Editor and saw the submission. First try worked.

Phase 3: Design Iteration (45 minutes)

The form worked correctly on the first try — the slow part was making it look right. Over six rounds of plain-English feedback, I shaped the layout until it matched the rest of the site. Each round: describe the change, Claude updates the file, refresh the browser.

🔧 Developer section: Design feedback rounds

Each round: I described the change, Claude Code updated app/globals.css or ContactForm.tsx, I refreshed localhost:3000/contact in the browser, gave feedback. The terminal workflow meant no file switching in VS Code — Claude Code just edited the right file every time.

Iteration lesson

Design iteration is still the slowest part, even with AI. But Claude Code’s terminal access eliminated all the "which file do I edit?" friction. I gave feedback in plain English, it updated CSS and JSX directly, I refreshed. No context switching.

Phase 4: Email Notifications (30 minutes)

Submissions were saving to the database, but I had no way to know when one arrived without logging in to check. Adding instant email notifications meant connecting an email-sending service — Claude recommended Resend (a free API that sends email programmatically) and wired it into the form’s submit logic.

🔧 Developer section: Resend email integration

The email template was simple HTML: sender name, email, message, and timestamp. Good enough for launch.

Deployment: One Command and One Fix

Tested the form locally, verified email notifications worked, then pushed to GitHub. Vercel auto-deployed — and the build failed.

The error: new row violates row-level security policy for table "submissions". The RLS policy I’d written allowed anon role inserts, but Supabase wasn’t recognizing it. I tried recreating the policy, same error. Eventually: disabled RLS entirely on the submissions table (ran ALTER TABLE submissions DISABLE ROW LEVEL SECURITY; in the Supabase SQL Editor).

Submitted the form in production — success message appeared, email arrived, submission logged in Supabase. RLS is worth re-enabling later for security, but disabling it unblocked deployment.

Build lesson

Local dev servers are forgiving. Production builds are not. Supabase RLS policies can fail silently in ways that only surface during deployment. When blocked: disable RLS to ship, re-enable and debug policies later.

One more issue: forgot to add Resend environment variables to Vercel. Went to Vercel dashboard → project Settings → Environment Variables, added RESEND_API_KEY and NOTIFICATION_EMAIL, redeployed. Email notifications worked in production.

Terminal — git push
user@MacBook-Air joseandgoose-site-main % git add -A && git commit -m "Replace Google Form with custom Supabase contact form" && git push
[main 86b1045] Replace Google Form with custom Supabase contact form
7 files changed, 507 insertions(+), 46 deletions(-)
To https://github.com/joseandgoose/starter.git
7dae34b..86b1045 main → main

One git pushVercel deployed in 60 seconds → contact form live at joseandgoose.com/contact.

Final Output

A custom contact form at joseandgoose.com/contact with Supabase database storage, Resend email notifications, first/last name fields, character count, validation, success/error messages, and a design that matches the site’s cream and forest green aesthetic — built in 2 hours (vs 8–10 hours for my first Supabase project, Numerator).

What went fast

What needed patience

Claude Code vs Claude.ai: 4x Speed Difference

Numerator took 8–10 hours using Claude.ai in the browser. This contact form took 2 hours using Claude Code in the terminal. Same developer (me), similar complexity (both used Supabase, both had API routes, both required design iteration).

The difference:

The terminal workflow eliminated 50% of the steps. No context switching between browser and editor. No "which file do I edit?" questions. No copy-paste errors. Just: describe, refresh, iterate.

And because I’d already built Numerator (learned Supabase, understood RLS policies, knew how API routes worked), the second database project was 4x faster. The tools stayed the same. The experience compounded.

← Back to all writing