{"slug": "a-reddit-campaign-with-a-hero-image-in-one-conversation", "title": "A Reddit campaign with a hero image, in one conversation", "summary": "Growomat's hosted MCP connector enabled a Reddit campaign to be built from a single prompt, with six tool calls creating a paused image post ad targeted at the mcp, ClaudeAI, and SaaS communities, scoped to reddit-ads only. The campaign was validated with one degraded finding, and the design ensures drafts are editable and disconnected until synced.", "body_md": "# A Reddit campaign with a hero image, in one conversation\n\nOne prompt, six tool calls and a paused Reddit image ad — plus the promoted post we shipped that read like an ad, and the change that stopped the next one.\n\n**Our last Reddit post ended on a claim we should have shown rather than stated:\nthat shipping a campaign is now one conversation. So here is the conversation.**\n\nThe thing being built is a promoted **image post** — a title, a picture, a\nbutton — pointed at one landing page, in three communities. The picture is the\npart most people skip, and it is the part Reddit gives the most room to, so it\ngets its own name here: the **hero**.\n\nEverything below runs through Growomat's hosted MCP connector, which exposes the same public API our own dashboard uses as a set of tools. Any MCP client can drive it; we use Claude. Nothing in the flow is Reddit-specific until the very last step, which is rather the point.\n\n## The prompt\n\nThis is the whole input. It is worth reading closely, because almost every line of it exists to close a decision that would otherwise be made for you:\n\n```\nBuild a Reddit campaign for growomat.com/run-ads-from-claude, and stop\nbefore anything reaches Reddit.\n\nWho I want: someone who already builds with MCP servers and already\nspends money on ads. Not a beginner audience.\n\nCampaign \"Reddit · MCP launch\", daily budget <your number>, maximize\nclicks, and scope it to reddit-ads only — I do not want this on any\nother platform.\n\nAd group: communities mcp, ClaudeAI, SaaS. Bare names, no r/ prefix.\n\nAd: an image post. Use the hero I uploaded as the marketing image, call\nto action \"Learn More\". Write the title yourself — sentence case,\n60 to 120 characters, something a person would actually post.\n\nThen validate the export and show me every finding. Do not sync.\n```\n\nTwo of those lines are load-bearing in a way that is not obvious.\n\n**\"Scope it to reddit-ads only.\"** A campaign carries a per-platform budget\nsplit, and **absent is not zero**. A campaign with no split exports to every\nconnected platform at the *full* budget, so a campaign you thought of as one\nnumber becomes that number on Google Ads, on Microsoft Advertising, on Reddit\nAds and on Meta Ads at the same time. It is the single most expensive default in\nthe product, and the only defence is saying which platform you meant.\n\n**\"Bare names, no r/ prefix.\"** Reddit accepts `[\"mcp\", \"ClaudeAI\"]`\n\nand refuses\n`[\"r/mcp\", \"r/ClaudeAI\"]`\n\nwith `400 You cannot set invalid communities`\n\n. We\nlearned that one the way everybody learns it.\n\n## What came back\n\nSix calls, in order, each one returning the object it created:\n\n```\ncreate_campaign     → PAUSED · budget scoped to reddit-ads only\ncreate_ad_group     → communities: [mcp, ClaudeAI, SaaS]\ncreate_asset        → IMAGE · 1200×628 · registered by URL\ncreate_ad           → postType: \"IMAGE\" · cta: \"Learn More\"\nattach_asset_to_ad  → scope: AD · role: MARKETING_IMAGE\nvalidate_export     → valid: true · 1 finding (degraded)\n```\n\nEverything a platform has and the shared model does not — the communities, the post type, the call to action — rides in one per-platform extension map on the object it belongs to, keyed by platform id. Nothing above branches on Reddit until the adapter does.\n\nThe campaign now exists in the product, complete, editable, and connected to nothing. That last property is the design: a draft that costs nothing to throw away is a draft you will actually redo.\n\n## An image post has nowhere to put your body copy\n\nA promoted post is a *post*, and Reddit's post types are not interchangeable\nskins over the same fields. They carry different things.\n\nAn **image post** is a title and a picture. There is no body. Whatever argument\nyou wanted to make has to survive in one sentence above the image, and the\nclick comes from a call-to-action button attached to the creative.\n\nA **text post** is the mirror image. It takes as much body as you like, and it\ngets no button at all — Reddit refuses a click URL on a free-form post. Send one\nanyway and you have published an ad that points nowhere. The honest best a text\nad can do is carry its own link inside the body as a named markdown link, which\nis what ours now does; the click becomes a tap on a link in the text, and the\ntracking survives.\n\nBecause the two formats destroy different halves of your copy, the format has to\nbe a *choice*, not a guess. Ours used to be inferred — if an image was attached,\nyou got an image post — and that inference quietly deleted the post text of\neveryone who attached a picture to a piece of writing. It is a field now\n(`postType`\n\n), and the old inference only survives for campaigns authored before\nthe field existed, so nothing changed underneath anybody.\n\nThe finding in the trace above comes from the same place. Declare an image post, forget the picture, and validation says so:\n\n```\nREDDIT_DECLARED_IMAGE_NO_MEDIA  ·  degraded  ·  _native.postType\n\"This ad is set to run as an image post, but no image reached it.\n It will export as a text post.\"\n```\n\nWarned, not blocked. The ad does export — what is lost is the shape you asked for, and a validator that can name that is more use than one that refuses.\n\n## The hero has one job\n\nThe image rides on the ad as a `MARKETING_IMAGE`\n\n: landscape, 1.91:1, 1200×628.\nAttach one at a different ratio and the square and portrait variants can be\nderived from it by cropping; the reverse does not work, so the landscape one is\nthe one to have.\n\nThere is no separate upload step. Reddit consumes media *inside* the post\ncreation job, by URL, so registering the asset is genuinely just handing over a\nlink and letting the sync fetch the bytes when it needs them.\n\nWhat we ask an image for has narrowed a lot with practice:\n\n```\nA single subject, photographed or rendered, filling a 1.91:1 frame.\nNo text in the image — the title carries every word, and Reddit\ncrops and compresses the picture on mobile until small type is\nunreadable anyway.\nNo logo lock-up, no badge, no border, no stock handshake.\nIt should look like something a person would have posted, and it\nshould still read at thumbnail size in a scrolling feed.\n```\n\nThat last line is the whole brief. A feed is a scroll, and the hero's job is to survive it — not to explain the product, which is what the title is for.\n\n## How we got ours great\n\nHere is the first promoted post this product ever produced, in full. The title was:\n\nIntegrate MCP Easily\n\nTwenty characters, in a slot that takes three hundred. Title Case. Below it, a body assembled out of search-ad description fragments, because that is what the product had lying around. The verdict, from the person whose money it was, was that the Reddit copy was crap.\n\nThey were right, and it was not the model's fault. Nothing had ever told it what a Reddit post is. It was handed the slots of a search ad — three headlines under thirty characters, two descriptions under ninety — and asked to fill them, and it filled them correctly. Then a translator moved the results into a post, where \"correct\" reads as an advert wearing a costume.\n\nSo the fix was not a better model or a longer prompt. It was telling the drafting step what platform it was writing for — and putting that where it cannot drift, which is the same file the platform's field limits live in:\n\n```\ncopyStyle: {                              // abridged from the file\n  post: {\n    persona: 'a Reddit-native copywriter who writes the way'\n      + ' redditors actually post',\n    rules: [\n      'Write like a person telling other people something'\n      + ' useful, not like a brand announcing itself.',\n      'Ground every specific in the business context given'\n      + ' below — the website, the audience, the value props.',\n      'Plain language, short sentences, no corporate register.',\n      'Never reuse a search headline as a post title, and'\n      + ' never assemble the body out of search fragments.',\n    ],\n    avoid: [\n      'hashtags', 'ALL CAPS', 'emoji spam',\n      'marketing superlatives (\"revolutionary\", \"best-in-class\")',\n      'Title-Case Keyword Slabs', 'exclamation-mark spam',\n      'anything that reads like an ad wrote it',\n    ],\n    slots: {\n      postTitle: { targetLength: { min: 60, max: 120 }, … },\n      postBody:  { targetLength: { min: 400, max: 900 }, … },\n    },\n  },\n},\n```\n\nThree things about that block are deliberate.\n\n**The target lengths are not the limits.** Reddit's title cap is 300 characters\nand its body cap is far higher than anything we would ever write; those numbers\nare enforced elsewhere, as truncation rules. The 60–120 band is a *quality*\ntarget, and it exists because a 20-character title in a 300-character slot is how\na reader can tell, at a glance and without reading it, that a brand wrote this.\n\n**The avoid list is specific enough to fail.** \"Be authentic\" is not an\ninstruction. \"Never assemble the body out of search description fragments\" names\nthe exact thing that went wrong, so it can be checked.\n\n**It lives in the platform's capability manifest.** Each platform we support\ndeclares what it can do in one versioned file — its field limits, its bidding\nstrategies, its accepted call-to-action values, and now how its copy should\nread. The drafting step reads that file rather than knowing about Reddit, which\nis why the next platform is a manifest rather than a fork of the copy code. And\nbecause a manifest that claims something its adapter cannot do fails our\nconformance suite, that file is checked by CI rather than by hope.\n\n## Nothing goes live until you say so\n\nEverything to this point has touched no ad platform at all. Publishing is two more calls, and both of them are gates:\n\n```\npreview_sync_plan(campaign_ids: […], platforms: [\"reddit-ads\"])\n    → what would be created, updated or removed · planHash\n\ntrigger_sync(campaign_ids: […], plan_hash: \"<that hash>\")\n    → started · poll get_sync_status until terminal\n```\n\nPassing the hash back is what turns \"preview then push\" into a real safety\ngate: if anything changed between the two calls, the sync fails with\n`SYNC_PLAN_DRIFT`\n\nrather than pushing a plan you never saw. The scope behaves the\nsame way — a plan that would touch a campaign outside the ids you named is\nrejected, not silently widened.\n\nWhat lands on Reddit is paused. You look at it in Reddit's own interface, with\nits own preview, and you enable it there or you go back and edit. And then read\nthe delivery status rather than the response code, because the most expensive\nlesson from our own integration was that a `200`\n\nmeans Reddit accepted your\nrequest, not that anything is running.\n\nOne more thing that is part of the creative, not separate from it: **leave the\ncomments on and answer them.** Comments are Reddit's trust surface. An ignored\nthread under a promoted post reads as astroturf, and it is the cheapest\nqualitative feedback any channel will ever give you.\n\n## What our own flight actually bought\n\nThe honest part. Our first Reddit flight — nine product-led ads, run through this same pipeline — bought 23,204 impressions and 184 clicks at a 0.79% click-through rate. Traffic metrics a lot of people would call healthy. Signups from it: none we could attribute.\n\nThe finding was not really about the creative. It was that we had waived our own \"measurement before spend\" rule to get the flight out, so the platform could never bid toward a signup and we could never join a click to one. Cheap clicks that buy nothing look exactly like cheap clicks that buy something, right up until you try to read the number that matters. We re-cut the creative toward content rather than product hooks, and then paused the whole thing during a repositioning.\n\nSo: the copy bar stopped us shipping something embarrassing. It did not make the channel work. Those two get confused constantly, and keeping them apart is most of what running a channel honestly consists of.\n\n## If you are doing this yourself\n\n**Say which platform the budget is for.** Absent is not zero, in our product and in most others.**Pick the post format explicitly**, and know what it costs you: an image post has no body, a text post has no button.** Give the hero one job**— survive a scrolling feed at 1.91:1 — and put every word in the title.** Write the platform's copy rules down where the code reads them**, not in a prompt you retype. A rule that is not in the pipeline is a rule you will forget on a Friday.** Validate, preview, then push, and read the delivery status afterwards.**A success response is a claim about your request, not about your campaign.\n\n## Run all four platforms from one place.\n\nBuild, optimize and report on Google, Microsoft, Reddit and Meta campaigns without an agency retainer.\n\n[See what it costs](/pricing)\n\nRead next\n\nengineering\n\n[Why the Reddit Ads API was harder than it looks](/blog/reddit-ads-api-integration-lessons)\n\nWe budgeted a day to integrate Reddit Ads and it took two weeks. Here are the refusals we hit, why our tests never caught them, and how it ends.\n\n[How to create beautiful user-centric products with AI](/blog/harness-ai-to-build-user-centric-products)\n\nThe models are the easy part. What keeps a product user-centric is the harness around them, from principles and stories to prototypes, tests and gates.", "url": "https://wpnews.pro/news/a-reddit-campaign-with-a-hero-image-in-one-conversation", "canonical_source": "https://growomat.com/blog/reddit-image-ad-in-one-conversation", "published_at": "2026-08-22 00:00:00+00:00", "updated_at": "2026-08-22 14:12:42.350999+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "ai-agents"], "entities": ["Growomat", "Reddit", "Claude", "MCP"], "alternates": {"html": "https://wpnews.pro/news/a-reddit-campaign-with-a-hero-image-in-one-conversation", "markdown": "https://wpnews.pro/news/a-reddit-campaign-with-a-hero-image-in-one-conversation.md", "text": "https://wpnews.pro/news/a-reddit-campaign-with-a-hero-image-in-one-conversation.txt", "jsonld": "https://wpnews.pro/news/a-reddit-campaign-with-a-hero-image-in-one-conversation.jsonld"}}