posts/one-shotting-leadlink
// Essay/8 min read/2026-03-10

I One-Shotted an Entire SaaS App with OpenAI's 5.4 Model

One prompt. One run. A fully functional link-in-bio app with Stripe billing, SMS delivery, and Supabase auth — built in Codex.

share//x/linkedin/hn
45 minSpec Time
8 minExecution
0Follow-ups
10Tasks
One prompt, one working SaaS appSpec took 45 min, execution took 8 minSpecification engineering > promptingThe spec is the product, the model is the compiler
Key Takeaway
The spec is the product now. The model is the compiler. Write better specs — that's the whole game.

I wrote a spec. I pasted it into OpenAI's Codex with the new 5.4 model. I hit run. And I got back a working SaaS app — auth, billing, SMS, database, public pages, the whole thing.

One prompt. One shot. No follow-ups.

The app is called LeadLink. It's a link-in-bio page for local service businesses with a built-in lead capture form that texts the business owner when someone submits it. $9/month. One design. No customization. The kind of boring, profitable micro-SaaS that actually makes money.

And the entire thing was built from a single prompt.

The Prompt Was the Product

I didn't write code. I didn't debug. I didn't even have a back-and-forth conversation with the model. I wrote a specification — a detailed, opinionated, constrained specification — and the model executed it.

This is the thing I've been writing about for months. The skill isn't prompting. It isn't even managing an AI in a feedback loop. It's specification engineering. Writing a document so clear and so constrained that a machine can execute it without asking questions.

The spec was roughly 2,000 words. It covered:

  • Hard constraints — what the app will NOT do (no themes, no tiers, no dashboard, no analytics, no mobile app)
  • Data model — two Supabase tables with exact column types, RLS policies
  • Routes — every page and API endpoint with its purpose
  • Task breakdown — 10 discrete tasks with acceptance criteria and dependency graph
  • Environment variables — the exact list
  • Out of scope — an explicit "not yet" list so the model doesn't get creative

The constraints mattered more than the features. Every time I've seen an AI go sideways, it's because the spec left room for interpretation. "Build me a SaaS" gets you a mess. "Build me exactly this, with exactly these tables, and don't do anything else" gets you a product.

What the 5.4 Model Did Differently

I've tried this before with earlier models. They'd get 60-70% of the way there and then hallucinate a feature, forget a webhook handler, or wire up auth wrong. You'd spend more time debugging than you saved.

The 5.4 model in Codex just... followed instructions. It read the task breakdown, understood the dependency graph, and executed each task in order. The Stripe webhook handler had all three events. The RLS policies were correct. The magic link auth flow actually redirected to the right place.

I don't know what changed architecturally. I don't care. What I care about is that the gap between "spec" and "working software" just collapsed to a single step.

The Full Prompt

I'm including the entire specification below. Not a summary. Not the highlights. The whole thing. Because the prompt IS the interesting part — it's the artifact that has value. The code it generated is commodity output.

If you want to build this yourself, copy this spec, paste it into Codex, and hit run.


LeadLink — Codex Build Spec

What This Is

A link-in-bio page for local service businesses with a built-in lead capture form that sends an SMS to the business owner when someone submits it. $9/month. One design. No customization.

Constraints

These are hard rules. If a task introduces something not listed here, it's out of scope.

  • One page template. No theme picker, no color selector, no font options. One good-looking responsive design.
  • One plan. $9/month. No tiers, no trials, no annual pricing.
  • No dashboard. Business owners log in to edit their page. That's the only authenticated view.
  • No analytics. V1 has no stats, no charts, no "leads this week."
  • No mobile app. It's a web app. It works on phones already.

Data Model (Supabase)

businesses table:

ColumnTypeNotes
iduuid, PK
user_iduuid, FK → auth.usersSupabase auth
nametextBusiness display name
slugtext, uniqueURL path: leadlink.app/{slug}
phonetextOwner's phone for SMS delivery
logo_urltext, nullableSupabase storage URL
linksjsonbArray of { label, url }, max 10
is_activeboolean, default falseFlipped true when Stripe subscription is active
stripe_customer_idtext, nullable
stripe_subscription_idtext, nullable
created_attimestamptz

leads table:

ColumnTypeNotes
iduuid, PK
business_iduuid, FK → businesses
nametextLead's name
phonetextLead's phone number
messagetextWhat they need
created_attimestamptz

Row-Level Security

  • businesses: owners can read/update their own row. Public can read name, slug, logo_url, links for active businesses.
  • leads: insert is public (anyone can submit a lead). Select is restricted to the business owner.

Pages / Routes

Public (no auth):

  • / — Marketing landing page. Headline, value prop, "Get Started" button → signup.
  • /[slug] — Public business page. Renders name, logo, links, and lead capture form.

Authenticated (business owner):

  • /signup — Create account (Supabase auth magic link), then redirect to /edit.
  • /login — Magic link login. Redirect to /edit.
  • /edit — Edit business page: name, slug, logo, links, phone number. One screen. Save button.

