Skip to main content

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:

FieldNotes
keysubstack, medium, docs, x, linkedin, instagram, threads, facebook, bluesky
kindpublishing, promo, or both
upload_post_platformupload-post platform id if supported; NULL ⇒ a manual channel
configper-channel defaults — character limits, default hashtags, etc.

The distinction that matters most is auto vs. manual:

  • Auto channels (upload_post_platform is set): approved promos are dispatched by the system through the upload-post API at their scheduled_for time.
  • Manual channels (upload_post_platform is NULL, e.g. Substack): a human publishes and records the resulting URL — nothing is dispatched.

The promo lifecycle

draft ──▶ reviewing ──▶ approved ──▶ published
▲ │
└──────────┘ (request_changes)
  1. Draft. An agent (or person) writes channel-ready copy that respects the channel's limits.
  2. Review. Submit for review; a reviewer approves or requests changes.
  3. Approved. Ready to ship, optionally with a scheduled_for time.
  4. 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.