All work
AI SaaS Platform

AdCraft AI: Ad Research & Generation SaaS

Built a production AI SaaS that researches products across Reddit, TikTok, Google, and YouTube, then generates buyer personas, platform-specific ad copy, and ad imagery, with Stripe billing and full observability.

Sector
AI SaaS Platform
Timeline
In production
Role
Lead Systems Architect
  • Next.js 14
  • Turborepo
  • Anthropic Claude API
  • Supabase
  • BullMQ
  • Cloudflare R2
  • Stripe
  • Playwright
  • Sentry
  • PostHog
AdCraft AI: Ad Research & Generation SaaS
6 platforms
Research Surfaces

Reddit, Google, TikTok, Facebook, Instagram, and YouTube

3 workers
Scraper Pool

Bounded headless Chromium pool, throughput without memory collapse

Subs + top-ups
Billing

Stripe subscriptions alongside one-time credit purchases

The problem

Ad creative starts with research nobody wants to do: reading what actual buyers say across half a dozen platforms, turning that into personas, then writing platform-specific copy against each one. Doing it properly takes days; skipping it produces the generic creative that fails.

The solution

Built a SaaS where a user pastes product URLs and the system runs a multi-stage research and generation pipeline, scraping across platforms, then chaining Claude models to produce personas, copy, and images. No ad account connections: users download the output and take it to their own ad manager.

Approach

How it was built.

01

Tier the Models to the Task

The generation pipeline is a chain, and not every link needs the same model. The cheap, high-volume first chain runs on Claude Haiku; the chains that produce personas and final copy run on Sonnet. Cost tracks the work actually being done rather than the most expensive step in the chain.

02

Queue Everything Slow

Scraping and generation are minutes-long, failure-prone operations, so they run as BullMQ jobs against Upstash Redis with a bounded three-worker Playwright pool. The web request returns immediately and the browser subscribes to progress rather than holding a connection open.

03

Stream Progress, Don't Poll

Job progress is published to Redis and fanned out to the browser over Server-Sent Events, so a multi-minute generation feels observable instead of frozen, and a reconnecting client resumes from the current state.

Technical detail

The parts worth explaining.

Image Provider Behind an Interface

Image generation sits behind an ImageService interface with OpenAI GPT Image and fal.ai Flux implementations. Model quality and pricing in this space move monthly, the abstraction means switching providers is a config change, not a refactor.

SSE Over Redis Pub/Sub

Workers publish progress to Redis channels; the Next.js route handler subscribes and forwards to the client as SSE. Any worker on any container can drive any user's progress stream without the two ever knowing about each other.

redis.subscribe(`job:${jobId}`, (msg) =>
  controller.enqueue(`data: ${msg}\n\n`)
);

Observability From Day One

Sentry for errors and PostHog for product analytics were wired in before launch rather than after the first incident, in a pipeline this asynchronous, a failure with no trace is a failure you will never reproduce.

What I took from it
  • Model selection is an architectural decision, not a prompt detail; tier it per chain
  • Anything measured in minutes belongs in a queue, and anything in a queue needs a progress channel
  • Abstract the fastest-moving vendor in your stack; in AI products that is the image model
Where it goes next
  • Expand the research corpus to additional social surfaces
  • Team workspaces with shared brand and asset libraries
  • Creative performance feedback loop from user-reported results

Got a problem shaped like this?

If something here looks like what you're facing, tell me about it. The first conversation is an architecture discussion, not a sales call.