Telegram to Google Sheets, expenses logged hands free
You meant to log that coffee. Then the day happened. By the time you remember, the receipt is gone, the amount is fuzzy, and your “budget” is basically vibes.
Founders feel it when month-end rolls around. Marketers notice it when reimbursements drag. And if you run a small team, you already know the pain of chasing people for “just one more expense.” This Telegram Sheets expenses automation fixes that.
You send a quick Telegram message (or a voice note, or a receipt photo). The workflow turns it into clean rows in Google Sheets, updates budgets, and even sends a quiet daily summary when you stop logging for a bit. Here’s how it works and what you’ll get out of it.
How This Automation Works
The full n8n workflow, from trigger to final output:
n8n Workflow Template: Telegram to Google Sheets, expenses logged hands free
flowchart LR
subgraph sg0["Telegram Flow"]
direction LR
n0@{ icon: "mdi:database", form: "rounded", label: "Google Sheets: Get Rows (Ded..", pos: "b", h: 48 }
n1@{ icon: "mdi:swap-horizontal", form: "rounded", label: "IF (Is Duplicate?)", pos: "b", h: 48 }
n2@{ icon: "mdi:swap-horizontal", form: "rounded", label: "Switch (Voice/Photo/Text)", pos: "b", h: 48 }
n3["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Code (Restore Telegram Paylo.."]
n4@{ icon: "mdi:swap-vertical", form: "rounded", label: "Set (Text Context)", pos: "b", h: 48 }
n5@{ icon: "mdi:robot", form: "rounded", label: "Google Gemini Chat (Text → J..", pos: "b", h: 48 }
n6["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Code (Parse Gemini JSON)"]
n7["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Code (Split expenses to items)"]
n8@{ icon: "mdi:database", form: "rounded", label: "Google Sheets → Append row(s)", pos: "b", h: 48 }
n9@{ icon: "mdi:swap-horizontal", form: "rounded", label: "IF (Has expenses?)", pos: "b", h: 48 }
n10@{ icon: "mdi:swap-vertical", form: "rounded", label: "Set (Photo Context)", pos: "b", h: 48 }
n11["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Code (Pick Best Photo)"]
n12["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Code (Normalize Gemini Image.."]
n13["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/telegram.svg' width='40' height='40' /></div><br/>Telegram → Send Error Messag.."]
n14["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/telegram.svg' width='40' height='40' /></div><br/>Telegram → Send Final Message"]
n15@{ icon: "mdi:robot", form: "rounded", label: "Google Gemini (Analyze Image)", pos: "b", h: 48 }
n16@{ icon: "mdi:swap-vertical", form: "rounded", label: "Set (Voice Context)", pos: "b", h: 48 }
n17["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/telegram.svg' width='40' height='40' /></div><br/>Telegram → Get Voice File"]
n18["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/telegram.svg' width='40' height='40' /></div><br/>Telegram → Get Image File"]
n19@{ icon: "mdi:robot", form: "rounded", label: "Google Gemini (Analyze Audio)", pos: "b", h: 48 }
n20["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Code (Normalize Gemini Audio.."]
n21["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Code (Normalize Gemini Text .."]
n22@{ icon: "mdi:swap-horizontal", form: "rounded", label: "Switch (Command Router)", pos: "b", h: 48 }
n23["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Code (Parse Budget Amount)"]
n24@{ icon: "mdi:swap-horizontal", form: "rounded", label: "IF (Budget ok?)", pos: "b", h: 48 }
n25["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/telegram.svg' width='40' height='40' /></div><br/>Telegram → Budget Error"]
n26@{ icon: "mdi:database", form: "rounded", label: "Google Sheets → Append or up..", pos: "b", h: 48 }
n27["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/telegram.svg' width='40' height='40' /></div><br/>Telegram → Budget Updated"]
n28@{ icon: "mdi:database", form: "rounded", label: "GS - Get Daily Report Range", pos: "b", h: 48 }
n29["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Code - Build Daily Report"]
n30["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/telegram.svg' width='40' height='40' /></div><br/>TG - Send Daily Report"]
n31["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Code (Schedule Report Token)"]
n32["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/telegram.svg' width='40' height='40' /></div><br/>Telegram Trigger"]
n33["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Code - Check Latest Token"]
n34@{ icon: "mdi:swap-horizontal", form: "rounded", label: "If", pos: "b", h: 48 }
n35@{ icon: "mdi:cog", form: "rounded", label: "ReportTokens", pos: "b", h: 48 }
n36@{ icon: "mdi:cog", form: "rounded", label: "Data table → Get row(s)", pos: "b", h: 48 }
n37@{ icon: "mdi:cog", form: "rounded", label: "Data table → Delete row(s)", pos: "b", h: 48 }
n38@{ icon: "mdi:cog", form: "rounded", label: "Wait", pos: "b", h: 48 }
n39@{ icon: "mdi:swap-vertical", form: "rounded", label: "CONFIG - User Settings", pos: "b", h: 48 }
n34 --> n37
n38 --> n36
n35 --> n38
n24 --> n26
n24 --> n25
n32 --> n39
n9 --> n7
n9 --> n14
n9 --> n13
n1 --> n3
n4 --> n5
n10 --> n11
n16 --> n17
n39 --> n22
n11 --> n18
n22 --> n23
n22 --> n0
n6 --> n9
n29 --> n30
n33 --> n34
n36 --> n33
n2 --> n16
n2 --> n10
n2 --> n4
n23 --> n24
n28 --> n29
n18 --> n15
n17 --> n19
n31 --> n35
n37 --> n28
n19 --> n20
n15 --> n12
n7 --> n8
n3 --> n2
n8 --> n31
n5 --> n21
n21 --> n6
n20 --> n6
n12 --> n6
n0 --> n1
n26 --> n27
end
%% Styling
classDef trigger fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
classDef ai fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
classDef aiModel fill:#e8eaf6,stroke:#3f51b5,stroke-width:2px
classDef decision fill:#fff8e1,stroke:#f9a825,stroke-width:2px
classDef database fill:#fce4ec,stroke:#c2185b,stroke-width:2px
classDef api fill:#fff3e0,stroke:#e65100,stroke-width:2px
classDef code fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
classDef disabled stroke-dasharray: 5 5,opacity: 0.5
class n32 trigger
class n5,n15,n19 ai
class n1,n2,n9,n22,n24,n34 decision
class n0,n8,n26,n28 database
class n3,n6,n7,n11,n12,n20,n21,n23,n29,n31,n33 code
classDef customIcon fill:none,stroke:none
class n3,n6,n7,n11,n12,n13,n14,n17,n18,n20,n21,n23,n25,n27,n29,n30,n31,n32,n33 customIcon
The Problem: Expense tracking dies in the data entry
Most expense apps fail for one boring reason: you have to open them. Unlock your phone, find the app, wait for it to load, pick a category, type an amount, pick a currency, save. It’s not “hard,” it’s just annoying enough that small purchases don’t get logged. Those small purchases add up, and the bigger cost is mental. You carry a constant “I should track this” guilt, then your spreadsheet is missing half the story when you actually need it.
It adds up fast. Here’s where it breaks down in real life.
- You forget the small stuff (parking, coffee, tips), and those are usually the most frequent.
- Manual categorizing takes longer than it should, so you skip it or guess, which makes reports useless later.
- Receipt photos live in your camera roll with no structure, so you cannot search, filter, or summarize them.
- Budget tracking becomes a once-a-month scramble instead of a calm, daily habit.
The Solution: Log expenses in Telegram, auto-write to Google Sheets
This n8n workflow turns Telegram into your fastest expense input. You message a Telegram bot with whatever you have in the moment: plain text (“Lunch 12, Taxi 8”), a voice note while you’re driving, or a photo of a receipt. The workflow detects what you sent, then uses an AI model to extract the merchant/item, amount, currency, and a sensible category. If you list multiple items in one message, it intelligently splits them into separate expenses. Finally, it appends clean, structured rows into your Google Sheet, where the data is fully yours and ready for filtering, pivot tables, or a dashboard.
It starts with a Telegram message and routes by input type (text, voice, photo). AI handles extraction and currency detection, then n8n normalizes the result and writes it to Google Sheets. After that, a “quiet” reporting layer waits for about 30 minutes of inactivity before sending a tidy summary, so you don’t get spammed after every entry.
What You Get: Automation vs. Results
| What This Workflow Automates | Results You’ll Get |
|---|---|
|
|
Example: What This Looks Like
Say you log 6 expenses a day. In a typical expense app, even a “quick” entry is maybe 1 minute once you unlock, open, category-pick, and save, so that’s about 6 minutes daily (and realistically, you skip a few). With this workflow, you can send “Coffee 5, Metro 3” in one Telegram message in about 10 seconds, and the workflow splits it into two rows. Add one receipt photo that takes another 10 seconds. You’re done in under a minute, and your sheet stays accurate without you thinking about it.
What You’ll Need
- n8n instance (try n8n Cloud free)
- Self-hosting option if you prefer (Hostinger works well)
- Telegram to send texts, voice notes, and photos
- Google Sheets to store your expense ledger
- Gemini API key (get it from Google AI Studio / Google Cloud)
Skill level: Intermediate. You’ll connect accounts, copy a Sheet template, and edit a few configuration fields in n8n.
Don’t want to set this up yourself? Talk to an automation expert (free 15-minute consultation).
How It Works
A Telegram message triggers everything. When your bot receives a message, the workflow loads your user settings (spreadsheet ID, currency defaults, locale formatting) and decides if this is an expense log or a command like “/add budget 500.”
The workflow detects the input type. Text gets sent straight to AI extraction. Voice notes are fetched from Telegram first, then analyzed. Receipt photos go through “pick best photo,” normalization, then image analysis (OCR-style parsing).
AI extracts structured fields you can actually use. The output is normalized and parsed into consistent JSON, then the workflow validates that it found at least one expense. If you wrote multiple items, it splits them into separate rows so your sheet stays tidy.
Google Sheets is updated and you get a clean confirmation. Expenses are appended as rows, budgets can be updated in a separate tab, and Telegram replies with success (or a helpful error). A reporting token + wait logic makes sure you get one summary after about 30 minutes of inactivity, not constant pings.
You can easily modify categories to match your chart of accounts based on your needs. See the full implementation guide below for customization options.
Step-by-Step Implementation Guide
Step 1: Configure the Telegram Trigger
This workflow starts when a user sends a Telegram message (text, photo, or voice) to your bot.
- Add and open Telegram Incoming Trigger.
- Credential Required: Connect your telegramApi credentials.
- Keep Updates set to
messageto capture text, photo, and voice inputs. - Verify the trigger is connected to User Config Settings as shown in the execution flow.
Step 2: Connect Google Sheets
The workflow logs expenses, budget updates, and daily report data to Google Sheets.
- Open User Config Settings and set your sheet IDs: spreadsheet_id to
Input your Spread Sheet ID here(replace with your actual ID), sheet_gid_log togid=0(or your log sheet ID), sheet_gid_dashboard toInput your Sheet "Dashboard" ID here, and sheet_gid_budget toInput your Sheet "Budget Topups" ID here. - In Fetch Sheet Rows for Dedup, confirm the Document is
{{ $('User Config Settings').item.json.spreadsheet_id }}and Sheet is{{ $('User Config Settings').item.json.sheet_gid_log }}. - In Append Rows to Sheet, verify column mappings like Date →
{{ $json.date }}and Update_ID →{{ $json.update_id }}. - In Fetch Daily Report Range, set Range to
D1:I2. - In Update Budget Sheet Row, keep operation as
appendand verify sheetName uses{{ Number($('User Config Settings').item.json.sheet_gid_budget) }}. - Credential Required: Connect your googleSheetsOAuth2Api credentials to all Google Sheets nodes (including Fetch Sheet Rows for Dedup, Append Rows to Sheet, Fetch Daily Report Range, and Update Budget Sheet Row).
Step 3: Configure Routing and Deduplication
Incoming messages are deduplicated and routed by command and content type.
- In Command Routing Switch, keep the regex checks for budget commands:
/^\/add(?:@\w+)?\s+budget\b/i.test(($json.message?.text || '').trim()). - Confirm Fetch Sheet Rows for Dedup filters by Update_ID using
{{ $json.update_id }}, then passes into Duplicate Check Branch. - In Duplicate Check Branch, ensure the condition checks
{{ $json.Message_ID }}for non-empty so duplicates are blocked. - Verify Script Restore Telegram Data restores the original Telegram JSON so Route by Input Type can inspect
message.voice,message.photo, andmessage.text. - Confirm Route by Input Type outputs to Assign Voice Context, Assign Photo Context, or Assign Text Context based on message type.
Step 4: Set Up AI Processing (Gemini Text, Image, Audio)
Gemini nodes extract structured expense data from text, receipt photos, and voice notes.
- In Assign Text Context, ensure raw_input is
{{ $json.message.text }}and now uses{{ $now.setZone('Asia/Ho_Chi_Minh').toFormat('yyyy-LL-dd HH:mm:ss') }}. - In Assign Photo Context, keep caption and raw_input as
{{ $json.message.caption || "[photo]" }}. - In Assign Voice Context, keep file_id as
{{ $json.message.voice.file_id }}and raw_input as[voice]. - Credential Required: Connect your googlePalmApi credentials in Gemini Text Extraction, Gemini Image Analysis, and Gemini Audio Analysis.
- Verify Gemini Text Extraction uses model
models/gemini-2.5-flashand includes the prompt containing{{ $json.now }}and{{ $('User Config Settings').item.json.currency_code }}. - Ensure Gemini Image Analysis uses resource
image, inputTypebinary, and the receipt extraction prompt. - Ensure Gemini Audio Analysis uses resource
audio, inputTypebinary, and the transcription + extraction prompt.
Step 5: Process Expenses and Write to Sheets
Parsed expenses are validated, split into rows, and appended to your log sheet, then a report token is created.
- Keep Script Parse Gemini JSON connected after each normalize node to validate strict JSON from Gemini.
- In Validate Expenses Present, retain the condition
{{ ($json.expenses || []).length > 0 }}. - Validate Expenses Present outputs to both Script Split Expense Items and Telegram Success Reply in parallel.
- In Append Rows to Sheet, confirm column mappings for Item, Amount, Category, and Payment_Method match your sheet headers.
- Ensure Script Create Report Token follows Append Rows to Sheet and feeds Report Token Table.
Step 6: Configure Budget Command Handling
Budget updates are parsed from the “/add budget” command and written to a separate sheet.
- Keep Command Routing Switch connected to Script Parse Budget Value for the budget command branch.
- In Script Parse Budget Value, leave the parsing logic as-is and ensure it uses
{{ $('User Config Settings').item.json.currency_symbol }}. - Verify Budget Validation Check evaluates
{{ $json.ok }}as a boolean. - In Update Budget Sheet Row, ensure ts uses
{{ new Date().toLocaleString('vi-VN', { timeZone: 'Asia/Ho_Chi_Minh' }) }}. - Confirm success and error replies are handled by Telegram Budget Updated and Telegram Budget Error.
Step 7: Schedule and Send Daily Reports
After logging expenses, a report token is stored and a delayed check determines whether to send a daily report.
- In Report Token Table, set dataTableId to your actual Data Table ID (replace
[YOUR_ID]). - Ensure Report Token Table → Delay Timer remains in place, and Delay Timer uses amount
30and unitminutes. - Update Data Table Fetch Rows and Data Table Remove Rows to use the same Data Table ID as Report Token Table.
- Confirm Script Verify Latest Token checks
{{ $node["Delay Timer"].json.report_token }}and passes to Conditional Send Gate. - When Conditional Send Gate is true, ensure it flows to Data Table Remove Rows → Fetch Daily Report Range → Script Build Daily Report → Telegram Send Daily Report.
Step 8: Configure Telegram Responses
Telegram nodes send confirmation, error, and report messages back to the user.
- Credential Required: Connect your telegramApi credentials to all Telegram nodes (including Telegram Success Reply, Telegram Error Reply, Telegram Budget Updated, Telegram Budget Error, Telegram Fetch Voice File, Telegram Fetch Image File, and Telegram Send Daily Report).
- In Telegram Success Reply, keep text as
{{ $json.summary_text }}and chatId as{{ $json.chat_id }}. - In Telegram Error Reply, keep text as
⚠️ Could not understand expenses in: "{{$json.raw_input}}"and chatId as{{ $('Telegram Incoming Trigger').item.json.message.chat.id }}. - In Telegram Send Daily Report, keep chatId as
{{ $node["Telegram Incoming Trigger"].json.message.chat.id }}.
Step 9: Test and Activate Your Workflow
Run end-to-end tests with different input types and confirm the correct responses, sheet updates, and report scheduling.
- Click Execute Workflow and send a test Telegram message: a text expense like
Lunch 10k, a receipt photo, and a voice note. - Confirm Route by Input Type routes correctly and Gemini nodes produce structured JSON that passes Validate Expenses Present.
- Verify new rows appear in the log sheet from Append Rows to Sheet and budget entries append in Update Budget Sheet Row when using
/add budget 500k. - Check Telegram replies from Telegram Success Reply, Telegram Error Reply, Telegram Budget Updated, and Telegram Budget Error.
- Wait 30 minutes (or temporarily reduce the Delay Timer to
1minute) and confirm a daily report is sent by Telegram Send Daily Report when the token matches. - When everything works, switch the workflow to Active to enable production use.
Common Gotchas
- Telegram credentials can expire or the bot can lose permissions. If things break, check your Telegram bot token in n8n credentials and confirm the bot is still in the right chat.
- If you’re using Wait nodes or external processing (voice/photo analysis), processing times vary. Bump up the wait duration if downstream nodes fail on empty responses.
- Default prompts in AI nodes are generic. Add your brand voice (categories, merchant rules, “what counts as an expense”) early or you’ll be editing outputs forever.
Frequently Asked Questions
About 45 minutes if you already have Telegram and Google Sheets ready.
No. You’ll mostly connect accounts and paste in a few IDs from your Google Sheet.
Yes. n8n has a free self-hosted option and a free trial on n8n Cloud. Cloud plans start at $20/month for higher volume. You’ll also need to factor in Gemini API costs (it’s currently free or very cheap for many use cases).
Two options: n8n Cloud (managed, easiest setup) or self-hosting on a VPS. For self-hosting, Hostinger VPS is affordable and handles n8n well. Self-hosting gives you unlimited executions but requires basic server management.
Yes, and you should. Update the defaults in the “CONFIG – User Settings” node (currency code, symbol, locale) and adjust the AI extraction prompt in the Gemini text/image/audio nodes to match your category list. Common tweaks include adding “Client meals” vs “Team meals,” forcing certain merchants into fixed categories, and setting a default currency for ambiguous amounts.
Usually it’s an expired bot token or the wrong chat context. Re-check your Telegram credentials in n8n, then confirm the bot can still receive messages from the chat you’re testing in. If voice/photo steps fail, it can also be Telegram file access not returning properly, so look at the Telegram “fetch file” nodes first. And yes, rate limits happen, especially if you dump a bunch of photos at once.
A lot. On n8n Cloud Starter you’re limited by monthly executions, while self-hosting is mainly limited by your server and API quotas. In practice, this workflow is fine for personal use and small teams logging dozens of expenses per day, even with receipt photos.
Often, yes. This workflow isn’t just “send a message to a sheet.” It routes different input types, calls AI for extraction, splits multiple items, de-dupes against existing rows, and runs a debounce-style daily report using a data table and a wait timer. That kind of logic is doable in Zapier or Make, but it tends to get expensive and fiddly as soon as you add branching and storage. If you only need a simple two-step “Telegram text → one row,” other tools can be quicker. Talk to an automation expert if you want a recommendation for your exact setup.
Once this is running, expense tracking stops being a “task” and becomes a quick message you barely think about. The workflow handles the repetitive cleanup, and your Google Sheet stays ready for real decisions.
Need Help Setting This Up?
Our automation experts can build and customize this workflow for your specific needs. Free 15-minute consultation—no commitment required.