AI Lead Generation Platform — SaaS Case Study
This is the case study for the AI lead-generation SaaS I built and shipped as the solo full-stack developer. The product helps sales teams find, filter, and reach the right leads without burning a week per campaign on manual research. Spain-born, Paraguay-based, I owned the build from blank repository through production deployment and the post-launch fix window.
What was the problem?
Sales teams were drowning in lead lists that were 90% noise. The old workflow was: scrape a list, hand-filter it in a spreadsheet, write outreach copy one at a time, paste into a CRM, follow up manually, repeat. The repetitive parts ate the senior reps' time while the actual selling — the conversations — got squeezed into the margins of the day.
The hypothesis behind the product was simple: most of the filtering and the first-pass outreach copy are pattern-recognition tasks an LLM can do well, if you give it the right context and a strict structured-output contract. The team didn't need another generic AI tool; they needed a focused SaaS that took their existing lead lists in, applied their own qualification rules, and gave them prioritized, ready-to-send outreach drafts out the other side — with a dashboard their non-technical staff could actually use.
The constraint was the usual one for solo-built SaaS: ship something users will pay for, in months not quarters, with zero room for the kind of over-engineering that kills indie products.
What was the approach?
I treated this as a SaaS MVP build with one defensible feature — the AI filtering and outreach pipeline — and a deliberately boring everything-else. The full sequencing I use is documented in build a SaaS MVP fast; this project followed it closely.
The phased plan:
- Discovery and spec. Lock the one feature that made the product worth shipping (the AI scoring + outreach pipeline), agree the three features that could wait, and freeze the data model before any code went in.
- Vertical slice first. End-to-end happy path from authentication through lead upload, scoring, and a single outreach draft — deployed to a staging environment in week one. This forced every system to exist in skeleton form before any of them got polished.
- Tighten the AI loop. Prompt iteration against a frozen evaluation set of real leads with known good/bad outcomes. The scoring step ran first; the copy-generation step plugged in once scoring was reliable.
- Production hardening. Stripe billing, webhook idempotency, multi-tenant isolation in Postgres, error monitoring, audit logging, GDPR-compliant data export and deletion.
- Ship and hand over. Production deploy to the customer's own AWS account, 90-minute handover call, written runbook in the repo, 14-day post-launch fix window.
There were no surprises in the schedule because the spec was specific enough to refuse scope drift. New requests after spec-freeze went into a v2 list, not into the v1 sprint.
What is in the stack?
The stack stayed deliberately boring because the differentiating value lived in the AI pipeline, not in the framework choice.
- SvelteKit + TypeScript on the front-end. Fewer concepts than Next.js, smaller bundles, SSR by default for the marketing pages and CSR-after-hydration for the authenticated app. See the SvelteKit docs for the rendering model.
- Node.js + Express + Prisma on the back-end. REST API, fully typed end-to-end through generated Prisma types and shared Zod schemas. Background jobs ran on a separate worker process so HTTP requests never blocked on LLM latency.
- PostgreSQL on AWS RDS as the system of record. Migrations versioned in git. Row-level tenant isolation enforced at the query layer.
- OpenAI API for the lead-scoring and outreach-copy generation steps. Structured outputs (JSON mode) so downstream code could parse results without regex gymnastics. Prompts versioned in code; behavior reproducible across deploys. OpenAI's documentation covers the structured-output contracts I leaned on.
- Stripe for everything billing — Checkout, Customer Portal, webhook-driven subscription state, retries, dunning, Stripe Tax. Webhooks signed and idempotent. The Stripe billing docs are the reference I send to anyone wiring their first subscription product.
- AWS for hosting — EC2 for the app and worker, RDS for Postgres, S3 for user-uploaded lead lists, CloudFront for static assets, IAM for least-privilege service roles. Deployed into the customer's own AWS account so ownership and billing stayed with them.
- GitHub Actions for CI — type-check, lint, unit tests, Prisma migration check, and a deploy step gated on a manual approval.
The pieces are each individually documented and replaceable. No proprietary lock-in. If the team ever fires me, they walk away with a stack their next developer already knows.
What was the outcome?
The platform shipped to production on schedule. Paying users adopted it. The team continued running and extending the product after my engagement closed — which is the outcome that actually matters for a solo SaaS build, more than any single launch-day vanity metric.
A few qualitative notes that are worth flagging without inventing numbers:
- The AI scoring step reduced the time per lead-list pass from a workday-scale chore to a coffee-break task, according to the team's own internal feedback.
- The dashboard was usable by non-technical staff without a training session — the dropdown-and-filter UI carried the cognitive load that the AI was no longer carrying.
- The Stripe wiring did not leak revenue. Failed payments hit the dunning workflow, recovered most automatically, and the rest reached a human within a day instead of going silent.
- The platform stayed online without me needing to be on call. The runbook plus error monitoring meant the team handled normal operations themselves.
I won't put a revenue figure on this page that I cannot independently verify; the absence of one is not a failure, it's a deliberate constraint on what this case study claims.
What did I learn?
A few things landed harder than I expected.
The boring stack is the right stack when the differentiator is elsewhere. Spending an extra week researching the "best" framework would have bought nothing. SvelteKit + Node + Postgres is good enough for almost any SaaS at the scale of a first product, and the time saved went into the AI pipeline where it actually mattered.
Evaluation harness before prompt iteration, not after. I built the eval set a few weeks later than I should have. Once it existed, every prompt change took ten minutes to validate instead of an hour of manual spot-checking. If I rebuild this, the eval set is week-one work.
Stripe webhook hardening is not optional. Every SaaS I've seen leak revenue leaked it through unsigned, non-idempotent, non-replayable webhook handlers. Doing it correctly the first time saved a class of incidents from ever existing. The pattern is documented well; there is no excuse for not following it.
Document the operational runbook as you build, not at handover. The runbook in the repo grew alongside the features. By the time the handover call happened, the document was already complete because every system had been written down the day it was wired up.
For the methodology context behind how I sequence work like this, see build a SaaS MVP fast and how much does a SaaS MVP cost. For the broader stack reasoning, see best stack for AI SaaS in 2026. The full SaaS MVP service page covers scope and pricing for a similar build, and AI automation covers the OpenAI integration side as a standalone engagement.
Email hello@ignax.dev with a one-paragraph description of your idea if you want to discuss something in this shape. Repository style and commit cadence are visible at github.com/ignaxdev.
Frequently asked questions
What stack did you ship this on?
SvelteKit + TypeScript on the front, Node.js (Express + Prisma) on the back, PostgreSQL as the system of record, OpenAI's API for the lead-scoring and copy-generation steps, Stripe for subscription billing, and AWS (EC2 + RDS + S3 + CloudFront) for infrastructure. Nothing exotic — the boring stack is what let me ship solo without a platform team behind me. Every piece is well-documented and replaceable.
How long did the first production version take?
From signed spec to first paying customer the build ran roughly three months, working solo. The first deployable version landed inside week six; the remainder went into the dashboard polish, billing edge cases, and the AI filtering tuning loop. The slow parts were not the AI calls — they were the Stripe webhook hardening and the multi-tenant data model, which is true for most SaaS work.
Could you build this again for me?
Yes. The architecture pattern travels: SvelteKit + Node + Postgres + a managed LLM API + Stripe is the default I reach for on any AI-first SaaS MVP. Different domain, same scaffolding. If you want a similar product, the [SaaS MVP service](/services/saas-mvp) page has the full scope and pricing band, and the [AI automation service](/services/ai-automation) covers the OpenAI integration side.
How did you handle Stripe billing without a finance team?
Stripe Checkout for the purchase flow, Customer Portal for self-service, webhook-driven subscription state stored in Postgres, signed and idempotent handlers, automatic retries on transient failures, and a dunning workflow that pinged Slack on hard failures. Stripe Tax handled VAT. I documented the entire flow in a repo runbook so the team could debug billing incidents without me on call.
Why OpenAI's API instead of a self-hosted model?
Cost, speed-to-market, and debuggability. A managed API meant I shipped the differentiating feature in days instead of weeks, with no GPU bill and no model-ops drift. Prompts lived in versioned code, never in a no-code tool, so behavior was reproducible. If volume justifies it later, the abstraction layer makes swapping to Anthropic or a self-hosted model a single-file change.
What would you change if you started over?
Two things. First, I would put the lead-scoring evaluation harness in place on day one rather than week eight — every prompt change should be benchmarked against a frozen test set before merging. Second, I would adopt pgvector earlier instead of relational lookups for the de-duplication pass; the migration was clean but should not have been a migration.