This workflow corresponds to n8n.io template #10155 β we link there as the canonical source.
This workflow follows the Agent β Googlegemini recipe pattern β see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide β
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "7be734f2-0de6-4763-81e5-346cb715bd66",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1664,
624
],
"parameters": {
"width": 896,
"height": 928,
"content": "# \ud83d\ude80 Quick Start\n\n## \ud83d\udccb Workflow Overview\n- **Ingest** Telegram messages as the source (text, voice, image).\n- **Route** by type and **validate** supported formats (text/voice/photo only).\n- **Transform** voice to text and **extract** text from images via Gemini.\n- **Parse** free-form content into structured expense entries (`amount`, `item`, `date`).\n- **Write** one entry per row to Google Sheets .\n- **Notify** the user on Telegram with a confirmation summary.\n\n## \ud83d\udd04 How it works\n1) **Trigger** on each new Telegram message (event-driven). \n2) **Detect & route**: \n - Text \u2192 pass through. \n - Voice \u2192 download file \u2192 transcribe to text via Gemini. \n - Image \u2192 download file \u2192 extract readable text via Gemini. \n3) **Normalize** all paths into a single `raw_text` field for processing. \n4) **Extract** a strict JSON array of `{amount, item, date}` via LLM (date defaults to \u201ctoday\u201d when not explicit). \n5) **Auto-fix** malformed JSON and **enforce** the expected schema. \n6) **Split & batch** parsed entries so each entry becomes an individual row. \n7) **Append/Update** rows in Google Sheets. \n8) **Send** a Telegram status message listing `item \u2014 amount (date)`; **fail fast** with a friendly error for unsupported inputs.\n\n## \u2705 Prerequisites\n### \ud83d\udd12 Credentials\n- **Telegram**: Create a bot via BotFather \u2192 copy the **Bot Token** \u2192 add the credential in n8n.\n- **Google Sheets OAuth2**: Add an OAuth2 credential in n8n and ensure the target spreadsheet is accessible.\n- **Gemini (Google AI)**: Add an API credential in n8n for voice transcription, image analysis, and the expense parser nodes.\n### \ud83d\udee0\ufe0f Setup\n- **Create Google Sheet**: Create a **Log Expense** Google Sheet.\n- **Point the workflow:** Configure the newly created **Google Sheet** in n8n to be used by **GSheet: Log Expense**.\n- **Telegram chat ID**: In **Telegram: Send Confirmation**, set `chatId`.\n- **Enable the workflow**: Turn on the workflow so it listens for Telegram messages.\n\n"
},
"typeVersion": 1
},
{
"id": "dba88ead-1ca9-488d-9cb2-c1fdc4156dce",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-688,
560
],
"parameters": {
"color": 2,
"width": 672,
"height": 992,
"content": "## 1) Message Intake & Routing\n**Purpose:** Receive user messages and route by input type. \n**Covers nodes:** `Start: On Telegram Message` \u2192 `Route: Text, Voice or Image?` \u2192 (`Telegram: Send error message` on unsupported input) \n**What it does:**\n- Listens for Telegram messages.\n- Detects **text / voice / image** and sends each down the right path.\n- Gracefully replies if the message type isn\u2019t supported.\n\n**Inputs consumed:** Telegram `message` (text/voice/photo). \n**Output produced:** Routed payload for the chosen channel (text, voice, or image)."
},
"typeVersion": 1
},
{
"id": "67cb65eb-65c3-4e83-a5db-8fc0a227513a",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
560
],
"parameters": {
"color": 3,
"width": 688,
"height": 992,
"content": "## 2) Input Capture & Normalization\n**Purpose:** Turn everything into plain text for parsing. \n**Covers nodes:** \n`Set: Text Input` \n`Telegram: Get Voice File` \u2192 `Gemini: Transcribe Voice` \u2192 `Set: Voice Input (Text)` \n`Telegram: Get Image File` \u2192 `Gemini: Analyze an image` \u2192 `Set: image Input (Text)` \n**What it does:**\n- Text: uses the message directly.\n- Voice: downloads the audio and transcribes it to text.\n- Image: downloads the photo and extracts financial text (e.g., amounts, dates, merchant).\n- Standardizes to a single **`raw_text`** string regardless of source.\n\n**Inputs consumed:** Telegram text, voice (binary), image (binary). \n**Output produced:** `raw_text` (string)."
},
"typeVersion": 1
},
{
"id": "bbcaf572-fe02-49d9-bb73-9e0229c4e931",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
720,
560
],
"parameters": {
"color": 5,
"width": 432,
"height": 992,
"content": "## 3) Expense Parsing & Validation\n**Purpose:** Convert free-form text into structured expenses. \n**Covers nodes:** `AI: Extract Expenses`\n**What it does:**\n- Parses `raw_text` into a strict JSON array of **{ amount, item, date }**.\n- Handles multiple expenses in one message and normalizes dates/amounts.\n- Validates/fixes JSON so downstream steps get clean structure.\n\n**Inputs consumed:** `raw_text`. \n**Output produced:** `output` (JSON array of expense objects)."
},
"typeVersion": 1
},
{
"id": "40c74ab6-4079-475d-89d1-c861ca778c94",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1168,
560
],
"parameters": {
"color": 4,
"width": 1056,
"height": 992,
"content": "## 4) Row Generation, Sheet Write & Confirmation\n**Purpose:** Turn parsed entries into rows, save them, and notify the user. \n**Covers nodes:** `Split: Entries to Rows` \u2192 `If null?` \u2192 `Set: Extract Output` \u2192 `GSheet: Log Expense` \u2192 `Telegram: Send Confirmation` /**Fallback on empty:** `Telegram: Send Null`\n- **What it does:**\n- Splits the output array so each expense becomes a single item. \n- (Optional) Map fields to column headers (Amount, Item, Date) in Set: Extract Output. \n- Appends rows in Google Sheets. \n- Sends a brief Telegram summary; if no items, sends a null/empty notice. \n\n**Inputs consumed:** output (JSON array of expenses) \n**Output produced:** New rows in Google Sheets + a Telegram confirmation (or empty notice)\n"
},
"typeVersion": 1
},
{
"id": "5ca4b51a-2d7e-46f9-82a5-081b0cb970fd",
"name": "GSheet: Log Expense",
"type": "n8n-nodes-base.googleSheets",
"position": [
1824,
1056
],
"parameters": {
"columns": {
"value": {},
"schema": [
{
"id": "output",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "output",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "autoMapInputData",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {
"cellFormat": "RAW"
},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 161899167,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1GRwglX459qYYAQm_gRZadyDwVR1dw8t6zuZU3T-T-Ak/edit#gid=161899167",
"cachedResultName": "test"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1GRwglX459qYYAQm_gRZadyDwVR1dw8t6zuZU3T-T-Ak",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1GRwglX459qYYAQm_gRZadyDwVR1dw8t6zuZU3T-T-Ak/edit?usp=drivesdk",
"cachedResultName": "\u8bb0\u8d26"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 4.7
},
{
"id": "297ff453-2268-40ba-95c3-fa7a3e736467",
"name": "LLM: Gemini Flash",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
768,
1408
],
"parameters": {
"options": {}
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "9519104e-7bb0-4b48-aa2c-cd416266a9de",
"name": "Gemini: Transcribe Voice",
"type": "@n8n/n8n-nodes-langchain.googleGemini",
"position": [
272,
1072
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "models/gemini-2.5-flash",
"cachedResultName": "models/gemini-2.5-flash"
},
"options": {},
"resource": "audio",
"inputType": "binary",
"binaryPropertyName": "=data "
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 1
},
{
"id": "8bd5377e-39ef-4af7-846d-1d3a279151c2",
"name": "Telegram: Send Confirmation",
"type": "n8n-nodes-base.telegram",
"position": [
2016,
1056
],
"parameters": {
"text": "={{\n(() => {\n const items = $input.all();\n\n const arr = items\n .map(i => i.json?.output ?? i.json)\n .filter(Boolean);\n\n const lines = arr.map(e => {\n const item = (e?.Item ?? '').toString().trim() || '\u2014';\n const amount = (e?.Amount ?? '').toString().trim() || '\u2014';\n const date = e?.Date ? ` (${e.Date})` : '';\n return `\u2022 ${item} \u2014 ${amount}${date}`;\n });\n\n const text =\n '\ud83d\udcd2 Bookkeeping Complete\\n\\n' +\n (lines.length ? lines.join('\\n') : '(No items found)');\n\n // Telegram MarkdownV2 \u9700\u8981\u8f6c\u4e49\u7684\u5b57\u7b26\uff1a\n // _ * [ ] ( ) ~ ` > # + - = | { } . ! \\\n const escapeMdV2 = (s) =>\n s.replace(/([_*[\\]()~`>#+\\-=|{}.!\\\\])/g, '\\\\$1');\n\n return escapeMdV2(text);\n})()\n}}\n",
"chatId": "<Your chat id>",
"additionalFields": {
"parse_mode": "MarkdownV2"
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"executeOnce": true,
"retryOnFail": true,
"typeVersion": 1.2,
"waitBetweenTries": 3000
},
{
"id": "ec454606-5cc7-45ff-a8fe-a8ecd39b7b13",
"name": "Telegram: Get Voice File",
"type": "n8n-nodes-base.telegram",
"position": [
64,
1072
],
"parameters": {
"fileId": "={{ $json.message.voice.file_id }}",
"resource": "file",
"additionalFields": {}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 1.2
},
{
"id": "ab30a0a4-0f88-437a-b75f-488eb11eb38b",
"name": "Set: Text Input",
"type": "n8n-nodes-base.set",
"position": [
384,
912
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "bed1c3bb-9ee7-4663-b89a-8a701df2c049",
"name": "raw_text",
"type": "string",
"value": "={{ $json.message.text }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "9850615f-f28d-4b68-8fe2-fbf704172667",
"name": "Set: Voice Input (Text)",
"type": "n8n-nodes-base.set",
"position": [
496,
1072
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "82a0d6b4-f320-4b39-b05a-a2723f4d22fa",
"name": "raw_text",
"type": "string",
"value": "={{ $json.content.parts.toJsonString() }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "8cbe3328-cdd1-4773-90ad-4822859ddba1",
"name": "AI: Extract Expenses",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
832,
1040
],
"parameters": {
"text": "=information:{{ $json.raw_text }}",
"options": {
"systemMessage": "=You are a STRICT, deterministic expense parser. The user will provide free-form text or an ASR (voice) transcription that describes one or more bookkeeping entries.\n\nGOAL\nReturn a JSON ARRAY of 0..N objects. Each object has these EXACT keys:\n{\n \"amount\": number | null, // numeric value only; no currency symbol\n \"item\": string | null, // what was paid for / received\n \"date\": date // ISO 8601 \"YYYY-MM-DD\"; \n\nCURRENT DATE CONTEXT (REQUIRED)\n- NOW_ISO = \"{{ new Date }}\" \n- Define CURRENT_DATE as the calendar date of NOW_ISO (YYYY-MM-DD) respecting its timezone info if present.\n\nHARD RULES (NO HALLUCINATIONS)\n- Work ONLY with the given input text; DO NOT invent, infer, or fetch anything external.\n- If a field cannot be reliably determined, set it to null (do NOT guess). Exception: \"date\" must never be null\u2014if no explicit/relative date is found, use CURRENT_DATE.\n- Output MUST be raw JSON array only (no markdown, no comments, no extra text).\n- Each distinct purchase must yield its own object. Do NOT merge separate purchases. Do NOT compute totals from line items unless the text explicitly marks a \u201ctotal\u201d.\n- Amounts are positive numbers; do NOT add signs to indicate expense/income.\n\nMULTI-ENTRY DETECTION\nTreat as separate purchases when the text clearly presents distinct (amount, item) pairs or clearly separated entries by:\n- Newlines, bullet points, semicolons/commas that separate complete (amount + item) phrases, or connectors like \u201cand/&, plus\u201d when each part has its own amount or distinct item.\n- Examples of separate entries:\n - \"Lunch 12; Grab 18; Netflix 55\" \u2192 three objects.\n - \"RM8 coffee, RM5 bun\" \u2192 two objects.\nNOT separate (single entry):\n - A list of items but ONE explicit \"total\" (e.g., \"apples 3, milk 5, total 8\") \u2192 output ONE object with amount=8 and item=apples & milk.\n - Quantity/price math for the same item (e.g., \"2 x coffee RM5 each, total RM10\") \u2192 ONE object, amount=10, item=\"coffee\".\nIf multiple amounts exist but items are not clearly distinguishable, create multiple objects with item=null rather than guessing item names.\n\nAMOUNT PARSING\n- Normalize to a JSON number (strip currency symbols/codes/words): RM12.50 \u2192 12.5, $1,299 \u2192 1299, MYR 36 \u2192 36.\n- Support magnitude suffixes: 1.2k/1.2K \u2192 1200; 36k/36K \u2192 36000.\n- Prefer an explicit total; if both unit price and total appear for the same purchase, choose the total.\n- Ignore percentages unless explicitly stated as the paid amount.\n- If multiple numeric candidates exist within one purchase with no clear total, choose the first well-formed currency-like amount; otherwise amount = null.\n\nITEM EXTRACTION\n- Concise description of what the payment is for (e.g., \"coffee\", \"Grab ride\", \"rental\", \"Netflix subscription\").\n- Remove ASR fillers (\u201cuh\u201d, \u201cyou know\u201d), keep core item only.\n- If truly unknown, item = null.\n\nDATE RESOLUTION\n- Always output ISO \"YYYY-MM-DD\".\n- Recognize explicit dates in common formats and normalize (e.g., 22/10/2025, 2025-10-22, Oct 22, 2025). If month/day are given without a year and it is unambiguous, fill the year from CURRENT_DATE; otherwise treat missing parts as absent and use CURRENT_DATE.\n- Relative terms (multi-language; case-insensitive):\n - \"today\", \"\u4eca\u5929\", \"hari ini\" \u2192 CURRENT_DATE\n - \"yesterday\", \"\u6628\u5929\", \"semalam\" \u2192 CURRENT_DATE minus 1 day\n - \"tomorrow\", \"\u660e\u5929\", \"esok\" \u2192 CURRENT_DATE plus 1 day\n - \"the day before yesterday\" / \"\u524d\u5929\" \u2192 CURRENT_DATE minus 2 days\n- If a single relative date clearly applies to the whole input, apply it to all entries. If different parts contain different explicit/relative dates, resolve per entry. If no date cue for an entry, use CURRENT_DATE.\n\nNORMALIZATION & SANITY\n- Trim whitespace; tolerate ASR noise and mixed punctuation (including full-width).\n- Do not include currency symbols/codes anywhere in JSON.\n\nOUTPUT FORMAT\n- Return exactly one JSON array. Each array element is an object with keys in this order: amount, item, date.\n- Numbers must be valid JSON numbers (no quotes).\n\nEXAMPLES (assume NOW_ISO = \"2025-10-22T15:00:00+08:00\", so CURRENT_DATE = \"2025-10-22\")\n\nInput: \"Paid RM12.50 for latte at Starbucks today\"\nOutput:\n[{\"amount\":12.5,\"item\":\"latte at Starbucks\",\"date\":\"2025-10-22\"}]\n\nInput: \"Grab ride to Mid Valley, 18 ringgit, yesterday; Netflix monthly 55\"\nOutput:\n[\n {\"amount\":18,\"item\":\"Grab ride to Mid Valley\",\"date\":\"2025-10-21\"},\n {\"amount\":55,\"item\":\"Netflix monthly\",\"date\":\"2025-10-22\"}\n]\n\nInput: \"RM8 coffee, RM5 bun\"\nOutput:\n[\n {\"amount\":8,\"item\":\"coffee\",\"date\":\"2025-10-22\"},\n {\"amount\":5,\"item\":\"bun\",\"date\":\"2025-10-22\"}\n]\n\nInput: \"apples 3, milk 5, TOTAL 8\"\nOutput:\n[{\"amount\":8,\"item\":\"apples & milk\",\"date\":\"2025-10-22\"}]\n"
},
"promptType": "define",
"hasOutputParser": true
},
"retryOnFail": true,
"typeVersion": 2.2
},
{
"id": "cd20d803-47a3-496b-8160-0052c6b50ef4",
"name": "Split: Entries to Rows",
"type": "n8n-nodes-base.splitOut",
"position": [
1216,
1040
],
"parameters": {
"include": "allOtherFields",
"options": {},
"fieldToSplitOut": "output"
},
"executeOnce": false,
"typeVersion": 1,
"alwaysOutputData": true
},
{
"id": "a1e5d645-da07-479c-84c7-5e6ad67d2f7c",
"name": "Telegram: Send error message",
"type": "n8n-nodes-base.telegram",
"position": [
-144,
1392
],
"parameters": {
"text": "=\ud83e\udd16Sorry, I can only understand text, voice and image for bookkeeping. ",
"chatId": "<Your chat id>",
"additionalFields": {}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"executeOnce": true,
"retryOnFail": true,
"typeVersion": 1.2
},
{
"id": "076077d4-a0c9-4800-aa1e-1f455255a8db",
"name": "Gemini: Analyze an image",
"type": "@n8n/n8n-nodes-langchain.googleGemini",
"position": [
272,
1264
],
"parameters": {
"text": "=Financial Image Text Extraction Prompt\n\nYour task is to analyze the provided image and **extract all text and numerical information related to financial or transactional data**. \nThe image may contain receipts, invoices, transfer records, expense summaries, or digital payment screenshots (e.g., WeChat, Alipay, banking apps).\n\n#### Instructions:\n- Extract **all visible text and numbers**, including:\n - Dates and times \n - Merchant or transaction party names \n - Notes, descriptions, or remarks \n - Amounts, currency symbols, and positive/negative signs \n - Summary information (e.g., total income, total expense)\n- **Preserve the original order and wording** as they appear in the image. \n- Ensure numerical accuracy and retain all currency symbols. \n- **Ignore decorative or non-informational elements**, such as logos, icons, watermarks, background graphics, or design borders. \n- Do **not** interpret, categorize, or restructure the extracted data. \n The output should be **raw readable text** that captures all relevant words and numbers for bookkeeping analysis.\n\n#### Goal:\nProduce an **accurate, unmodified textual extraction** of the financial and accounting information visible in the image \u2014 ready for further processing by a bookkeeping or accounting system.\n",
"modelId": {
"__rl": true,
"mode": "list",
"value": "models/gemini-2.5-flash",
"cachedResultName": "models/gemini-2.5-flash"
},
"options": {},
"resource": "image",
"inputType": "binary",
"operation": "analyze"
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 1
},
{
"id": "fe77a802-f56e-4fae-ae7c-0c6dabf43d2c",
"name": "Set: image Input (Text)",
"type": "n8n-nodes-base.set",
"position": [
496,
1264
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "82a0d6b4-f320-4b39-b05a-a2723f4d22fa",
"name": "raw_text",
"type": "string",
"value": "={{ $json.content.parts.toJsonString() }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "3c907845-b556-4a13-a93b-1319e953e001",
"name": "Route: Text, Voice or Image?",
"type": "n8n-nodes-base.switch",
"position": [
-384,
1136
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "Text",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a3e1bf10-5e6d-4004-87f9-b0f6e8c421c8",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ !!$json.message.text }}",
"rightValue": "text"
}
]
},
"renameOutput": true
},
{
"outputKey": "Voice",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "7de0d37c-e95d-4780-958b-25fe7a851243",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ !!$json.message.voice }}",
"rightValue": "voice"
}
]
},
"renameOutput": true
},
{
"outputKey": "Image",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "f85ee560-74b1-47ab-ac61-06081de1a14f",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ !!$json.message.photo }}",
"rightValue": "photo"
}
]
},
"renameOutput": true
}
]
},
"options": {
"fallbackOutput": "extra",
"allMatchingOutputs": false,
"renameFallbackOutput": "else"
}
},
"typeVersion": 3.3
},
{
"id": "45416109-6e88-4b09-8327-bea0d9878b7e",
"name": "Auto-Fix JSON",
"type": "@n8n/n8n-nodes-langchain.outputParserAutofixing",
"position": [
880,
1248
],
"parameters": {
"options": {
"prompt": "Instructions:\n--------------\n{instructions}\n--------------\nCompletion:\n--------------\n{completion}\n--------------\n\nAbove, the Completion did not satisfy the constraints given in the Instructions.\nError:\n--------------\n{error}\n--------------\n\nPlease try again. Please only respond with an answer that satisfies the constraints laid out in the Instructions:"
}
},
"typeVersion": 1
},
{
"id": "6c8d0ca0-9e88-41d7-9692-32b3751c70b0",
"name": "AI Helper: Define Schema",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
992,
1392
],
"parameters": {
"jsonSchemaExample": "[\n {\n \"amount\": \"number | null\",\n \"item\": \"string | null\", \n \"date\": \"date\"\n }\n]"
},
"typeVersion": 1.3
},
{
"id": "95435178-b029-4319-a886-7a0aef028363",
"name": "Telegram: Get Image File",
"type": "n8n-nodes-base.telegram",
"position": [
64,
1264
],
"parameters": {
"fileId": "={{ $json.message.photo[0].file_id }}",
"resource": "file",
"additionalFields": {}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 1.2
},
{
"id": "a0d74a66-0e2f-460a-ada8-c52c1a38535b",
"name": "Start: On Telegram Message",
"type": "n8n-nodes-base.telegramTrigger",
"position": [
-608,
1168
],
"parameters": {
"updates": [
"message"
],
"additionalFields": {}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "44724e36-7f53-4e42-b588-c7a808f57f70",
"name": "Set: Extract Output",
"type": "n8n-nodes-base.set",
"position": [
1632,
1056
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "6b917f33-f6c0-4a49-b12c-6861238874b6",
"name": "Amount",
"type": "string",
"value": "={{ $json.output.amount }}"
},
{
"id": "f029fc1b-937a-432c-9832-fa1a0b7717ed",
"name": "Item",
"type": "string",
"value": "={{ $json.output.item }}"
},
{
"id": "e7908c7b-e1a5-4854-b6d2-7a57d90521b6",
"name": "Date",
"type": "string",
"value": "={{ $json.output.date }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "b2122f43-7eaf-4d09-ba73-b756fa8d2863",
"name": "If null?",
"type": "n8n-nodes-base.if",
"position": [
1424,
1152
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "or",
"conditions": [
{
"id": "2afe62ea-0a1b-4836-a1d4-7863e35e7f39",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ !!$json.output }}",
"rightValue": ""
},
{
"id": "c8509d26-8c3c-4e43-8870-47691e631d8d",
"operator": {
"type": "array",
"operation": "contains",
"rightType": "any"
},
"leftValue": "={{ $json.output }}",
"rightValue": "null"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "b4d6dc9a-d623-455d-8fd5-a2f757229a07",
"name": "Telegram: Send Null",
"type": "n8n-nodes-base.telegram",
"position": [
1616,
1280
],
"parameters": {
"text": "=\ud83d\udcd2 Bookkeeping Complete\n\nnull",
"chatId": "<Your chat id>",
"additionalFields": {}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"executeOnce": true,
"retryOnFail": true,
"typeVersion": 1.2
}
],
"connections": {
"If null?": {
"main": [
[
{
"node": "Set: Extract Output",
"type": "main",
"index": 0
}
],
[
{
"node": "Telegram: Send Null",
"type": "main",
"index": 0
}
]
]
},
"Auto-Fix JSON": {
"ai_outputParser": [
[
{
"node": "AI: Extract Expenses",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Set: Text Input": {
"main": [
[
{
"node": "AI: Extract Expenses",
"type": "main",
"index": 0
}
]
]
},
"LLM: Gemini Flash": {
"ai_languageModel": [
[
{
"node": "Auto-Fix JSON",
"type": "ai_languageModel",
"index": 0
},
{
"node": "AI: Extract Expenses",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"GSheet: Log Expense": {
"main": [
[
{
"node": "Telegram: Send Confirmation",
"type": "main",
"index": 0
}
]
]
},
"Set: Extract Output": {
"main": [
[
{
"node": "GSheet: Log Expense",
"type": "main",
"index": 0
}
]
]
},
"AI: Extract Expenses": {
"main": [
[
{
"node": "Split: Entries to Rows",
"type": "main",
"index": 0
}
]
]
},
"Split: Entries to Rows": {
"main": [
[
{
"node": "If null?",
"type": "main",
"index": 0
}
]
]
},
"Set: Voice Input (Text)": {
"main": [
[
{
"node": "AI: Extract Expenses",
"type": "main",
"index": 0
}
]
]
},
"Set: image Input (Text)": {
"main": [
[
{
"node": "AI: Extract Expenses",
"type": "main",
"index": 0
}
]
]
},
"AI Helper: Define Schema": {
"ai_outputParser": [
[
{
"node": "Auto-Fix JSON",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Gemini: Analyze an image": {
"main": [
[
{
"node": "Set: image Input (Text)",
"type": "main",
"index": 0
}
]
]
},
"Gemini: Transcribe Voice": {
"main": [
[
{
"node": "Set: Voice Input (Text)",
"type": "main",
"index": 0
}
]
]
},
"Telegram: Get Image File": {
"main": [
[
{
"node": "Gemini: Analyze an image",
"type": "main",
"index": 0
}
]
]
},
"Telegram: Get Voice File": {
"main": [
[
{
"node": "Gemini: Transcribe Voice",
"type": "main",
"index": 0
}
]
]
},
"Start: On Telegram Message": {
"main": [
[
{
"node": "Route: Text, Voice or Image?",
"type": "main",
"index": 0
}
]
]
},
"Route: Text, Voice or Image?": {
"main": [
[
{
"node": "Set: Text Input",
"type": "main",
"index": 0
}
],
[
{
"node": "Telegram: Get Voice File",
"type": "main",
"index": 0
}
],
[
{
"node": "Telegram: Get Image File",
"type": "main",
"index": 0
}
],
[
{
"node": "Telegram: Send error message",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing β you'll add your own.
googlePalmApigoogleSheetsOAuth2ApitelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
π Context switch kills the habit: Because bookkeeping lives outside the apps you use every day, you postpone it β forget to log. π§± High input friction: Youβre forced to fill rigid fields (amount/category/date/notesβ¦), which is slow and discouraging for quick capture. ποΈπΈ Weak orβ¦
Source: https://n8n.io/workflows/10155/ β original creator credit. Request a take-down β
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This project is a template for building a complete academic virtual assistant using n8n. It connects to Telegram, answers frequently asked questions by querying MongoDB, keeps the community informed a
This workflow transforms your Telegram bot into an intelligent creative assistant. It can chat conversationally, fetch trending image prompts from PromptHero for inspiration, or perform a deep "remix"
> AI-powered nutrition assistant for Telegram β log meals, set goals, and get personalized daily reports with Google Sheets integration.
This automation is designed to help you generate AI-powered music tracks, cover art, and fully rendered music videos β all triggered from a simple Telegram chat and managed via Google Sheets.
This Shopify AI automation is an advanced n8n-powered workflow that transforms Shopify product collections into SEO-optimized blog articles with images, while maintaining full visibility and control t