Blog Post

How I Built RunPitch — An AI Proposal Generator with GPT-4 and Claude

10 min read

RunPitch is an AI-powered proposal generator I built to help freelancers on Upwork win more clients. Paste in a job posting, give it your profile, get a personalised, high-converting proposal in seconds. Multiple AI models, profile optimisation, tone customisation, tiered subscription.

It is live, it has paying users, and it is one of the two products I point to when a founder asks "have you actually shipped this kind of thing before?"

This is the build journal — architecture, technical decisions, what worked, what I rebuilt, and what I would do differently if I started today.

The problem that started it

I have been on Upwork since 2019 — 56 completed jobs, around $30K earned. The single biggest time-sink was always the same: writing proposals.

A good proposal takes 15–25 minutes. Read the job posting carefully. Match it against your skills. Address the specific pain points. Sound human, not templated. Personalise enough to stand out. Multiply that by 5–10 proposals a day to actually fill a pipeline, and proposal-writing eats half your week before you have done any client work.

When GPT-4 became reliably good at structured writing, the opportunity was obvious — but the obvious version (just paste GPT into a textbox) was not the right product. The right product was something that knew your context, your tone, your track record, and used the AI as a writing engine — not a substitute for a human pitch.

The decision: build it

A few things made the case clear:

  1. I had the unit economics in my head. I knew exactly how much a freelancer would pay to save 10 hours a week.
  2. I had personal pain. I would use this every day myself.
  3. The technical stack — Next.js + OpenAI + Stripe + PostgreSQL — was already what I built professionally.
  4. I could ship an MVP in evenings without quitting my job.

I gave myself six weeks. It took eight.

The architecture

Here is the full stack as it stands today:

  • Frontend: Next.js (App Router), TypeScript, Tailwind CSS, shadcn/ui components
  • Backend: Next.js API routes for the simple stuff, a small NestJS service for the AI orchestration
  • Database: PostgreSQL hosted on Supabase
  • Authentication: Supabase Auth (Google OAuth + email/password)
  • AI providers: OpenAI (GPT-4o, GPT-4o-mini) and Anthropic (Claude Sonnet, Claude Haiku)
  • Payments: Stripe Checkout + Customer Portal + webhooks
  • Hosting: Vercel (frontend + API routes), Railway (NestJS service), Supabase (database)
  • Monitoring: Sentry for errors, simple custom usage dashboard

Nothing exotic. The discipline was choosing well-understood tools so I could spend my thinking time on the product, not the infrastructure.

The dual-model AI system

The most interesting technical decision was building RunPitch on two AI providers, not one.

Founders who only integrate OpenAI eventually discover three problems:

  1. Outages. OpenAI has had at least four major outages I have noticed in the last year. If your entire product is "press button, get GPT-4 response", every outage is downtime.
  2. Quality variance per task. GPT-4 is excellent at certain styles. Claude is materially better at long-form, structured, persuasive writing — exactly what proposals are.
  3. Cost ceilings. Some users generate hundreds of proposals a month. The premium model on every request would destroy the unit economics.

So RunPitch routes requests across models based on three signals:

  • User tier (free users get cheaper models, premium users get the flagship)
  • Task complexity (long, persuasive proposals → Claude Sonnet; quick edits → GPT-4o-mini)
  • Provider availability (if OpenAI is degraded, Claude takes over and vice versa)

The orchestration layer is small — about 200 lines of TypeScript that wraps both SDKs behind a unified generateProposal() interface. The model selection happens before the call, and the result is logged with the model used so I can track quality and cost separately.

This is the kind of architecture decision that costs you a few extra days of build time and saves you weeks of firefighting later.

The prompt design problem

The naive prompt for an AI proposal generator is one big block: "You are a proposal writer. Here is the job posting. Here is the user's profile. Write a proposal."

That works at about 60% of the quality you want. The other 40% comes from doing what every founder eventually has to learn — treating prompts as code, not creative writing.

The actual RunPitch prompt is layered:

  1. System prompt — defines the role, tone constraints, output format
  2. Profile context — injected from the user's saved profile (skills, experience summary, communication style)
  3. Job analysis step — a separate, smaller AI call that extracts the client's specific pain points before the generation step
  4. Tone control — user-selected style (formal, conversational, technical) appended dynamically
  5. Structure enforcement — a strict template the output must follow

That last point matters more than people realise. The output of a single GPT-4 call drifts in structure across requests. By forcing a template — "Greeting → 1 sentence on the problem → 2–3 sentences on the solution → 1 sentence credibility → call to action" — every proposal feels intentional, not random.

What broke first

The first version of RunPitch had a critical flaw I did not catch in testing: I had no usage limits.

A single user on the free tier generated 47 proposals in one weekend. That is roughly $0.50 in AI costs for a free account that would never convert. Multiply that by a few hundred similar users and the unit economics implode before a single paying customer signs up.

