Channels & promos
A promo is channel-ready copy that amplifies a publication on one destination. When a publication reaches promoting, it fans out into promos — one per target channel — and each runs its own mini-pipeline.
Channels
Channels are a seeded lookup table (not user-created in Phase 1). Each channel declares how it publishes:
| Field | Notes |
|---|---|
key | substack, medium, docs, x, linkedin, instagram, threads, facebook, bluesky |
kind | publishing, promo, or both |
upload_post_platform | upload-post platform id if supported; NULL ⇒ a manual channel |
config | per-channel defaults — character limits, default hashtags, etc. |
The distinction that matters most is auto vs. manual:
- Auto channels (
upload_post_platformis set): approved promos are dispatched by the system through the upload-post API at theirscheduled_fortime. - Manual channels (
upload_post_platformisNULL, e.g. Substack): a human publishes and records the resulting URL — nothing is dispatched.
The promo lifecycle
draft ──▶ reviewing ──▶ approved ──▶ published
▲ │
└──────────┘ (request_changes)
- Draft. An agent (or person) writes channel-ready copy that respects the channel's limits.
- Review. Submit for review; a reviewer approves or requests changes.
- Approved. Ready to ship, optionally with a
scheduled_fortime. - Published. Auto channels dispatch via upload-post and record the
external_url; manual channels are marked published by hand.
Dispatch never lies
If an upload-post dispatch fails, the promo stays in approved with the error captured in its dispatch JSON — it's retried or fixed manually, never silently marked published.
Media
Images live in Supabase Storage and are linked to publications and promos through join tables (publication_media, promo_media) with a position — so the same asset can appear in an article and several promos.