Personal automation · Claude Code Channels (research preview), MCP plugins, Telegram Bot API

Claude Code Channels + Telegram → Automated Private Blog

A remote input pipeline that turns a Telegram bot into a personal secretary: send a link from the phone, Claude Code reads it, writes a private deep-dive note, commits and pushes, and Cloudflare Pages deploys. No backend of my own.

Claude Code Channels + Telegram → Automated Private Blog

A frictionless pipeline to feed a personal /private knowledge base. Send a link from the phone, get back a Spanish deep-dive note in under a minute. No backend of my own.

Built on Claude Code Channels, a research-preview feature (v2.1.80+) that lets an MCP plugin push events directly into a running Claude Code session.

Context: the reading-debt problem

Every day I stumble on something worth reading: a LinkedIn post, a third-party research article, an engineering blog, a paper. I save the link and tell myself I will come back to it. I almost never do.

A saved link is not worth much if you do not read it, do not really understand it, and cannot come back to it easily. For months — years, really — I kept piling interesting links into Notion, browser bookmarks and Telegram saved messages -> a big "black hole" where links accumulated but which I rarely came back to. When I did, I would feed them through the grinder (ChatGPT, which explained them well but sometimes way too tersely), and a few months later it was never that easy to find any of it again, buried in an endless list of old chats.

The framing here is borrowed. I ran into it first in Fran Pastor's post "Tu WhatsApp de links no es conocimiento" (linked at the bottom of the page), which diagnoses the same debt and builds an autonomous agent to triage a multi-source firehose (Gmail, Twitter, newsletters) down from thousands of signals to a handful worth reading. Same diagnosis, different prescription and different stack: I do not have a firehose to filter, I have a single curated stream that needs processing depth.

  • The bottleneck is not discovery. At this point I do not need a filter, a feed, or a scoring layer. For me that is the human-in-the-loop part... sharing with colleagues, debating it out... we are still human and communication is key.
  • The bottleneck is comprehension. I like things explained at the exact level that lets me understand them deeply — against my stack, my strengths, my blind spots.
  • I want to come back to it. Not just read it once — keep it, revisit it, build on it, quote it into future work.
The link-well: saving a link is not reading it Three capture buckets on the left — Notion dump, browser bookmarks, Telegram saved messages — all funnel into a single red "link well" labelled 'I will read it tomorrow'. From there an arrow leads to a black 'never' box marked as entropy. A caption at the bottom reads: save != process != understand != apply. Notion dump Bookmarks Saved Messages link well "I will read it tomorrow" never (entropy) save != process != understand != apply
Three capture buckets on the left (Notion dump, browser bookmarks, Telegram saved messages) all funnel into a red, dashed 'link well' box labelled 'I will read it tomorrow', which points to a black 'never (entropy)' box. Caption: save != process != understand != apply.

The real value: a personal knowledge manager

This project is not really about a Telegram bot. It is about turning a captured link into a page on a private site that belongs to me, explained the way I learn best, stored where I already look for what I know.

  • Input -> a single link sent to a private bot from my phone.
  • Output -> a deep-dive Spanish note on my own private site, behind Cloudflare Access, alongside the rest of my knowledge base.
  • Why it works -> the system already knows me. CLAUDE.md encodes my stack, my level, how I like things explained, which analogies land and what I already take for granted.
  • Every post is tailored. Same link sent by someone else would produce a different note. The system's job is not to summarise — it is to translate the source into *my* mental model.
  • The knowledge compounds. Notes live in git, versioned, searchable, and reachable from the same URL scheme as the rest of the site. Future-me can find them.
  • The friction is gone. From the moment I see a link to the moment a private page exists that explains it at my level: about one minute, zero context switch, zero editor open.
