Two campaign systems, both live today
Why a broadcast created under /broadcasts never shows up in the in-chat "Broadcast" composer (or vice versa) — two independent systems share the word "campaign" but aren't merged.
This is a real, unresolved product decision — not a bug to route around
Club genuinely runs two separate campaign engines side by side today. Whether and how they should eventually merge is an open decision for the product owner. This page describes both as they currently exist so you know which one you're looking at — it does not pick a "real" one.
The two systems
| The Broadcasts tab | The in-chat "Broadcast" composer mode | |
|---|---|---|
| Backend | app.products.club.core.campaign — its own service/worker/planner package | app.products.club.core.automation ("Track B") |
| Reached at | /api/admin/broadcasts | /api/admin/automation/* |
Its own Campaign model | Table campaigns (products/club/core/campaign/models.py) | Table automation_campaigns, class also named Campaign (app.core.models.automation) |
| Delivery status field | The three-lane off/shadow/live — see Shadow sends | Per-campaign mode (starts 'shadow') plus a soak gate before it can ever promote to live |
| What documents it publicly | Chat & broadcasts and this section's how-tos | Not part of the public API surface |
They are two different tables with the same class name, kept apart on purpose: the codebase's
own comment on the automation-side Campaign model calls out that the two lanes were namespaced
onto separate tables specifically so they could coexist without a schema clash, and flags
unifying them under one system as an explicit open decision the owner hasn't made yet.
What this means day to day
- A campaign built through Broadcasts will never appear in the in-chat "Broadcast" composer's list, and vice versa — they're not two views onto the same data, they're two different tables.
- The composer's own "Started in SHADOW" wording is honest about its own engine's state; it says nothing about whether the other system's account-wide lane is on.
- Auto-messages (welcome / expiring / expired / fan online) are a special case worth knowing:
even though their screen lives under the
/broadcasts/automessagesURL prefix (for permissions reasons — see Set up an automatic message), they're built on the automationCampaignmodel, not the broadcasts one. The URL doesn't tell you which engine you're in; the model backing the endpoint does.
How to tell which one you're looking at
If you're debugging from the API rather than the panel, the surest tell is the router prefix a
call actually hits: /api/admin/broadcasts/* (excluding /automessages) is the campaigns system;
/api/admin/broadcasts/automessages/* and /api/admin/automation/* are both Track B.
What's next
- Shadow sends and the kill switch — the delivery-state model for each of these two systems.
- Send a broadcast campaign — the
/broadcastssystem in practice. - Set up an automatic message — the Track-B system in practice.
Shadow sends and the kill switch
Why a message or campaign comes back "shadow", "deferred", or "stopped" instead of "sent" — and which of several gates is actually holding it back.
PPV composer money bugs
Three composer-layer mistakes that are invisible on screen — dollars not converted to cents, a dropped paywall lock, and a preview id outside the attached media.