AI reads receipt emails into your expense sheet

AI pulls the vendor, amount, currency and date out of receipt emails and logs them.

Medium Saves ~5 hours/month Finance & AdminBy Flowngn team

Works with n8n · Make · Zapier

How it works

Trigger: Gmail — new email that looks like a receipt
  1. Watch Gmail for receipts, invoices and order confirmations.
  2. Ask an AI model to extract vendor, amount, currency, date and category.
  3. Add a row to your Expenses sheet.

Diagram

trueGReceipt emailGmail TriggerExtract expenseBasic LLM ChainClaude modelAnthropic Chat Mo…{ }Read expenseCode⑂Is a receipt?IfGAdd to ExpensesGoogle Sheets
Trigger App Logic Code AI

Code

JavaScript used in the Code step — already included in the template below.

Read expense · Code node
// Mode: Run Once for Each Item
// The AI was asked for JSON like:
// {"vendor": "Uber", "amount": 12.5, "currency": "USD", "date": "2026-10-01", "category": "Transport"}
const match = $json.text.match(/\{[\s\S]*\}/);
const r = match ? JSON.parse(match[0]) : {};
const email = $("Receipt email").item.json;

if (!r.amount) return { json: { skip: true } };

return {
  json: {
    date: r.date || new Date(email.date).toISOString().slice(0, 10),
    vendor: r.vendor || email.from?.value?.[0]?.name || "",
    amount: Number(r.amount),
    currency: r.currency || "",
    category: r.category || "Other",
    email_subject: email.subject,
  },
};

Build it

Pick your tool — same automation, step by step.

Copy and paste straight into an n8n canvas (Ctrl+V), or download and use Import from File. Then connect your own credentials and replace every YOUR_… placeholder. AI steps use a Claude model node — swap it for OpenAI, Gemini or Ollama if you prefer.

Workflow JSON
Show code (215 lines)
{
  "name": "AI reads receipt emails into your expense sheet (Flowngn)",
  "nodes": [
    {
      "parameters": {
        "content": "## AI reads receipt emails into your expense sheet\nAI pulls the vendor, amount, currency and date out of receipt emails and logs them.\n\n**Setup**\n1. Open each node and connect your own credentials.\n2. Replace every YOUR_… placeholder.\n3. Test once, then activate.\n\nFree template from Flowngn",
        "height": 260,
        "width": 340
      },
      "id": "292cf932-28a7-4296-a76d-5cdfef5c8244",
      "name": "Setup notes",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -420,
        -60
      ]
    },
    {
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyHour"
            }
          ]
        },
        "simple": false,
        "filters": {
          "q": "receipt OR invoice OR \"order confirmation\""
        },
        "options": {}
      },
      "id": "292cf932-28a7-4296-a76d-5cdfef5c8244",
      "name": "Receipt email",
      "type": "n8n-nodes-base.gmailTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=Read this email. If it is a receipt or invoice for a purchase, reply ONLY with JSON: {\"vendor\": \"...\", \"amount\": 0, \"currency\": \"USD\", \"date\": \"YYYY-MM-DD\", \"category\": \"Software|Travel|Food|Office|Other\"}. If it is not a receipt, reply with {}.\n\nFrom: {{ $json.from.text }}\nSubject: {{ $json.subject }}\n\n{{ ($json.text || \"\").slice(0, 4000) }}"
      },
      "id": "292cf932-28a7-4296-a76d-5cdfef5c8244",
      "name": "Extract expense",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.5,
      "position": [
        300,
        0
      ]
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "claude-sonnet-5",
          "mode": "list"
        },
        "options": {}
      },
      "id": "292cf932-28a7-4296-a76d-5cdfef5c8244",
      "name": "Claude model",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "typeVersion": 1.3,
      "position": [
        300,
        220
      ]
    },
    {
      "parameters": {
        "jsCode": "// Mode: Run Once for Each Item\n// The AI was asked for JSON like:\n// {\"vendor\": \"Uber\", \"amount\": 12.5, \"currency\": \"USD\", \"date\": \"2026-10-01\", \"category\": \"Transport\"}\nconst match = $json.text.match(/\\{[\\s\\S]*\\}/);\nconst r = match ? JSON.parse(match[0]) : {};\nconst email = $(\"Receipt email\").item.json;\n\nif (!r.amount) return { json: { skip: true } };\n\nreturn {\n  json: {\n    date: r.date || new Date(email.date).toISOString().slice(0, 10),\n    vendor: r.vendor || email.from?.value?.[0]?.name || \"\",\n    amount: Number(r.amount),\n    currency: r.currency || \"\",\n    category: r.category || \"Other\",\n    email_subject: email.subject,\n  },\n};",
        "mode": "runOnceForEachItem"
      },
      "id": "292cf932-28a7-4296-a76d-5cdfef5c8244",
      "name": "Read expense",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        600,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.skip }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "notEquals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "292cf932-28a7-4296-a76d-5cdfef5c8244",
      "name": "Is a receipt?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        900,
        0
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "YOUR_SPREADSHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Expenses",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {}
        },
        "options": {}
      },
      "id": "292cf932-28a7-4296-a76d-5cdfef5c8244",
      "name": "Add to Expenses",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1200,
        0
      ]
    }
  ],
  "connections": {
    "Receipt email": {
      "main": [
        [
          {
            "node": "Extract expense",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract expense": {
      "main": [
        [
          {
            "node": "Read expense",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read expense": {
      "main": [
        [
          {
            "node": "Is a receipt?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is a receipt?": {
      "main": [
        [
          {
            "node": "Add to Expenses",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude model": {
      "ai_languageModel": [
        [
          {
            "node": "Extract expense",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}

Open in the playground to edit →

Tips

  • Check the sheet weekly at first — AI is good at this, but not perfect.

Related flows