API Routes (serverless):

  • POST /api/lead — Receives lead form submission. Inserts into leads table. Sends SMS via Twilio to the business owner's phone.
  • POST /api/stripe/checkout — Creates a Stripe Checkout session for $9/mo. Redirects to Stripe.
  • POST /api/stripe/webhook — Handles checkout.session.completed, customer.subscription.deleted, invoice.payment_failed. Updates is_active on the business.

Task Breakdown

Task 1: Project Scaffold — Set up the repo. Vercel project. Supabase project. Environment variables stubbed. Basic layout component with a clean sans-serif font and nothing else. Acceptance: vercel dev runs. Supabase client connects. A blank page renders at /.

Task 2: Database Setup — Create the Supabase migration for businesses and leads tables with the schema above. Set up RLS policies. Create a Supabase storage bucket for logos (public read, authenticated upload). Acceptance: Tables exist. RLS policies pass manual testing. Logo upload works via Supabase client. (Parallel with Task 1)

Task 3: Public Business Page (/[slug]) — Render the public-facing link page for a business. Fetch the business by slug. Display business name (large, centered), logo (if present), links as a vertical stack of pill-shaped buttons, and a prominent "Contact" / "Request a Quote" button that opens the lead form. If is_active is false, show a generic "This page isn't available" message. Design: White/light background. Dark text. Mobile-first. No customization — every page looks the same except for the content. (Parallel with Task 2)

Task 4: Lead Capture Form — Inline form (not a modal) with three fields: Name, Phone number, What do you need? Submit POSTs to /api/lead. Shows confirmation: "Thanks! [Business name] will text you shortly." Disables button after submit. No CAPTCHA in v1. (Parallel with Task 3)

Task 5: SMS Delivery (/api/lead) — Validate payload, insert into leads, look up owner's phone, send SMS via Twilio with lead details. Return 200 even if Twilio fails (lead is saved, SMS failure is logged). (Depends on Task 4)

Task 6: Auth + Onboarding (/signup, /login) — Supabase magic link auth. Signup redirects to /edit, which detects no business exists and shows onboarding. Login loads existing data. Minimal UI — one screen for auth, one for editing. (Parallel with Tasks 3–5)

Task 7: Edit Page (/edit) — Single authenticated page: business name, slug (auto-generated, editable, validated for uniqueness), phone number, logo upload, links (up to 10, add/remove/reorder with arrow buttons). Live preview link. (Depends on Task 6)

Task 8: Stripe Billing — One product: $9/month. Banner on edit page if not active. Checkout session via /api/stripe/checkout. Webhook handles checkout.session.completed, customer.subscription.deleted, invoice.payment_failed. (Depends on Task 7)

Task 9: Landing Page (/) — One screen. Headline about getting texted when someone wants to hire you. One paragraph. Screenshot/mockup. "Get Started" button. Price: "$9/month. That's it." Footer with contact email. No testimonials, no feature grid. (Parallel with everything)

Task 10: Deploy + End-to-End QA — Full loop test: landing page → signup → create page → verify inactive → subscribe → verify active → submit lead → verify SMS → verify database. All 9 steps pass. (Depends on all tasks)

Environment Variables

NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY=
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_PHONE_NUMBER=
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
NEXT_PUBLIC_STRIPE_PRICE_ID=
NEXT_PUBLIC_BASE_URL=

Out of Scope (The "Not Yet" List)

All of these are good ideas. None of them are v1: Custom domains. Analytics / lead history dashboard. Email notifications. Multiple team members. Custom colors / themes / fonts. AI anything. API access. Zapier / webhook integrations. Review/rating widgets. Appointment booking. Custom form fields. CAPTCHA / spam protection. Dark mode.


What This Means

I've been talking about specification engineering as the next skill that matters. This is what it looks like in practice.

The spec took me about 45 minutes to write. The model took about 8 minutes to execute it. I had a deployable SaaS app in under an hour.

Let that sink in. An hour. For an app with authentication, billing, SMS delivery, a database with row-level security, a public page, and an edit screen. The kind of thing that would have been a two-week sprint for a solo developer. Maybe a week if you're fast and have done it before.

The leverage isn't in the model. The leverage is in the spec. A vague spec gets you vague output. A precise spec with hard constraints, explicit acceptance criteria, and a clear dependency graph gets you a working product.

The Uncomfortable Part

I didn't use Claude for this one. I used OpenAI's 5.4 in Codex. And it worked on the first try.

I'm not switching loyalties. I use Claude Code for 95% of my work and it's still my daily driver for iterative development — the feedback loop, the course correction, the "not that, try this" workflow. That's where Claude shines.

But for this particular use case — "here is a complete spec, execute it in one shot" — the 5.4 model in Codex nailed it. Credit where it's due.

The takeaway isn't "use this model" or "use that model." The takeaway is that the spec is the product now. The model is the compiler. And just like you don't care whether your code compiles on GCC or Clang as long as the binary works, you shouldn't care which model executes your spec as long as the app works.

Write better specs. That's the whole game.

share//x/linkedin/hn
enjoyed this?

Get posts like this delivered to your inbox. No spam, no algorithms.