Write Once, Publish Everywhere Automatically: Notion + Next.js + Make.com
The hardest part of running a blog is not writing — it is distribution. I learned this the hard way: once an article is ready, it has to go on the website, then be announced on the Telegram channel, then posted to LinkedIn. Each step is separate manual work, and it is easy to forget something at every one of them. Eventually I built a pipeline for madrimov.uz where I only write in Notion — the website, Telegram and LinkedIn are handled by automation. Below is the architecture of this real system, the Make.com scenarios, and the lessons learned along the way.
The problem: one article — three separate jobs
The process used to look like this: I write an article, manually publish it on the site, then drop a link into the Telegram channel whenever I remember, and LinkedIn is usually forgotten altogether. The result:
- Lots of manual work: three or four copy-pastes per article.
- Consistency breaks down: the channel gets it today, the site tomorrow — or the other way around.
- The rhythm falls apart: in a busy week, nothing gets published at all.
The most expensive resource in content production is the attention that goes into writing. Distribution is mechanical work — which means a machine should do it.
Architecture: Notion as the single source of content
The whole system is built on one principle: content lives in exactly one place. For me that is a Notion database. Each article is one record with the properties it needs:
- Title and Slug — for the URL on the site.
- Content in three languages — Uzbek, Russian, English (the site is trilingual).
- Tags — multi-select, by topic.
- Published — a checkbox: this alone decides whether the article is visible on the site.
- PublishDate — the planned release date.
- TgPosted and LiPosted — checkboxes: whether the article has gone out to each channel.
Notion plays the role of a CMS here: a comfortable editor, a mobile app, version history — all for free. I never copy the content anywhere else: both the site and the scenarios read from this exact database.
On the Next.js side: ISR and a dependency-free fetch
The site runs on Next.js. I pull data from Notion without the official SDK, with a plain fetch — the API boils down to a single POST request, and there is no reason to maintain a separate dependency for that:
// lib/notion.ts
export async function getPublishedPosts() {
const res = await fetch(
`https://api.notion.com/v1/databases/${process.env.NOTION_DB_ID}/query`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NOTION_TOKEN}`,
"Notion-Version": "2022-06-28",
"Content-Type": "application/json",
},
body: JSON.stringify({
filter: { property: "Published", checkbox: { equals: true } },
sorts: [{ property: "PublishDate", direction: "descending" }],
}),
}
);
if (!res.ok) throw new Error(`Notion API: ${res.status}`);
const data = await res.json();
return data.results;
}For updates, ISR (Incremental Static Regeneration) is enough:
// app/blog/page.tsx
export const revalidate = 300; // 5 minutesTick Published in Notion — and the article is on the site within 5 minutes at most. No webhooks, no build triggers, no manual deploys. For a blog, a 5-minute delay bothers nobody, and in exchange the system stays radically simple.
Make.com: an automatic teaser to the Telegram channel
Now the distribution part. For the @madrimovblog channel there is a three-module scenario in Make.com that runs every morning at 9:
- Notion — Query a Database: finds records with
Published = trueandTgPosted = false, limit 1. - Telegram — Send a Message: posts to the channel with a teaser and the link
https://madrimov.uz/blog/{slug}. - Notion — Update a Database Item: sets
TgPosted = trueon that record.
The filter logic in Notion API terms looks like this:
{
"and": [
{ "property": "Published", "checkbox": { "equals": true } },
{ "property": "TgPosted", "checkbox": { "equals": false } }
]
}The third step is the heart of deduplication: without it, the scenario would find the same article again tomorrow and turn the channel into spam. If no matching record exists, the scenario simply ends quietly — that is a normal state, not an error.
LinkedIn: the same pattern, a different checkbox
LinkedIn gets its own scenario, but the pattern is exactly the same: Published = true & LiPosted = false → send the post → LiPosted = true. The only difference is the text format — LinkedIn gets an English annotation and a slightly more formal tone. Adding a new distribution channel is now very cheap: one checkbox property and one copy of a scenario.
The queue: automation keeps the "one article a week" rhythm
My favorite part of the system. When inspiration strikes, I write two or three articles in a row, give each a future PublishDate, and leave Published unchecked. A weekly scenario (Monday morning) does the following:
- Finds a record with
Published = falseandPublishDate <= today. - Sets
Published = trueon it.
That single checkbox flip triggers the whole chain: within 5 minutes ISR puts the article on the site, and the next morning the daily scenario distributes it to Telegram, then LinkedIn. Even when I am on vacation, the blog keeps living at a "one article a week" rhythm. This is a content queue: let writing depend on inspiration, and publishing on a schedule.
Why Make.com and not my own code?
I am a developer — I could have written these scenarios myself with cron and a small script. I deliberately did not:
- Cron means keeping a server (or a serverless function) alive and monitoring it — small, but still infrastructure.
- In Make, retries, error logs and run history come out of the box. If the Telegram API returns a 500 once, it retries on its own.
- A scenario is visual: opening it six months later, I understand everything at a glance.
When is your own code the better choice? For complex transformations (say, rendering markdown into another format), at high volume (Make charges per operation — thousands of runs get expensive), and when handling sensitive data. In my case it is about 30 operations a month — it fits comfortably even in the free tier.
Lessons learned
Idempotency is rule number one. Every distribution channel gets its own "already posted" checkbox. Without it, if a scenario runs again (a retry, a manual test), subscribers get the same post twice. Thanks to the checkbox filter, a second attempt simply returns an empty result and sends nothing.
Mark test records explicitly. One day my test article called "asdf" very nearly went out to the channel. Now test records carry a special tag, and every filter excludes them. Automation cannot tell a test from real content — that is your job.
Property names are an API contract. Renaming a property in Notion is easy — one click. But rename TgPosted to TelegramPosted and both the Make scenario and the site code break silently: Notion raises no error, it just returns an empty value. Collect the names into a single constants file on the code side, and think twice before renaming anything in Notion.
Conclusion
The whole system: a Notion database, 30 lines of fetch code in Next.js, and three tiny scenarios in Make. No dedicated server, no elaborate deploy chain. And the most important result is not technical: once the friction between writing and distribution disappeared, the writing itself increased. If you run a blog, a channel, or any other content stream — write once, and hand the rest to the machine.