Picture a chat where the whole editorial team lives. There's a link to a draft, an edit on top of it - "change the headline" - below that a question, "wait, did we already publish this?", and two hundred messages up, an approved text that never went out. Nobody lied and nobody slacked off. The material simply had no place that said where it currently was.
I worked through exactly this situation: material came in from several sources, went through editing, and left for different publishing channels. All of it lived in chat and got lost regularly. By the end of this article you'll understand how to turn an ordinary Notion database into a working pipeline: one where a card's status drives the automation, and the human stays a decision point - not a bottleneck, but a decision point.
Why a database should act as a pipeline
Notion is a note-taking service in which you can create a database: a table where each row is a separate card with its own fields. Usually people set one up as a list: "here's our material." A pipeline differs from a list in one property: from the card you can see not only what it is, but where it is right now.
I made a single database where each card is one piece of material, and the columns describe its state:
- channel - the platform the material is supposed to go to
- status - the stage the material is at right now
- publication date - when it's supposed to go out
- final text - what will actually go to the channel
- editor's notes - edits and comments
- risk flags - the things that need a careful look before sending
Sounds boring. But it's exactly this set that eliminates the chat: the question "what state is this material in?" stops being a question for a person and becomes a glance at a column.
flowchart LR I["Inbox: черновик"] --> R["У редактора"] R --> N["Готово для человека"] N --> H["Решение автора"] H -->|"ставит Scheduled<br/>и дату"| SCH["Scheduled"] SCH --> PUB["Опубликовано"] R -.->|"одобрение редактора<br/>не публикует"| SCH
Status as the only switch
This is the main decision, and it was also the main mistake in the first version.
Publishing is handled by a script - a small program that runs on its own on a schedule, reads the database, and sends the material. In the first version I tied it to editorial approval: the editor approved, so it can be published. Logical and wrong.
Editorial approval is a judgment about the quality of the text. It's not a command to send. Between "the text is good" and "the text should go out today in this channel" lies an entire decision that a human makes, and it can't be derived automatically.
In the second version the script picks up only cards that simultaneously have:
- status equal to "scheduled"
- a publication date that has already arrived
That's it. No "the editor approved, so it's fine." One status - one switch. If the material didn't go out, you know exactly where to look: the status. If the wrong one went out - same place.
A separate channel value for every platform
The second small thing of the same kind. The "channel" field must have its own separate value for every platform. When two channels shared one value, there was a risk of sending one platform's material to another - and that's the class of error that gets noticed not by you but by your readers.
The temptation is understandable: two platforms are similar, why multiply values. Because automation doesn't distinguish "similar" from "the same." To it, an identical value is an identical address.
Roles: who writes, who edits, who decides
The pipeline holds together not only on statuses, but on roles being separated and not reaching into each other's fields:
- generation writes the draft
- the editor records a verdict and edits the text
- a human makes the decision to publish
Each role changes only its own fields. Generation doesn't touch the publication status. The editor doesn't set the send date. The decision to publish doesn't rewrite the text.
This looks like bureaucracy right up until the first post-mortem of "why did this go out." When every field has one owner, the answer to that question takes a minute rather than an evening of reading the edit history. And, more importantly, nobody can accidentally publish material just by editing it.
flowchart TB
C["Запуск каждые 30 минут"] --> READ["Читает базу"]
READ --> Q{"Есть Scheduled<br/>с наступившей датой?"}
Q -->|"нет"| SIL["Пустой вывод.<br/>Никаких уведомлений"]
Q -->|"да"| P["Публикует"]
P --> MARK["Ставит отметку<br/>в базе"]
P --> ERR{"Ошибка?"}
ERR -->|"да"| ALERT["Сообщение автору,<br/>статус не меняется"]The automation reads, but doesn't decide
The script reads the database every half hour. After that come four rules, each of which I added after things got bad without it.
Silence is a normal result. If there's nothing to publish, the script writes nothing. Empty output is the absence of a notification, not a message saying "nothing here." The difference is fundamental: if a system tells you forty-eight times a day that it did nothing, you stop reading its messages - and you miss the one that mattered.
An error doesn't change the status. If something breaks at any step - network, permissions, format - the card stays in the "scheduled" state and goes out on the next run. This matters more than it seems. The opposite version looks like this: the script cleared the status, the send didn't go through, and a failed publication in the database is indistinguishable from a successful one. You find out a week later from a reader.
After success the script closes the card itself. It sets the status to "published" and records a link to the live material. Without this, the database and reality diverge in literally a week, and after that nobody trusts the database - and a pipeline nobody trusts turns back into a chat.
Integrations have different permissions. A separate subtlety: the script reaches Notion through an integration - a service connection with its own token, an access key. A token that can see one database may not see another. And the error in that case looks like "page not found," not like "no access." You'll spend half a day looking for a typo in an identifier that isn't there. Just remember the symptom: "not found" in Notion very often means "access wasn't granted."
Put it to work
What to check in practice
If you're assembling a pipeline like this for yourself, here's a sequence you can get through in one evening.
- Set up a database where a row is a piece of material, and add six fields: channel, status, publication date, final text, editor's notes, risk flags.
- Write out the list of statuses and find among them exactly one at which material goes out to a channel. If there are two candidates - you haven't designed the pipeline yet.
- Set up a separate channel value for every platform, even if the platforms are similar. Check that no value is used twice.
- Create an integration, grant it access to this specific database, and make sure the script reads it. If you see "page not found" - check access first, the identifier second.
- Write the database read against both conditions at once: status "scheduled" and publication date not in the future.
- Separately, make it so that on any error the status doesn't change. Test this deliberately: temporarily break the sending and see whether the card stayed scheduled.
- Add the result write-back: status "published" and a link to the material that went out.
- Set it to run once every half hour and make sure that an empty result sends nothing.
How to tell it worked
The criterion is simple, and it isn't about code.
Open the database on a random day and, without asking anyone and without opening the chat, answer three questions: what goes out today, what's stuck and why, what already went out and at what link. If the database answers all three - the pipeline works.
The second check is a week after launch. Take five cards with the status "published" and open the links recorded in them. All five should lead to live material in the channels named in the channel field. If even one doesn't match, you don't have a Notion problem - you have a status changing somewhere it shouldn't.
And the third, the most honest one: count how many times in a week someone asked in the chat "hey, has this been published yet?" Zero is the result.