evinjohnn/campaign: GitHub as a promo control plane

A tiny JSON-driven repo decides which campaign appears, who sees it, and when it disappears, without shipping new app code.

8 min read · evinjohnn/campaign

A small repository file sits at the center of a network of wires that feed a mobile screen, a web banner, and a checkout panel. The image explains how one checked-in JSON file can act like a control plane for promotion logic across multiple surfaces.
One file, many surfaces, one decision layer.
Key Takeaways

The marketing stack that fits in a repo

The surprising part is not that this repo stores campaign copy in JSON. It is that the file becomes the control plane. One static URL can decide what message appears, which users qualify, and when the promo goes dark.

That is GitHub-as-remote-config for marketing. Promotion logic stops being a special case buried in app code and becomes data that can be reviewed, versioned, and rolled back like any other change.

Inside campaigns.json

The schema is simple enough to read at a glance, but each field does real work. priority breaks ties, targeting filters by device or subscriber state, and expires_at acts like a built-in kill switch.

Once those rules exist, the client does not need a dashboard logic stack. It only needs to fetch the file, sort the active records, and render the best match.

{
  "active_campaigns": [
    {
      "id": "early-adopter-pricing",
      "priority": 100,
      "title": "Last Chance",
      "message": "Early adopter pricing ends in 24 hours.",
      "cta_text": "Claim the discount",
      "icon": "bolt",
      "targeting": {
        "os": "all",
        "requires_premium": false
      },
      "expires_at": "2026-12-31"
    }
  ]
}

Git-backed config becomes a runtime selector before the app ever paints a banner.

Why Git beats hardcoded promos

Hardcoding a promo banner looks simple until the deadline changes. Then the team is waiting on a code merge, a test pass, and a deployment, all for copy and timing that should have been mutable.

ApproachWhat changes fastTrade-off
Hardcoded in-app bannerAlmost nothing, until a new build shipsEvery edit rides the app release train
Git-backed JSON control planeCopy, eligibility, and sunset datesThe client must evaluate a tiny schema
Full marketing platformSegments, journeys, and analyticsMore power, more UI, more operational weight

The middle option is the sweet spot. It is small enough to live in a repo, but strong enough to separate promotion decisions from the app binary.

A close-up of campaign cards passing through a mechanical sorter. One expired card is cut away, one low-priority card is diverted, and one winning card falls into the output tray, illustrating how the repo filters a feed before the app renders anything.
A tiny rules engine can do the work of a much larger stack.

What the repo buys a solo maintainer

The shape of the repo points to the maintainer reality. This is what a solo operator or small team builds when they want versioned campaigns, a tiny deployment surface, and no extra admin console to keep alive.

The Git history becomes the audit trail. A campaign can be added, changed, or rolled back with the same muscle memory as any other code change, which is exactly why this feels more like infrastructure than copywriting.

The trade-off line

The narrowness is the point. This repo is not trying to replace a full marketing suite, and that restraint makes it useful. It handles one job well: deciding what promo should exist, for whom, and until when.

If a team eventually needs richer segmentation, experiments, or analytics, those belong in a different layer. But for a lightweight product that just needs remote control over promotion, this is the right size of solution.