Back to HTML Pub

40 Ad Groups, 3 Landing Pages: A Claude + MCP Pipeline for Real Message Match

Michael Sacca
AI-Native Publishing
Landing Pages
Paid Traffic

Every paid account runs on a compromise nobody says out loud. The account has forty ad groups. The account has three landing pages.

Everyone on the team can explain why each group deserves its own page. The keyword intent is different. The promise in the ad is different. The person clicking is different. Nobody builds the forty pages, because pages are made by hand and hands are slow. So all forty groups point at the three closest matches, and the generic page absorbs the difference.

The name for the difference is message match: the first headline on the page repeats the promise the ad just made. When it repeats, the click continues. When it doesn't, the visitor has to figure out all over again why they clicked, and most of them don't bother. Ad platforms also price clicks partly on how relevant the destination page is to the ad, so the mismatch gets paid for twice — once in the conversion rate, once in the auction.

The tutorials stop one page too early

Every "build a landing page with AI" walkthrough ends the same way: one prompt, one page, a screenshot. One page was never the paid problem. One page is what you already have.

The paid problem is forty pages, and forty pages introduce a failure mode a single page never faces: drift. Ask a model for one landing page and you can review it in five minutes. Ask it for forty and a few will invent a claim you never approved, wander off the voice, or quietly rewrite the ad's promise into something more generic. Volume is where AI-generated pages go bad — not because the model got worse, but because your review capacity didn't scale with your output.

So the deliverable isn't a page. It's a pipeline: ad copy and keyword intent in, brand-locked page out, published, checked. Four stages, all automatable, one human pass at the end.

The pipeline

1. The ad group is the brief

Everything the page needs to know is already in the ad group. Export it — keywords, headlines, descriptions, the offer — and treat that export as the spec the page gets compiled from:

{
  "ad_group": "bookkeeping-for-therapists",
  "intent": "vertical solution",
  "keywords": [
    "therapist bookkeeping",
    "bookkeeping for private practice",
    "therapist small business accounting"
  ],
  "ad_headlines": [
    "Bookkeeping for Therapists",
    "Your Practice's Books, Done Monthly",
    "Free 20-Minute Consult"
  ],
  "offer": "Free 20-minute consult",
  "slug": "bookkeeping-for-therapists"
}

The primary headline is the most important field in the file. It is the promise the click was made on, and the page exists to keep it.

2. Lock the brand before you generate anything

Forty variants stay on-brand only if "the brand" is a file, not a feeling. Write it once, and every generation run reads it:

{
  "product": "Ledgerly",
  "voice": ["plain", "second person", "short sentences", "no exclamation marks"],
  "approved_claims": [
    "Built for service businesses",
    "Flat monthly pricing",
    "Books closed by the 10th"
  ],
  "banned_phrases": ["revolutionary", "guaranteed results", "best-in-class", "seamless"],
  "tokens": {
    "primary_color": "#1D4ED8",
    "font": "Inter",
    "cta_style": "solid button, sentence case"
  },
  "cta_rules": "One CTA per page. It must be the offer from the ad, worded the way the ad worded it."
}

The rule is simple: the model may rearrange the brand, it may never invent the brand. If a proof point isn't in approved_claims, it doesn't exist.

3. Compile the variant

Now the generation prompt. Notice how little of it is creative direction and how much of it is constraint:

You are generating one landing page variant.

INPUTS
- Ad group export (attached): keywords, ad headlines, offer.
- Brand spec (attached): voice, approved claims, banned phrases, design tokens.

HARD RULES
1. The page H1 repeats the ad group's primary headline. Near-verbatim. No improvements.
2. The subhead speaks to the dominant keyword intent, in the brand voice.
3. Use only claims from approved_claims. If a section needs a proof point that
   isn't in the list, leave the section out.
4. One CTA. It is the offer from the ad. Nothing else on the page asks for anything.
5. Output one self-contained HTML file using the design tokens. No external dependencies.

You are not writing a new page. You are compiling this ad group into a page.

That last line does the real work. Generation framed as compilation is what keeps forty variants consistent. A writer riffs; a compiler repeats. For paid traffic, repetition is the feature — the visitor clicked a specific promise, and the page's job is to say it back, not to say something better.

4. Publish through MCP, not through a person

This is where the argument from MCP started as a tool-use protocol. It became the publishing protocol when nobody was looking. stops being theory. The page isn't done when the HTML exists; it's done when it has a URL. One tool call per variant:

{
  "tool": "publish_page",
  "arguments": {
    "title": "Bookkeeping for Therapists — Ledgerly",
    "slug": "bookkeeping-for-therapists",
    "html": "<!doctype html>..."
  }
}

The response is a live URL. That URL becomes the ad group's final URL. Point a loop at a folder of forty exports and forty pages go live without anyone opening an editor, a CMS, or a deploy queue. This is the publish layer we've described in The Publish Button Your AI Was Missing — except here it isn't publishing one page, it's publishing a matrix of them.

The QA pass is the whole job

Generate forty of anything and some percentage drifts. There are two drift types, and they fail differently:

  • Off-message. The H1 stops repeating the ad. The page still looks fine, reads fine, and quietly breaks the one thing it existed to do.
  • Off-brand. A claim appears that legal never approved. The voice picks up exclamation marks. "Seamless" sneaks in.

Everything mechanical is a string check. Run it on every page, every run:

def qa_page(html, ad_group, brand):
    issues = []

    h1 = extract_h1(html)
    if ad_group["ad_headlines"][0].lower() not in h1.lower():
        issues.append("message drift: H1 lost the ad's headline")

    body = strip_tags(html).lower()
    for phrase in brand["banned_phrases"]:
        if phrase in body:
            issues.append(f"banned phrase present: {phrase}")

    if ad_group["offer"].lower() not in body:
        issues.append("offer mismatch: the ad's offer never appears")

    return issues

One check can't be a string match: the claims audit. That one is a model pass — hand the page text and the approved_claims list to Claude and ask for every claim on the page that isn't on the list. Deterministic checks catch mechanical drift; the model pass catches inventive drift. Models are good at finding claims. It's what they do when you wish they wouldn't.

Then, and only then, the human pass: skim the flagged pages plus the five highest-spend groups, whatever the flags say. That's the right shape for human attention here — you review exceptions and exposure, not forty pages. QA is what turns "forty variants" from a liability into a checked set.

The page is downstream of the ad

Once the pipeline exists, the operating model inverts, and this is the part that doesn't show up in a per-page cost comparison.

When the ad copy changes — and RSA winners rotate constantly — you don't edit the page. You re-run the pipeline for that ad group and the page recompiles from the new brief. The page is a build artifact of the ad, not an asset someone owns. When a group gets paused, its page comes down in the same motion. Nobody maintains forty pages, because nobody maintains pages at all. They maintain the spec and the brand file, and everything else is output.

And once the marginal cost of a variant is a tool call, the old question flips. It stops being "can we afford a page per ad group?" and becomes "why is any group still pointing at a generic page?" There is no longer an answer to that one.

There's a second benefit that has nothing to do with Quality-Score-style pricing. A page that repeats a specific promise to a specific searcher is the same bet this blog has made about specificity everywhere else — real intent, real wording, no hedging — the argument in Specificity Beats Sophistication: Why Generic AI Pages Are About To Lose The Search Results, applied to the traffic you're paying for instead of the traffic you're earning.

Forty ad groups and forty pages isn't more work. It's the same account, finally matched to itself — one promise per click, kept all the way to the button.

Keep Reading