Adding something to a calendar on a phone is about eight taps: open the app, tap plus, type the title, pick a date, pick a start time, pick an end time, save. Saying "lunch with Priya Friday at 1" into a voice note takes three seconds.
This post shows how to build a personal calendar assistant in n8n that you talk to through Telegram, by text or voice note. A Gemini-powered AI Agent reads and writes your Google Calendar and Google Tasks, transcribes voice notes itself (no separate speech-to-text service), and replies in plain language. Everything runs on built-in n8n nodes and Gemini's free tier.
What it can do
- "What's on tomorrow?" → lists tomorrow's events
- "Dentist Tuesday 3pm" → creates a 30-minute event and confirms the exact time
- "Remind me to renew my passport by the 30th" → creates a Google Task with a due date
- "What's on my to-do list?" / "Mark the passport one done"
- Follow-ups work: "Actually make it 4pm" after creating an event
The architecture
Telegram Trigger (message) -> Set: settings (allowed user IDs, timezone, default event length) -> IF: is this an allowed user? (no -> silently ignore) -> Switch: voice or text? voice -> Telegram: download file -> Gemini: transcribe audio -> Set request text -> Set request -> AI Agent chat model: Gemini memory: Simple Memory keyed on chat ID (last 10 messages) tools: Get calendar events, Create calendar event, Get open tasks, Create task, Complete task -> Code: format reply (strip markdown, split long messages) -> Telegram: send message Step 1: Lock the bot down first
A Telegram bot is public: anyone who finds its username can message it. A bot connected to your calendar must only answer you. The settings node holds a comma-separated list of allowed numeric user IDs (get yours from @userinfobot), and an IF node checks every message:
{{ $json.allowed_user_ids.split(',').map(s => s.trim()).includes(String($json.message.from.id)) }} Messages from anyone else go nowhere. Don't reply to them at all: a reply confirms the bot is live.
Step 2: Voice notes without a transcription service
Telegram voice notes arrive as a file_id. The Telegram node (resource: File) downloads the OGG file as binary, and n8n's Google Gemini node has an Audio → Transcribe operation that takes binary input directly. Gemini handles Telegram's OGG/Opus format fine, so there's no conversion step and no Whisper bill.
The Switch node routes on:
voice: {{ $json.message.voice?.file_id || $json.message.audio?.file_id }} exists text: {{ $json.message.text }} exists Anything else (stickers, photos) gets a short "send me text or a voice note" reply.
Both branches end in a Set node that outputs the same field, request, so the agent doesn't care which way the message came in.
Step 3: Give the agent the date
This is the single most important part of the prompt. LLMs don't know what day it is, and "next Friday" is meaningless without that. The system message injects the current time in your timezone on every run:
You are a concise personal assistant that manages the user's Google Calendar and Google Tasks through Telegram. Now: {{ $now.setZone(timezone).toFormat("cccc d LLLL yyyy, HH:mm") }} ({{ timezone }}). Resolve "today", "tomorrow", "next Friday" etc. against this. Use ISO 8601 date-times with the correct UTC offset in every tool call. Rules: - Use the tools for every calendar or task question; never guess what is in the calendar. - New events default to {{ default_event_minutes }} minutes when no end time is given. Repeat back the title, day, start and end after creating one. - A task with a deadline gets a due date; a request with a fixed time ("dentist at 3pm Tuesday") is an event. - A weekday name ("Friday") means the next one after today; if today is that weekday, ask whether they mean today or next week. - If the request is ambiguous (missing day or time), ask one short question instead of guessing. - Reply in plain text, max 8 short lines, times in 24-hour format, no markdown tables. Notice "with the correct UTC offset". Without it, the model sends 2026-11-06T13:00:00 with no offset, Google Calendar interprets it in UTC, and your 1pm lunch lands at 1pm UTC. Including the day name (cccc) also helps the model with weekday arithmetic.
"Repeat back the title, day, start and end" is your safety net: you see immediately if it misheard something.
Step 4: Tools with $fromAI()
n8n's tool nodes (Google Calendar Tool, Google Tasks Tool) let the agent fill parameters using $fromAI(). Each call names the parameter and describes it for the model:
Create calendar event calendar: primary start: {{ $fromAI('start', 'Event start, ISO 8601 with offset', 'string') }} end: {{ $fromAI('end', 'Event end, ISO 8601 with offset', 'string') }} summary: {{ $fromAI('title', 'Short event title', 'string') }} location: {{ $fromAI('location', 'Location, or empty string', 'string') }} Get calendar events timeMin: {{ $fromAI('after', 'Start of the time range, ISO 8601 with offset', 'string') }} timeMax: {{ $fromAI('before', 'End of the time range, ISO 8601 with offset', 'string') }} options: singleEvents = true, orderBy = startTime, limit 25 Two settings on Get events save a lot of confusion:
- singleEvents: true expands recurring events into individual occurrences. Without it, your weekly stand-up appears once, dated the day you created it.
- fields limited to
id, summary, start, end, location, description, hangoutLinkkeeps the tool response small, which keeps the agent fast and cheap.
For tasks, "Complete task" needs a task ID, and the description says where to get it ("ID of the task to complete, from Get open tasks"). The agent learns to call Get open tasks first, find the match, then complete it.
Set the agent's max iterations to around 8. That allows "check calendar, then create event, then confirm" without letting a confused model loop.
Step 5: Memory and replies
Simple Memory keyed on the Telegram chat ID with a 10-message window makes follow-ups work ("move it to 4pm" knows what "it" is).
A final Code node cleans the reply for Telegram:
const req = $('Calendar and tasks assistant').first().json; let text = String(req.output || '').replace(/\*\*(.+?)\*\*/g, '$1').replace(/^#+\s*/gm, '').trim(); if (!text) text = "Sorry, I couldn't do that just now. Please try again in a minute."; const heard = $('Use transcript as request').isExecuted ? `🎙 "${$('Use transcript as request').first().json.request}"\n\n` : ''; text = heard + text; const parts = []; for (let i = 0; i < text.length; i += 3900) parts.push(text.slice(i, i + 3900)); const chatId = $('Set assistant settings').first().json.message.chat.id; return parts.map(t => ({ json: { chat_id: chatId, text: t } })); For voice notes it echoes what it heard first, so a transcription mistake is obvious. It also splits anything over Telegram's 4,096-character message limit.
Setup
- Create a bot with @botfather, add a Telegram credential in n8n.
- Add Google Gemini (PaLM) API (free key from aistudio.google.com), Google Calendar OAuth2 and Google Tasks OAuth2 credentials.
- In the settings node set your numeric Telegram user ID and your IANA timezone (
Europe/London,America/New_York). - Activate and message your bot.
Gotchas
- Telegram webhooks need HTTPS. n8n Cloud handles this. Self-hosted n8n needs a public HTTPS URL (
WEBHOOK_URL), or the trigger can't register. - One trigger per bot. Telegram only allows one webhook per bot token, so a test copy of the workflow will steal messages from the live one. Use a second bot for testing.
- Deleting is deliberately missing. The free version can create but not move or delete events. Letting an LLM delete calendar entries without a confirmation step is how you lose a meeting.
- Model swaps are easy. Any tool-calling chat model can replace Gemini in the agent, though you'll need another way to transcribe voice notes.
Going further
Once it's in daily use, the requests change from "add this" to "what's my day look like?" and "remind me before it starts". So I built a Pro version: safe move and cancel (the agent states which event it means and only deletes after you say yes), free/busy answers ("when am I free Thursday afternoon?"), a 7:30 morning briefing with today's events, due tasks and the weather, reminders before every meeting with the location or video link (checked every 10 minutes, never sent twice), and error alerts. It's $24 on Payhip: Telegram calendar assistant Pro. More templates are on the store and my n8n creator profile. There's also a free one on n8n.io that sends a daily Telegram digest of new YouTube videos with AI summaries, if you like the Telegram-as-interface idea.
What would you add as the next tool? Gmail and Notion are the two I get asked about most.
The free workflow JSON for this and 24 other n8n templates is on GitHub: n8n-ai-workflow-templates.