Case Study 08 · Marketing Operations
An internal web app that writes Meta ad copy from four live sources instead of memory: the business's own ad performance data, what customers actually say in chat and call transcripts, current web trends, and what competitors are running. Designed, built, deployed and in daily use in one working day.
The Problem
The business had run thousands of Meta ads across several education programmes, split between two creative formats: click-to-WhatsApp ads that route a prospect into a conversation with a sales closer, and lead-generation ads built around a keyword tile. Ad copy was written by hand in a general-purpose chat model, one prompt at a time, from memory of what had worked before.
That process worked, but three things were consistently missing. The model never saw the account's real performance data, so it had no idea which ads were actually converting or which angles had already been exhausted. It never saw what customers were actually worried about, since that context was locked inside thousands of chat threads and hundreds of call recordings nobody had time to read. And every batch started from scratch, with no memory of the last one.
Before
After
The Data Foundation
Before writing any application logic, every ad the account had ever run was exported from the Meta Marketing API with full lifetime metrics, then aggregated per creative. This became the ground truth for the winners library and the style examples the model would learn from, rather than starting from generic marketing advice.
| Data pulled | Used for |
|---|---|
| Every ad with lifetime performance metrics | Winners and losers library, what is working right now |
| Every unique creative, deduplicated | Copy examples the model learns structure and specificity from |
| Highest-spend creatives, carrying the large majority of total spend | Proven, high-confidence patterns to brief every batch on |
| Existing keyword tile creatives | Style reference and duplicate detection for new tiles |
| Programme codes with topic banks | Structured tile generation per programme |
On top of this sits a keyword and pain-point master document, the full ad copy history, and a brand rulebook that applies to every line of copy the app produces: no em dashes, regional English conventions, a banned word list, and correct programme naming throughout.
What Was Built
The app is a single web page served by a Cloudflare Worker, with Claude doing the writing and Cloudflare KV storing everything shared across the team. Three tabs cover the full workflow.
Pick a lead type, tick the hooks and pain points to cover, choose how many. Produces primary text, headline, call to action, and the correct routing template for each ad. Every generated ad is required to ask the customer's key qualifying detail and to reinforce a core trust signal, enforced automatically rather than left to memory.
Pick a programme and a pain family. Produces the keyword tile, capitalised, a fixed number of parts, under a strict character limit, ending in a consistent call to action, plus the matching primary text. Every tile is checked against every tile already running, to prevent an accidental duplicate going live.
A web trend scan, a competitor scan of the public ad library, the customer pain-point database, the last month of the account's own results, and the winners ranking. Results and winners are split by ad format so the two are never compared against each other. Anything selected here is written directly into the brief for the next batch.
Every batch carries controls beyond the tab itself: creative type (keyword tile, an AI image prompt, or both), a pain and gap intensity dial from 1 to 5, a freedom dial from strict template to deliberate outlier, toggles for which research feeds should shape the brief, batch size, and a "feeling lucky" mode that picks a random lead type, a current trend, and a real customer theme with no brief required at all.
Quality and Workflow
Every generated ad passes through a rule checker before it can be copied out: em dash detection, banned words, missing required qualifiers, character limits, emoji where none belong, duplicate tiles, and missing calls to action. Each card carries a revise box with quick preset chips, an undo, and a "more like this" action that produces six fresh variants of any card.
A background job runs every morning without anyone pressing anything: it pulls the last month of performance data, rebuilds the winners library from the recent window, and reads new chat and call transcripts into the pain-point database. Whoever generates a batch that day is briefed on the previous week's results automatically.
The Pain-Point Database
Customer pain points and questions are the best source of ad angles available, but they were locked inside thousands of chat threads and hundreds of call recordings. A manual export would work once and go stale immediately. Building an automatic pipeline hit three real constraints at once: an outbound request cap per invocation on the hosting platform's free tier, a call platform that does not retain transcripts indefinitely, and rate limits on every API involved.
The solution was a two-phase pipeline that runs in short, resumable steps. Each step pulls a bounded batch of conversations into a queue, saves its position, then hands the queue to the model for extraction. Only the customer's side of each conversation is kept. Every line is tagged by kind (pain, desire, question, objection), theme, and programme, deduplicated, and stored. Raw call transcripts are saved the moment they are fetched, so they survive even after the call platform later drops them. A cursor is saved per conversation so nothing is ever read twice, and the process resumes cleanly if interrupted.
The backfill extracted well over a thousand customer-voice lines within its first working session, climbing daily. Once caught up, the database tops up every morning, and every ad brief now carries the most common customer worries for the relevant programme, plus a handful of rare ones flagged explicitly as angles nobody else is running.
Problems Solved Along the Way
The build surfaced fifteen distinct problems, from API access and hosting through to copy rules and team workflow. A representative selection, each with what went wrong and what closed it for good.
The model had no idea which past ads had worked or which angles were already burnt. Fixed by building a winners library (high result count at the lowest cost per result) and a losers library (high spend for very few results), both written into every brief with the actual copy attached, rebuilding itself every morning.
The first version ran inside a chat interface and generated fine. Moving it standalone hit a concurrency error, since a consumer subscription and API access are separate services with separate billing. Fixed with a proper API key held server-side, retry with backoff on rate limits, and batches split into smaller passes so one failure never loses the whole set.
Long-running actions were working but reporting progress to a status line hidden on the wrong tab. Replaced with a visible toast in the corner on every tab: a spinner and live message while something runs, buttons disabled until it finishes, and results held on screen afterwards.
A platform version upgrade deprecated the batch lookup the library relied on to fetch ad copy, and a scheduled refresh quietly returned zero winners with no obvious cause. Fixed by rewriting the fetch through the supported paginated route and pinning the API version explicitly, so the next deprecation cannot cause a silent failure again.
A new integration returned a generic, unhelpful error on its first run. Rather than guess, a step-by-step test tool was built that checks each stage of the connection in sequence. It isolated the exact call causing the failure in one click, after an hour of guessing had found nothing.
Team feedback after the first batches: descriptions varied but everything sat at the same intensity, and the model followed the rules so closely nothing surprising came out. Fixed with a pain-intensity dial and a freedom dial, each on a defined 1 to 5 scale, plus a one-click "make this winner odder" action, turning variation into a control rather than a lottery.
Where It Stands
Live and password protected
Deployed behind a login covering the entire app, not just the API, in daily use by the team that used to write every ad by hand.
Self-refreshing every morning
Performance data, the winners library, and the customer pain-point database all update on an unattended daily schedule, no manual trigger required.
Shared, not personal
Every completed batch is kept automatically and visible to the whole team, stamped with who made it, so nothing generated is invisible to colleagues.
What The Build Taught
The winners library and the existing tile set gave the model a floor to stand on that no amount of prompt engineering could replace.
A copy-rule fix that would have meant re-briefing a human took minutes once the rule lived in one place and was checked automatically on every batch.
Two of the fifteen problems were the tool working correctly while looking completely dead. A visible progress signal would have prevented both.
A pipeline that can be interrupted and resumed without losing its place is a pipeline people will actually run against a real backlog.
Heavier verification tiers exist for reading other people's data. Reading your own business's own channel typically needs a much lighter access tier.
A dedicated step-by-step connection test found a real fault in one click, after significant time spent guessing had found nothing.
Variation has to be a dial, not a hope. Left alone, a model briefed heavily on winners will converge back onto the winners. Making intensity and freedom explicit controls, on a defined scale, is what kept the output from flattening into a single template.
Technical Stack
| Component | Technology | Purpose | Cost |
|---|---|---|---|
| Application and hosting | Cloudflare Worker with static assets | Serves the page, proxies the model API, talks to every external source, runs the daily job | Free |
| Shared storage | Cloudflare KV | Batches, saved research, winners library, pain-point database, all shared across the team | Free |
| Copywriting | Claude API | Every batch and revision, briefed on the winners library, research feeds and rule set | Per use |
| Ad performance data | Meta Marketing API | Lifetime and recent performance, the winners and losers library, creative thumbnails | Existing |
| Customer voice extraction | Chat and call transcript APIs | Pain point, desire, question and objection mining, resumable and deduplicated | Existing |
| Scheduling | Cron trigger, daily | Unattended morning refresh of performance data, winners library and pain-point database | Free |
Result
What used to be one person's manual workflow, opening a chat tool, pasting a brief, iterating from memory, is now a shared app any team member can run. Every batch is briefed on real performance data, real customer language, current trends, and competitor activity, checked automatically against house copy rules before it can leave the tool, and visible to the whole team the moment it is generated.
The build surfaced fifteen distinct real problems along the way, from API access and hosting through to copy rules and team workflow, each documented with what went wrong, what was done about it, and where it stands, so anyone on the team can pick it up, understand every decision, and extend it.
The pattern generalises well beyond ad copy: any creative workflow shaped like "brief the model on our own results, our own customers' language, and current context, then check the output against our own rules before a human ever sees it" can be assembled from the same parts.