Back to articles
engineering August 7, 2026 6 min read

Building a Telegram Bot as My CMS

I turned my daily Telegram chats into a lightweight CMS, swapping bulky admin panels for a bot that lets me draft, edit, and publish from anywhere.

I’ve been running a personal site for a few years now, and every time I sat down to write a new post the workflow felt a little forced. WordPress was the obvious fallback, but the admin UI never quite matched the way I spend my day – bouncing between a terminal, a code editor, and a handful of messaging apps. One rainy afternoon, while scrolling through a chat with a friend, the idea struck: what if the place I already spend most of my time could also be the place I publish from?

The moment the idea clicked

I was on a train, phone in hand, trying to capture a thought about a recent project. Opening my notes app felt clunky, opening the CMS dashboard felt impossible without a laptop. I typed the draft into Telegram, sent it to myself, and thought, that’s a post already. The notion that a bot could act as a thin publishing layer started to feel less like a gimmick and more like a genuine solution to three problems I kept hitting:

WordPress, a custom React dashboard, even headless CMS services all introduced friction I wasn’t willing to pay. The bot promised a single point of interaction – my phone – and a tiny amount of code to glue it to the static site generator I already use.

Sketching the feature set

I started with a notebook, scribbling commands that felt natural in a chat context. The list grew quickly, and I grouped them into three buckets: draft handling, publishing actions, and asset management.

Seeing the list on paper made it clear that the bot would need a tiny state machine per user, persisting drafts somewhere safe. I chose a simple JSON file per draft stored in a private GitHub repository – no database, no extra hosting, just the same place my site’s source lives.

Building the skeleton

The first prototype was a Node.js script using the telegraf library. I kept the code under 150 lines, just enough to listen for messages, parse commands, and write JSON files. The real magic happened when I hooked the repository’s push event to my Netlify build hook. Every time the bot wrote a new post.json, Netlify rebuilt the site, and the fresh article appeared instantly.

const { Telegraf } = require('telegraf')
const bot = new Telegraf(process.env.BOT_TOKEN)

bot.command('draft', ctx => {
  const id = Date.now().toString()
  // create empty draft file in repo
  ctx.reply(`Draft started – ID ${id}. Use /title to set a heading.`)
})

bot.launch()

I wasn’t trying to build a full‑featured CMS, just a thin layer that let me type, edit, and push. The simplicity was deceptive; each command had edge cases that surfaced later.

The rough patches

Draft persistence

At first I wrote drafts directly to the repo’s drafts/ folder. The first time I tried to edit a draft from two different devices, a merge conflict erupted. Git isn’t built for rapid, concurrent writes from a bot. The fix was to move drafts to a private Gist, which gave me a REST endpoint with versioning and eliminated the conflict window.

Image handling

Telegram lets you send photos, but the files come as temporary URLs. I attempted to forward those URLs straight to Cloudinary, assuming a one‑liner would work. The API rejected the request because the file wasn’t publicly reachable. The workaround was to download the image to the server first, then upload it. That added a few seconds of latency, but the user experience remained acceptable.

Scheduling quirks

I wanted /schedule to accept natural language like “tomorrow at 9am”. I tried the chrono-node package, but its parsing sometimes mis‑interpreted time zones. After a few missed posts, I switched to an explicit ISO format, sacrificing convenience for reliability.

Moments I almost turned back

There were two points where I questioned the whole approach. The first came after the image upload saga; I wondered if a static site was the right home for a bot‑driven workflow. The second arrived when the Gist rate limit throttled my drafts during a weekend writing sprint. Each time I considered falling back to a conventional admin UI, I reminded myself why I started: the joy of publishing from a single place I already trusted.

Both setbacks turned into learning moments. The image bug taught me to treat external services as black boxes and write adapters that could be swapped later. The rate‑limit issue nudged me toward caching draft IDs locally on the bot server, dramatically reducing API calls.

What worked better than expected

What I would change next time

If I were to rebuild this today, I’d probably:

A broader take‑away

The whole experiment reminded me that the “right” tool is often the one that lives already in your daily workflow. I didn’t need a polished admin panel; I needed a bridge between the place I think and the place my site lives. Building that bridge forced me to confront assumptions about what a CMS must look like, and the answer turned out to be a handful of chat commands.

If you find yourself fighting a tool because it doesn’t match how you work, ask yourself whether a small, custom layer could solve the friction. You might end up with something that feels less like a product and more like an extension of your own habits.

Closing thoughts

Developers love to champion the latest framework or platform, but sometimes the best solution is the one you cobble together from the services you already love. My Telegram bot isn’t perfect, but it lets me write, edit, and publish without ever leaving a conversation. That simplicity is a reminder: question the conventions that feel comfortable for the crowd, and build what feels comfortable for you.

Need something like this built?

I work on full-stack web apps — backend systems, APIs, and the front-ends that sit on top. If this post was useful and you've got a project that needs it, I'd like to hear about it.

Want future posts like this?

No mailing list yet — for now, email me and I'll let you know when something new goes up.

Email me