Telegram Task Bot: Build One With the quik.md API

Build a Telegram task bot in an afternoon: message the bot and tasks land filed in the right quik.md project. Real code, cron-friendly, with a morning digest.

By Ege Beşe9 min read

A Telegram task bot turns any Telegram message into a filed task: you message the bot, a small script polls getUpdates, posts the text to quik.md's capture endpoint, and the AI organizer files it into the right project. This guide builds the whole loop with two cron jobs and about sixty lines of code.

Telegram is where a lot of people already think out loud: saved messages, quick forwards, voice notes recorded mid-walk. A bot that listens to one private chat turns that habit into a task inbox without a new app to open. The working recipe below is the same one in our Telegram cron bot example, expanded with the reasoning and the reliability details.

Overhead flat-lay of a warm-paper desk with a small folded paper plane flying over an open notebook toward a shallow wooden tray, a fountain pen nearby, playful but minimal.
Send a message to the bot, and it lands filed in the right tray.

What does the finished bot do?

The finished bot does three things. It captures: every text message you send it becomes a quik.md item, AI-filed into the right project. It reports: every morning at 07:30 it posts your open todos due today back into the chat. And it closes the loop: replying to a digest line marks that task done. Everything runs on two cron jobs, so there is no server to babysit and no webhook infrastructure to secure.

The architecture is deliberately boring. Telegram's bot API exposes getUpdates for polling, which means a Cloudflare Worker, a Vercel cron, or a spare box all work as the host. quik.md supplies the capture endpoint and the AI filing. Your script is just the glue.

Step 1: create the Telegram bot and the quik.md key

Open Telegram, message @BotFather, and run /newbot. BotFather returns a token that authenticates every call your script makes. Then in quik.md, open Settings, then Developer, and create an API key. Keys are prefixed qk_ and shown once, so store it immediately.

Put both values into environment variables on whatever runs your cron:

TELEGRAM_BOT_TOKEN=123456:abc...
TELEGRAM_CHAT_ID=<your chat id>
QUIK_KEY=qk_...

The chat id is what lets the bot message you back for the digest. The simplest way to learn yours is to send the bot any message, then call getUpdates once by hand and read message.chat.id from the response.

Step 2: how do you capture messages on a cron?

Run a poller every minute. It calls getUpdates with the last seen offset, posts each new message's text to /api/v1/capture with organize: true, and advances the offset. This is the working code:

// poll-telegram.mjs. run on a 1-minute cron
const TG = process.env.TELEGRAM_BOT_TOKEN
const QK = process.env.QUIK_KEY
let offset = Number(globalThis.OFFSET ?? 0)

const updates = await fetch(
  `https://api.telegram.org/bot${TG}/getUpdates?offset=${offset}&timeout=0`,
).then((r) => r.json())

for (const u of updates.result ?? []) {
  offset = u.update_id + 1
  const text = u.message?.text
  if (!text) continue

  await fetch("https://quik.md/api/v1/capture", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${QK}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ text, organize: true, source: "api" }),
  })
}
globalThis.OFFSET = offset

Three details matter. The offset parameter tells Telegram which updates you have already seen, so each update is delivered once. The organize: true flag is what turns a raw message into a filed task: quik.md's server-side AI reads the text, picks the project, and writes the next step. And source: "api" marks the item so you can tell bot captures apart from keyboard ones in review.

Step 3: how do you capture voice messages?

Voice messages take one extra hop. Telegram gives you a file_id on the message; the getFile method turns that into a downloadable path. Download the bytes, forward them to quik.md's /api/v1/voice/transcribe endpoint, and pipe the returned text into /api/v1/capture exactly like a typed message.

The result is a genuine voice-driven telegram to do bot: you hold the mic in Telegram, say "follow up with Maya about the onboarding doc, Tuesday", and a filed task appears, transcript preserved. Voice messages are where this bot earns its keep, because the alternative is opening a task app with your hands full.

Step 4: send the morning digest back to Telegram

Capture without a report back is a one-way valve. A daily cron at 07:30 queries your open todos due today and posts them into the same chat:

