Skip to main content
๐Ÿ• ~6 min read

The publishing pipeline

Every idea in Contentpipe travels the same route โ€” from a one-line stub to a live article, spawning channel promos along the way. Here's how each status works and who moves it forward.

A publication is a single article. It carries exactly one status at a time, and that status determines what actions humans and agents are allowed to take on it. Statuses only ever move forward โ€” except reopen, which sends an approved item back to draft for edits.

Every piece from draft onward has a date

A publication carries a scheduled date from draft up. You do not have to pick one: promoting a stub to draft sets it to a week from now, which is a placeholder to move rather than an answer. The point is that the piece appears on the campaign calendar, where a wrong date is visible and fixable, instead of being invisible because it has none.

stub and archived are exempt โ€” a stub has nothing to schedule yet, and an abandoned stub still needs to be archivable.

Once a piece is promoting its date stops being a plan and becomes a record of when it went out, so it can no longer be moved โ€” same for a published promo. Everything else about a live piece stays editable, including the canonical URL it requires.

A promo can never be scheduled before the publication it promotes; that is enforced in the database, on every write path.

A stub is just an idea

A stub is a title and an optional note. Agents can pick one up and draft it, or you can promote it to draft yourself and start writing. Batch-creating stubs is how campaign planning happens.

Overviewโ€‹

The pipeline is deliberately linear so that state is never ambiguous โ€” at any moment you know exactly who owns an item and what happens next. Publications and their promos share draft and reviewing, so the same review muscle memory applies everywhere; the state an approval lands in then diverges, because what happens next diverges. A publication becomes approved and waits for a human to publish it by hand; a promo becomes scheduled, which on an auto channel means its post is already booked with upload-post.

stub โ”€โ”€โ–ถ draft โ”€โ”€โ–ถ reviewing โ”€โ”€โ–ถ approved โ”€โ”€โ–ถ promoting โ”€โ”€โ–ถ archived
โ–ฒ โ”‚ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ (request_changes)
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The six statusesโ€‹

Each status pairs a color with a glyph, so the pipeline reads at a glance and stays legible for color-blind users.

StatusUsed byWhat it means
stubpublicationsA raw idea โ€” title plus an optional note.
draftbothBeing written by a person or an agent.
reviewingbothAwaiting critique before it can be approved.
approvedpublicationsSigned off and ready to ship.
scheduledpromosApproved, and booked with upload-post โ€” see below.
promotingpublicationsLive at its canonical URL, fanning out into channel promos.
publishedpromosThe post has gone out on its channel.
archivedpublicationsRetired from the pipeline โ€” the outlier.
Promoting is published

A publication has no separate published status. Entering promoting requires the canonical URL โ€” the address the piece now lives at โ€” and stamps published_at at the same moment. The two were never independent: a promo is a post linking to the article, so it can't be written before the article has a URL, and the article has no URL before it's live. Splitting them only let a publication sit in a state its promos could never be worked from. Nothing follows promoting but archived; a piece that's live and never promoted simply has no promos.

Draft promos on approval, send them once live

Once a publication hits approved, Contentpipe can generate promos for every connected channel. They go out once it reaches promoting and has a URL to link to.

Promosโ€‹

An approved publication can start collecting channel promos โ€” one or more per channel (Substack, X, LinkedIn, Bluesky, and more) โ€” and they go out once it's promoting. Each promo runs its own mini-pipeline: draft โ†’ reviewing โ†’ scheduled โ†’ published.

draft โ”€โ”€โ–ถ reviewing โ”€โ”€โ–ถ scheduled โ”€โ”€โ–ถ published
โ–ฒ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ (request_changes)

Promos on auto channels are sent through upload-post's own scheduler โ€” the promo is uploaded with its dispatch time and their side posts it (no local cron). Channels without upload-post support (like Substack) are manual โ€” a human publishes and records the URL. Publications always publish manually.

Approving a promo sends it

On an auto channel, approving records the verdict and books the post with upload-post in one step โ€” there's no separate "now schedule it" action to forget. Because of that, a promo with no dispatch time, or one whose time has already passed, is refused: it stays in review until you set a future one. Bulk approve applies the same rule per promo, so untimed ones are rejected while their neighbours go out.

Manual channels are exempt rather than refused โ€” nothing to book โ€” so they approve and wait for you to publish them and record the URL.

Channel limits are enforced

Promos respect each channel's limits. A draft over the character cap (e.g. 280 / 280 on X) can't leave review until it fits.

Automating with MCPโ€‹

AI clients drive the pipeline over the Model Context Protocol. A client calls a workflow-level tool with an item id; the server validates the transition in the database and returns the updated item.

MCP client
await client.callTool("submit_for_review", {
publication: "pub_9f21",
});

The tool maps to a SECURITY INVOKER Postgres function, so RLS and the transition triggers apply to the calling user exactly as they would in the admin UI.

Statuses never skip

Skipping statuses is rejected. Trying to jump a publication from draft straight to promoting raises an invalid-transition error naming the current status and the allowed next states โ€” so the client can self-correct. Always go through review.