The CLAUDE.md profile acts as a lens that tailors each link to the reader Left-to-right flow. A single blue link card on the left represents the raw URL — the same one anyone else would see. A big amber CLAUDE.md card in the middle is labelled "the lens" and contains four white chips describing what the file encodes about the reader: stack (AWS, TS, NestJS, Terraform), level (senior backend, hands-on architect), style (deep dive, ES, inline SVG, analogies) and priors (already knows Docker, MCP, hex arch...). An arrow leads from CLAUDE.md into a green "your note" card on the right showing a tailored deep-dive in Spanish with an inline SVG diagram and a /private/... slug tag. Two semi-transparent cards behind the main note hint at the rest of the knowledge base that accumulates over time. A bottom caption reads: same link, different reader -> different note. Yours, persistent, and it compounds with every new one. El perfil es la lente el sistema ya te conoce INPUT one link https://example/post same URL anyone else would see THE LENS CLAUDE.md encodes who I am, in the repo itself stack AWS · TS · NestJS · Terraform level senior backend, hands-on architect style deep dive · ES · inline SVG · analogies priors already knows Docker, MCP, hex arch… same file drives the full style guide of the site YOUR NOTE deep dive · ES + inline SVG diagrams /private/... same link, different reader -> different note yours, persistent, and it compounds with every new one
Left-to-right flow. A single blue link card on the left is the raw URL, the same one anyone else would see. A big amber CLAUDE.md card in the middle labelled 'the lens' contains four chips: stack (AWS, TS, NestJS, Terraform), level (senior backend), style (deep dive, ES, inline SVG, analogies) and priors (already knows Docker, MCP, hex arch). An arrow leads into a green 'your note' card on the right showing a tailored deep-dive, an inline SVG diagram placeholder and a /private/... slug tag. Two semi-transparent cards behind it hint at the rest of the knowledge base. Bottom caption: same link, different reader -> different note. Yours, persistent, and it compounds with every new one.

The idea: push, not pull

A channel inverts the control flow of MCP. Instead of Claude reaching out to a tool, the plugin pushes an event straight into the live session.

  • Traditional MCP -> Claude is the initiator. It asks, the server answers.
  • Channel -> the plugin is the initiator. It pushes a prompt straight into Claude Code.
  • A DM to a private bot of mine becomes a remote input that lands in my terminal as if I had typed it.
  • Entirely local. The plugin runs next to Claude Code, nothing public is exposed.
  • The flow itself lives in CLAUDE.md, colocated with the code of the site it feeds.
Traditional MCP (pull) versus Claude Code Channels (push) Side-by-side comparison. Left: the traditional MCP pattern with Claude Code as initiator, asking an MCP Server and getting an answer. Right: a Claude Code Channel where the plugin is the initiator and pushes events into the running Claude Code session. Traditional MCP (pull) Claude decides when to reach out Claude Code initiator host + MCP client 1. asks 2. answers MCP Server passive · waits to be called exposes tools + resources the host controls the exchange Channel (push) server pushes events into the session Channel plugin (MCP) initiator Bun process · polls external source pushes event Claude Code receives <channel> as a prompt --channels plugin:telegram@… the external world lands in the live session inversion of control — the initiator moves from the host to the server
Traditional MCP on the left (Claude Code asks an MCP Server on demand) versus a Claude Code Channel on the right (the plugin pushes events into the running Claude Code session). The initiator moves from the host to the server.

End-to-end architecture

Three local pieces cooperate. Only two interactions cross the public internet.

  • Telegram Bot API -> Bun-based channel plugin (polls getUpdates).
  • Channel plugin -> Claude Code session. Pushes the message as a <channel> prompt.
  • Claude Code -> WebFetch + Write + npm run build + git commit/push + reply to Telegram.
  • GitHub -> Cloudflare Pages build -> site redeployed behind Cloudflare Access.
  • Only two things leave my laptop: the Telegram poll and the final git push.
