AutomationFlows β€Ί AI & RAG β€Ί Multi-modal Expense Tracking with Telegram, Gemini AI & Google Sheets

Multi-modal Expense Tracking with Telegram, Gemini AI & Google Sheets

ByOwenLee @owen-n8nlabβœ“ on n8n.io

πŸ”€ 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…

Event triggerβ˜…β˜…β˜…β˜…β˜† complexityAI-powered25 nodesGoogle SheetsGoogle Gemini ChatGoogle GeminiTelegramAgentOutput Parser AutofixingOutput Parser StructuredTelegram Trigger
AI & RAG Trigger: Event Nodes: 25 Complexity: β˜…β˜…β˜…β˜…β˜† AI nodes: yes Added:

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 β†’

Download .json
{
  "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.

Pro

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 β†’

More AI & RAG workflows β†’ Β· Browse all categories β†’

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

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

Telegram, MongoDB, Telegram Trigger +6
AI & RAG

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"

Telegram Trigger, Output Parser Structured, Telegram +6
AI & RAG

&gt; AI-powered nutrition assistant for Telegram β€” log meals, set goals, and get personalized daily reports with Google Sheets integration.

Telegram, Google Gemini, Google Gemini Chat +7
AI & RAG

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.

OpenAI Chat, Memory Buffer Window, Output Parser Structured +11
AI & RAG

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

Shopify Trigger, HTTP Request, Google Sheets +7