Fixing this took two days of work I should have built upfront:

  • Per-tier daily and monthly generation limits stored in the database
  • A middleware that checks usage before each generation request
  • Visible usage UI in the dashboard so users see their remaining quota
  • A smart upgrade prompt that shows when a user hits their cap

Lesson: build usage limits before launch, not after. They are not premature optimisation. They are unit economics.

Stripe and the billing layer

Stripe billing for an AI SaaS is harder than it looks. The standard flow — subscription, customer portal, webhook — gets you 70% there. The other 30% is the AI-specific edge cases:

  • What happens when a user cancels mid-month? (They keep access until period end, but you should freeze their AI quota immediately if they are on a usage-capped plan.)
  • What happens when a payment fails? (You need a grace period, not an instant lockout.)
  • What if Stripe webhook processing fails? (Idempotency. Always idempotency.)

The webhook handler in RunPitch is genuinely the most-tested code in the project. Every event is logged, every state transition has a database row, every failure path is reversible. If billing breaks, customers leave and never come back.

If you are building this from scratch, do not skip the boring parts:

  • Idempotency keys on every webhook handler (Stripe sends events more than once during retries)
  • A subscription_events table that logs everything so you can replay or debug
  • A grace-period state for failed-payment users so you do not lock them out instantly

The deployment and CI/CD setup

Everything sits on a GitHub Actions pipeline. Every PR runs:

  1. Type checking
  2. Linting
  3. A small set of integration tests against a test database
  4. A preview deployment to Vercel for visual inspection

Production deploy on merge to main. No manual steps. This is non-negotiable for me at this point — every project I have shipped without CI/CD has eventually broken in a way CI/CD would have caught.

Net effect: I have shipped RunPitch from my phone. Not because I want to — because I can.

The numbers (loosely)

I will not share exact revenue numbers, but the rough shape:

  • AI costs per generation: ~$0.012 average (mix of GPT-4o and Claude Sonnet)
  • Average user: 30–60 proposals/month
  • Monthly infrastructure: ~$80 (Vercel, Supabase, Railway, Sentry)
  • Free-to-paid conversion: in the single-digit percentages, which is normal for self-serve B2C-style tools
  • Time from idea to first paying customer: ~9 weeks
  • Total build cost in real money (excluding my time): under $500 (domain, design assets, Stripe Atlas, monitoring)

The product is profitable. Not yet life-changing money, but every line of growth from here is compounding.

What I would do differently if I started today

Honest list:

  1. Ship a waitlist before writing code. I built first and validated after. It worked, but I was lucky. Next time: landing page + waitlist for two weeks before writing a single line.
  2. Pick Bun + Hono instead of NestJS for the AI service. I built RunPitch before Bun was stable enough. Today, the orchestration service would be 30% smaller and significantly faster on Bun + Hono.
  3. Bake observability in from day one. I added Sentry late. Two weeks of debugging in the dark could have been two hours with proper logging from the start.
  4. Write the upgrade flow before the free tier. I built the free experience first, then bolted on paid. The reverse — build the upgrade path first — would have made the free tier a clearer funnel from day one.
  5. Use Claude for the proposal generation by default. I started with GPT-4 because the SDK was more familiar. Claude Sonnet is genuinely better at this specific task. Founders building products that depend on persuasive long-form writing should not default to GPT.

What I would tell a founder building this kind of product

Five things, in order of importance:

  1. Validate the pain before you write code. Talk to 10 people who would use it. If 8 of them say "I would pay for that" — you have a product. If they say "that is interesting" — you have nothing.
  2. Build for unit economics from day one. The cost of every generation. The conversion rate. The time-to-value. If your numbers do not work at 100 users, they will not work at 10,000.
  3. Pick boring tools. The exotic stack you saw in a Twitter thread is the stack that will burn you at 2 a.m. when production breaks. Use what you know.
  4. Two AI providers, not one. Cost, quality, and uptime all have answers in dual-provider architecture. Build it from the start, even if you only call one initially.
  5. Ship before you are ready. RunPitch had bugs at launch. It still got users, who told me what to fix, which made it better than anything I would have shipped at "ready".

If you are building something like this

Building an AI SaaS product is a specific skill. There is the AI part, the SaaS part, and the part that connects them — and most engineering articles only cover one.

I run an AI Integration Audit and build AI SaaS MVPs end-to-end for founders who want a partner with experience shipping this exact kind of product. If you are scoping something similar, the free 15-minute call is the fastest way to figure out the right architecture for your specific problem.

Or just visit runpitch.com and see what shipped.

Building something with AI?

I build production-ready AI SaaS products for founders — the same stack behind RunPitch and Help Writing Resumes. Book a free 15-minute call.

Ship with Daniel

Building and shipping AI SaaS products — one feature at a time. Join the newsletter.

Get the newsletter