End-to-end architecture: phone to Telegram to Claude Code Channels to Cloudflare Pages Full publishing loop. A mobile user sends a URL to a Telegram bot. The Telegram Bot API is polled by a Bun-based MCP channel plugin running on the local machine. The plugin pushes the message as a channel event into a long-running Claude Code session on the same machine. Claude Code reads CLAUDE.md, uses WebFetch to read the URL, writes a markdown file under src/content/private, runs npm build, git commit and git push. GitHub triggers a Cloudflare Pages build that publishes the site. The new private post is gated by Cloudflare Access, and the same mobile user reads it back when access is approved. You mobile sends link Telegram Bot API @my_bot public HTTPS LOCAL MACHINE · NO PUBLIC ENDPOINT Channel plugin plugin:telegram@… Bun · MCP server poll Claude Code claude --channels reads CLAUDE.md push Actions driven by CLAUDE.md 1. WebFetch(url) read the linked article 2. analyse + write deep-dive note in Spanish 3. Write src/content/private/ save as <slug>.md 4. npm run build verify the site compiles 5. git commit + push straight to master 6. reply to Telegram slug + live URL no Lambda · no Worker · no webhook · no PAT on the edge GitHub master Cloudflare Pages build + deploy Cloudflare Access /private gate reads back when access is approved the only things crossing the public internet: the Telegram poll and the final git push
Full publishing loop: phone → Telegram Bot API → Bun channel plugin running locally → long-running Claude Code session → WebFetch / Write / npm build / git push → GitHub → Cloudflare Pages build → Cloudflare Access → back to the phone. The plugin and Claude Code sit inside a dashed 'local machine, no public endpoint' box.

Infrastructure that collapses

The naive design would need five public moving pieces. Channels collapse that to three local ones.

  • Removed -> Cloudflare Worker hosting a Telegram webhook.
  • Removed -> bot token sitting at the edge.
  • Removed -> GitHub App or PAT with write access to the repo.
  • Removed -> a Claude API call from the Worker and its API key.
  • Removed -> a public HTTPS endpoint to secure and monitor.
  • Kept -> a long-running claude --channels session, the Telegram plugin, and CLAUDE.md. All local.
  • Only trade-off -> the session must be alive somewhere.
Naive design versus channels: infrastructure collapse Before-and-after comparison. The naive design on top requires five public moving pieces: a Cloudflare Worker hosting a Telegram webhook, a bot token stored at the edge, a GitHub App or personal access token, a call to the Claude API, and a public HTTPS endpoint to secure. The channels-based design on the bottom collapses all of that into three local pieces: a claude --channels session, the Telegram plugin, and CLAUDE.md as the system prompt — with nothing public and no secrets on the edge. Naive design five public moving pieces — each one a place for things to go wrong Cloudflare Worker deploy + maintain public HTTPS Telegram webhook TLS cert handler endpoint Bot token at the edge secret to rotate blast radius GitHub App or PAT scoped write creds commit on behalf Claude API + API key another secret billing scope collapses into Claude Code Channels three local pieces — nothing public, no secret at the edge claude --channels long-running session host + client + tools Telegram plugin Bun · MCP channel polls Bot API locally CLAUDE.md in the repo the flow = system prompt 5 pieces public → 3 pieces local
Before/after. Top: five boxes in a chain (Cloudflare Worker, Telegram webhook, bot token on the edge, GitHub App/PAT, Claude API call with API key) representing the naive public-infrastructure design. Bottom: three local boxes (claude --channels session, Telegram plugin, CLAUDE.md) representing the channels-based design. A 'collapses into' arrow connects them.

Why not Lambda, a Worker, or a hosted automation

I considered every obvious shape before settling on channels. None of them fit a curated, reactive, deep-dive-per-message use case cheaply or simply.

  • AWS Lambda + API Gateway + Claude API -> you are running a whole service for a one-person flow. You pay for invocations, pay for the gateway, pay per token for the LLM, and maintain the Terraform for something you send a few links a week to.
  • Cloudflare Worker + Telegram webhook -> cheaper per invocation, but you still host a public HTTPS endpoint, still store the bot token on the edge, and still need a GitHub App or PAT with write access to push commits back.
  • Zapier / Make / n8n -> shallow. You get "fetch URL" and "call LLM" steps, but not a deep-dive-with-diagrams-in-Spanish flow driven by the repo's own CLAUDE.md. You cannot point it at project conventions, so you end up rewriting half of the logic anyway.
  • A self-hosted daemon -> closest in spirit, but you own the bot-API polling, the reconnect logic, the secret management, the deploy pipeline and the alerting. You just rebuilt the channels plugin by hand.
  • Claude Code Channels -> the one shape where everything collapses. MCP already exists, Claude Code is already running, CLAUDE.md is already the system prompt. I add a plugin and one CLI flag. The flow is live.