// morning-digest.mjs. daily cron
const QK = process.env.QUIK_KEY
const TG = process.env.TELEGRAM_BOT_TOKEN
const CHAT = process.env.TELEGRAM_CHAT_ID

const today = new Date()
today.setHours(23, 59, 59, 999)

const r = await fetch(
  `https://quik.md/api/v1/items/search?status=todo&due_before=${today.toISOString()}&limit=20`,
  { headers: { Authorization: `Bearer ${QK}` } },
).then((r) => r.json())

const lines = r.items.map((i) => `• ${i.title || i.content_md.slice(0, 80)}`)
const text = lines.length
  ? `Today's plan:\n${lines.join("\n")}`
  : "Inbox zero. Take it easy."

await fetch(`https://api.telegram.org/bot${TG}/sendMessage`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ chat_id: CHAT, text }),
})

The search endpoint does the filtering: status=todo plus due_before set to the end of today, capped at 20 items. If the query comes back empty, the bot says so, which is the one notification worth getting. For the thinking behind a calm, reviewable inbox that only surfaces what matters, see the AI inbox use case.

Step 5: how do you complete tasks from Telegram?

Reply to a digest line and have the bot complete the task. The mechanics: parse the item id out of the digest line the user replied to, then call POST /api/v1/items/:id/toggle with { "is_completed": true }. Subtasks cascade automatically, so completing a parent closes its children without extra calls.

For batch completion, search first and bulk-toggle second: one call to /api/v1/items/search collects the ids, one call to /api/v1/items/bulk with op toggle marks them all done, up to 100 per request. The same search-then-bulk pattern powers most of the heavier automations in our Claude Code task inbox walkthrough, where an agent captures follow-ups it could not finish itself.

How do you keep the bot reliable?

The bot fails in three predictable ways, and all three are cheap to fix.

  1. Persist the offset. The example stores globalThis.OFFSET, which resets when the runtime restarts. On a serverless cron, write the offset to a file, KV, or a database row. A resetting offset replays old updates, and every replay becomes a duplicate capture.
  2. Respect rate limits. quik.md allows 60 requests per minute and 1,000 per day on free, 300 per minute and 10,000 per day on Pro. A one-minute poller sending a handful of messages stays far inside both. If you ever hit a 429, the retry_after value in the body tells you exactly how long to wait.
  3. Watch the AI quota. Every organize: true capture spends AI budget. A chatty bot on a busy chat can burn through it; when that happens, items still capture, they just arrive unfiled. Passing a client_id uuid per message also makes retries idempotent, so a flaky network cannot double-capture.

FAQ

How do I build a Telegram task bot?

Create a bot with @BotFather, mint a quik.md API key in Settings, then Developer, and run a one-minute cron that polls Telegram's getUpdates and POSTs each message text to /api/v1/capture. With organize set to true, quik.md's AI files each message into the right project. The whole capture loop is about twenty lines.

Can a Telegram bot turn voice messages into tasks?

Yes. Download the voice file with Telegram's getFile method, forward the bytes to quik.md's /api/v1/voice/transcribe endpoint, and pipe the returned text into /api/v1/capture. The result is the same as a typed message: a transcribed, AI-filed task.

Do I need quik.md Pro for a Telegram task bot?

No for basic capture. Posting text to /api/v1/capture with organize off works on the free plan, and items land in your inbox unfiled. AI filing with organize: true is Pro-only, so the self-filing version of the bot needs Pro. The bot in this guide uses polling, so it does not need the Pro-only webhooks feature either.

How does the bot file tasks into the right project?

Two ways. Pass organize: true and quik.md's server-side AI reads the message, picks the project, and writes the next step. Or pass a project_id explicitly if your bot already knows where the item belongs. The second option skips the AI quota cost entirely.

How do I stop duplicate tasks from my Telegram bot?

Persist the getUpdates offset somewhere durable, like a file or KV store, and send it on every poll. If the offset resets after a restart, Telegram replays old updates and every replay becomes a duplicate capture. For extra safety on retries, pass a stable client_id with each capture call so duplicates collapse server-side.

References

ShareShare on X

Keep reading