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.
- campaigns.json turns promotion into remote config, so the product can change what users see without an app redeploy.
- Priority, targeting, and expiry are enough to make a static file behave like a runtime decision engine.
- The repo wins by staying narrow: Git history, low overhead, and fast edits replace a heavier marketing stack.
- Its limits are intentional, because deeper segmentation and analytics would add complexity the project is trying to avoid.
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"
}
]
}
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.
| Approach | What changes fast | Trade-off |
|---|---|---|
| Hardcoded in-app banner | Almost nothing, until a new build ships | Every edit rides the app release train |
| Git-backed JSON control plane | Copy, eligibility, and sunset dates | The client must evaluate a tiny schema |
| Full marketing platform | Segments, journeys, and analytics | More 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.
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.