Costs

This pipeline adds zero marginal dollars per post. No new line item. Everything rides on tools I already pay for or use.

  • Telegram bot -> free. The Bot API has no pricing.
  • Claude Code subscription -> the basic ~€30/month plan is enough. Channel events consume the same monthly allowance as any other Claude Code session. At my volume (a handful of posts per week, ~2k words each) it fits comfortably inside the basic tier.
  • Cloudflare Pages -> free tier (500 builds / month, unlimited sites).
  • Cloudflare Access -> free on the Zero Trust plan for up to 50 users.
  • GitHub -> free for public repos.
  • Bun -> free runtime for the channel plugin.
  • Compare with the Lambda shape: API Gateway invocations + Lambda compute + per-token Claude API + CloudWatch logs + Terraform state bucket + domain + TLS + maintenance time. Low dollars, but non-zero, and you own the bill.
  • My flow adds exactly one artefact per run: a new commit in git. Nothing to monitor, renew, or rotate.

Setup and pairing

The happy path is short and strictly ordered. Pair first, lock immediately.

  • 1. Prereqs -> Claude Code v2.1.80+, a claude.ai login (Pro / Max / Team), and Bun.
  • 2. Install -> /plugin install telegram@claude-plugins-official.
  • 3. Configure -> /telegram:configure <token>. Token stored with 600 permissions in ~/.claude/channels/telegram/.env.
  • 4. Start -> claude --channels plugin:telegram@claude-plugins-official.
  • 5. Pair -> DM the bot, paste the returned code into /telegram:access pair <code>.
  • 6. Lock -> immediately run /telegram:access policy allowlist.
  • Never leave the bot in dmPolicy: "pairing". Anyone who finds it can request a fresh code.
# Inside a Claude Code session
/plugin install telegram@claude-plugins-official
/reload-plugins
/telegram:configure <token>

# Restart Claude Code with the channel enabled
claude --channels plugin:telegram@claude-plugins-official

# Pair your Telegram account, then immediately lock the bot
/telegram:access pair <code>
/telegram:access policy allowlist
The full setup, top to bottom. Replace <token> and <code> with the real values from BotFather and the DM reply.
Pairing sequence and lockdown Sequence diagram showing how to pair a Telegram account with a running Claude Code session and immediately lock the bot down. Three lifelines: you on Telegram, the channel plugin, and the access.json file. Steps: 1) DM any message to the bot; 2) plugin replies with a pairing code; 3) you run /telegram:access pair with that code inside Claude Code; 4) the plugin appends your sender id to access.json; 5) you run /telegram:access policy allowlist to lock the bot; 6) the plugin sets dmPolicy to allowlist in access.json. You (Telegram) mobile DM Channel plugin local Bun process access.json ~/.claude/channels/telegram 1 DM any message 2 reply with pairing code code: d87f4c 3 /telegram:access pair d87f4c typed inside Claude Code 4 append sender id allowFrom += [senderId] DO IMMEDIATELY — LOCKDOWN 5 /telegram:access policy allowlist 6 close the door dmPolicy = "allowlist" never leave the bot in dmPolicy "pairing" — any DM would request a fresh code
Sequence diagram with three lifelines (You on Telegram, the channel plugin, and the access.json file). Six numbered messages walk through pairing and lockdown: DM any message → plugin returns a code → /telegram:access pair inside Claude Code → the sender id is appended to access.json → /telegram:access policy allowlist → dmPolicy becomes 'allowlist' in access.json.

Security model

The bot handle is public, like an email address. Everything that actually protects the flow lives below it.

  • Drive-by DMs are dropped at the plugin. dmPolicy: "allowlist" plus a single allowFrom entry means any unknown senderId is discarded before reaching Claude.
  • SenderId spoofing is infeasible. Telegram signs it server-side. The only realistic path is account takeover, closed by Telegram Two-Step Verification.
  • Real threat: prompt injection via fetched content. A hidden "ignore previous instructions" in a linked page could push Claude toward unintended tool calls.
  • Mitigation: per-invocation tool-use approvals. Every Write, Bash or git push pops an Allow / Deny button on my phone.
  • The channel cannot widen Claude's tool surface or escape the working-directory sandbox. It only delivers a prompt.
  • The one switch that would break the model, --dangerously-skip-permissions, is kept out of this flow.

