Claude Code + Cursor: How a 2-Person Startup Shipped a SaaS in 30 Days
Two non-technical Vietnamese founders built a full dental clinic management SaaS — booking, patient records, invoicing, SMS reminders — in 30 days using Claude Code and Cursor. 15 paying clinics in month one. Pre-seed raised on traction.
Two founders. No engineering background. No funding. A domain problem they understood deeply — dental clinic management in Vietnam — and 30 days to build something clinics would actually pay for. This is how Ventra Rocket guided them through building a full SaaS product using Claude Code and Cursor as the engineering engine.
The Context: A Real Problem, Zero Code Experience
The founders had spent years working adjacent to dental clinics — one in medical supply sales, one in clinic administration. They knew the problem intimately:
- Clinics used paper booking systems or WhatsApp groups
- Patient records were in Excel spreadsheets, updated inconsistently
- Invoicing was manual, error-prone, with no payment tracking
- Appointment reminders were sent by hand via Zalo
Software solutions existed, but they were expensive (>$200/month), complex, and designed for large hospital groups — not small dental clinics with 2–5 chairs.
The opportunity: a simple, affordable SaaS purpose-built for Vietnamese dental clinics.
The constraint: neither founder could write code.
The Approach: Engineering Judgment Over Engineering Hours
Ventra Rocket's guidance to the founders was direct: you do not need to learn to code. You need to learn to be the product manager, domain expert, and QA lead — and let AI handle code generation. Your job is judgment, not syntax.
The tool stack:
- Claude Code: Architecture decisions, complex logic, debugging, code review
- Cursor: Rapid feature implementation, in-editor AI assistance
- Next.js + Supabase + Stripe: Modern, well-documented stack with strong AI training data
- Vercel: Zero-config deployment
- Twilio: SMS reminders via API
Week 1: Foundation — Get Something Running
The founders started by describing the product in detail to Claude Code. Claude Code returned a complete technical architecture:
nha-khoa-saas/
├── app/
│ ├── (auth)/
│ │ ├── login/
│ │ └── register/
│ ├── (dashboard)/
│ │ ├── patients/
│ │ ├── appointments/
│ │ ├── invoices/
│ │ └── settings/
│ └── api/
│ ├── appointments/
│ ├── patients/
│ ├── invoices/
│ └── sms/
├── lib/
│ ├── supabase/
│ ├── stripe/
│ └── twilio/
└── components/
├── ui/
├── calendar/
└── forms/
Claude Code then generated the Supabase database schema:
-- Generated by Claude Code from product description
CREATE TABLE clinics (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
address text,
phone text,
subscription_status text DEFAULT 'trial',
stripe_customer_id text,
created_at timestamptz DEFAULT now()
);
CREATE TABLE patients (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
clinic_id uuid REFERENCES clinics(id),
full_name text NOT NULL,
phone text,
date_of_birth date,
medical_history jsonb DEFAULT '{}',
created_at timestamptz DEFAULT now()
);
CREATE TABLE appointments (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
clinic_id uuid REFERENCES clinics(id),
patient_id uuid REFERENCES patients(id),
doctor_name text,
scheduled_at timestamptz NOT NULL,
duration_minutes int DEFAULT 30,
status text DEFAULT 'scheduled', -- scheduled, completed, cancelled, no_show
notes text,
treatment_type text,
reminder_sent_at timestamptz,
created_at timestamptz DEFAULT now()
);
CREATE TABLE invoices (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
clinic_id uuid REFERENCES clinics(id),
patient_id uuid REFERENCES patients(id),
appointment_id uuid REFERENCES appointments(id),
line_items jsonb NOT NULL, -- [{description, quantity, unit_price}]
total_amount decimal(10,2),
status text DEFAULT 'pending', -- pending, paid, overdue
paid_at timestamptz,
stripe_payment_intent_id text,
created_at timestamptz DEFAULT now()
);
By end of Week 1: working login, patient list, and appointment calendar — deployed on Vercel.
Week 2: Core Features with Cursor
With the foundation running, the founders used Cursor for rapid feature iteration. The workflow was:
- Describe the feature in plain Vietnamese to Cursor chat
- Review the generated code by asking Claude Code to explain any part they did not understand
- Test manually against real clinic scenarios they knew from experience
- Report bugs by pasting error messages into Claude Code for diagnosis
Example: SMS reminder feature
The founders described: "When an appointment is 24 hours away, automatically send an SMS reminder to the patient with the date, time, and doctor name."
Cursor generated:
// app/api/reminders/route.ts
import { createClient } from '@/lib/supabase/server';
import twilio from 'twilio';
const twilioClient = twilio(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN
);
export async function POST() {
const supabase = createClient();
// Find appointments in next 24-25 hours that haven't been reminded
const tomorrow = new Date();
tomorrow.setHours(tomorrow.getHours() + 24);
const dayAfter = new Date(tomorrow);
dayAfter.setHours(dayAfter.getHours() + 1);
const { data: appointments } = await supabase
.from('appointments')
.select(`
*,
patients(full_name, phone),
clinics(name)
`)
.gte('scheduled_at', tomorrow.toISOString())
.lte('scheduled_at', dayAfter.toISOString())
.eq('status', 'scheduled')
.is('reminder_sent_at', null);
const results = await Promise.allSettled(
(appointments ?? []).map(async (apt) => {
const patient = apt.patients as { full_name: string; phone: string };
const clinic = apt.clinics as { name: string };
const scheduledDate = new Date(apt.scheduled_at);
const timeStr = scheduledDate.toLocaleString('vi-VN', {
timeZone: 'Asia/Ho_Chi_Minh',
hour: '2-digit',
minute: '2-digit',
day: '2-digit',
month: '2-digit',
});
await twilioClient.messages.create({
body: `Nhắc lịch hẹn: ${patient.full_name} có lịch khám tại ${clinic.name} lúc ${timeStr} với BS ${apt.doctor_name}. Vui lòng đến đúng giờ.`,
from: process.env.TWILIO_PHONE_NUMBER,
to: `+84${patient.phone.replace(/^0/, '')}`,
});
await supabase
.from('appointments')
.update({ reminder_sent_at: new Date().toISOString() })
.eq('id', apt.id);
})
);
const sent = results.filter(r => r.status === 'fulfilled').length;
return Response.json({ sent, total: appointments?.length ?? 0 });
}
This endpoint was then triggered via a Vercel Cron Job every hour.
Week 3: Payments and Billing
Stripe integration was the most complex part — multi-tenant subscription billing. The founders described the pricing model; Claude Code generated the full Stripe integration:
// lib/stripe/subscriptions.ts
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function createClinicSubscription(clinicId: string, email: string) {
// Create Stripe customer
const customer = await stripe.customers.create({
email,
metadata: { clinic_id: clinicId }
});
// Create subscription with 14-day trial
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [{ price: process.env.STRIPE_PRICE_ID }],
trial_period_days: 14,
payment_behavior: 'default_incomplete',
payment_settings: { save_default_payment_method: 'on_subscription' },
expand: ['latest_invoice.payment_intent'],
});
return {
customerId: customer.id,
subscriptionId: subscription.id,
clientSecret: (subscription.latest_invoice as Stripe.Invoice)
.payment_intent
? ((subscription.latest_invoice as Stripe.Invoice).payment_intent as Stripe.PaymentIntent).client_secret
: null,
};
}
Week 4: Polish, QA, and Launch
The final week was the founders' most important contribution: domain expertise QA. They tested every feature against real clinic scenarios:
- What happens when a patient cancels 30 minutes before appointment?
- Can a doctor have overlapping appointments by accident?
- What if an invoice is partially paid?
Claude Code fixed every bug reported. The founders did not need to understand the fix — they needed to verify it worked.
End of Week 4: Deployed to production. Invited 5 clinic owners they knew personally for beta testing.
Results
| Metric | Result | |--------|--------| | Time to MVP | 30 days | | Lines of code (AI-generated) | ~8,500 (~80%) | | Features shipped | Booking, patient records, invoicing, SMS reminders, Stripe billing | | Beta clinics recruited | 5 | | Paying clinics after month 1 | 15 | | Monthly recurring revenue at month 1 | $900 MRR | | Pre-seed raised | Yes — based on traction |
Key Insights
AI Coding Tools Make Judgment, Not Code, the Bottleneck
The founders wrote almost no code. What they contributed was irreplaceable: domain expertise (how Vietnamese dental clinics actually work), UX decisions (what screens clinics need), and QA judgment (testing against real scenarios). AI cannot do any of these things. It can only write code for a product someone else has already figured out.
The Right Stack for AI-Assisted Development
Choose technologies with strong documentation and heavy AI training data coverage. Next.js, Supabase, Stripe, and Twilio are all well-represented in Claude's training data. This means generated code is accurate, idiomatic, and production-ready with minimal correction. Obscure or custom frameworks produce worse AI output.
Cursor for Speed, Claude Code for Depth
The workflow that emerged: Cursor for rapid feature implementation (inline suggestions, quick edits), Claude Code for architectural decisions and debugging complex issues. They are complementary, not interchangeable.
Validate Before You Build
The founders spent Week 0 (before any code) talking to 20 clinic owners and confirming willingness to pay. The 30-day build was fast because the product requirements were already validated. AI coding tools amplify execution speed — they do not substitute for market validation.
Conclusion
Two non-technical founders built a production SaaS in 30 days, acquired 15 paying customers in the first month, and raised pre-seed funding — all using Claude Code and Cursor as their engineering team.
The conclusion is not that engineering skills no longer matter. It is that AI coding tools have lowered the minimum viable engineering skill to execute on a well-specified product idea. Domain expertise and product judgment are now more differentiating than syntax knowledge.
Ventra Rocket helps founders and teams navigate AI-assisted development — from architecture to launch. Contact us to build your product.
Related Articles
AI Video Generation at Scale: Helping a Marketing Agency Produce 200 Videos/Month
A Vietnamese digital marketing agency serving 30+ e-commerce brands slashed video production cost from $800 to $35 per video and scaled to 200+ videos/month using an AI pipeline built on Claude, ElevenLabs, Runway Gen-3, and FFmpeg.
Gemini for Enterprise: Building a Multi-Modal Knowledge Base for a Hospital Network
A private hospital group in Vietnam with 12 locations unified 50,000+ medical records — PDFs, handwritten notes, X-rays, lab results — into a single AI-powered search system using Gemini 1.5 Pro. Diagnosis lookup time dropped from 15 minutes to 30 seconds.
Enterprise AI Adoption: Deploying OpenAI Codex for Internal Dev Teams
How an Australian fintech startup reduced developer onboarding from 3 months to 3 weeks, cut PR review time by 40%, and lifted test coverage from 45% to 89% — by integrating Codex across their entire development workflow.