CLAUDE.md as the system prompt

The flow is not code. It is a short section in the repo's own CLAUDE.md, read by Claude Code once per session.

  • Language -> Spanish.
  • Depth -> deep dive, senior backend reader.
  • Structure -> TL;DR -> Contexto -> Cómo funciona -> Puntos clave -> Cómo aplica a mi stack -> Opinión crítica.
  • Diagrams -> inline SVG, white-poster background with a fixed palette. Renders identically in light and dark mode.
  • Destination -> src/content/private/<slug>.md, committed and pushed to master.
  • The repo that hosts the site also defines how the site gets fed. No wrapper script, no separate service.
## Telegram Channel -> Private Blog Posts

When a message arrives via the Telegram channel containing a URL:

1. Fetch the link content (WebFetch)
2. Analyze the content thoroughly
3. Generate a private blog post following the style guide
4. Save as src/content/private/<slug>.md
5. Run `npm run build` to verify
6. Commit & push to master
7. Reply via Telegram with the slug and live URL

Style guide: Spanish, deep dive, inline SVG diagrams on the
white-poster palette, structure TL;DR -> Contexto -> Cómo funciona
-> Puntos clave -> Cómo aplica a mi stack -> Opinión crítica.
The relevant section of the repo's CLAUDE.md. This is the entire 'code' behind the flow — everything else is standard Claude Code behaviour.
CLAUDE.md as the system prompt for the channel flow Three-stage flow. A channel event arrives with a URL from Telegram. Claude Code loads the CLAUDE.md file in the working directory and matches the "Telegram Channel → Private Blog Posts" section. Claude then executes the numbered recipe: WebFetch the URL, analyse the content, generate a Spanish deep-dive post with inline SVG diagrams, save it under src/content/private, run npm run build, git commit and git push to master, and reply to Telegram with the slug and live URL. INCOMING Channel event <channel source="telegram"> https://example.com/post treated like a normal prompt LOADED ONCE PER SESSION CLAUDE.md ## Telegram Channel → Private Blog Posts style guide · diagram palette · slug rules a section of the repo’s own CLAUDE.md EXECUTED IN ORDER Recipe 1. WebFetch(url) 2. analyse full article 3. generate deep-dive (ES) 4. inline SVG diagrams 5. Write src/content/private/<slug>.md 6. npm run build 7. git commit + push master 8. reply to Telegram no wrapper script, no separate service — the flow lives in the codebase it touches
Three-stage flow: a channel event arrives with a URL; Claude Code matches the 'Telegram Channel → Private Blog Posts' section of CLAUDE.md loaded once per session; Claude then executes an ordered recipe (WebFetch, analyse, generate ES deep-dive, inline SVG diagrams, write src/content/private, npm run build, git commit+push, reply to Telegram).

Trade-offs

  • Session-dependent by design -> the pipeline is only live while the Claude Code session is running. No offline queueing on my side.
  • Why it still works for me -> my laptop is up ~24/7 with a long-running claude --channels session, so the pipeline is effectively always reachable.
  • If the machine is off -> Telegram does the queueing for me. Messages sent to the bot sit inside Telegram's own servers until I open the channel again; the plugin then replays them in order via getUpdates. Nothing is lost, nothing reaches Claude until I reopen the session.
  • Research preview -> the --channels contract is not frozen yet. Expect changes.
  • Per-invocation tool-use approvals break the "send a link and walk away" ergonomics.
  • The only escape, --dangerously-skip-permissions, is exactly what it sounds like and has no place in a shared setup.
  • git push straight to master is convenient but risky. Safer v2: push to a content/telegram-<date> branch, auto-merge only on green build.

Stack

Claude Code MCP Channels (research preview) Telegram Bot API Bun Astro SSG Cloudflare Pages Cloudflare Access

Links