AutomationFlowsAI & RAG › Log Email Receipts to Google Sheets with Gmail and Openai

Log Email Receipts to Google Sheets with Gmail and Openai

By43 Sunsets @the43sunsets on n8n.io

This workflow logs Gmail receipt attachments to Google Sheets by extracting structured fields with OpenAI (including category selection from your list), prevents duplicates by checking the ledger, and sends a monthly spending summary email via Gmail based on last month’s rows.…

Event trigger★★★★☆ complexityAI-powered23 nodesGmail TriggerGoogle SheetsOpenAIChain LlmOpenAI ChatOutput Parser StructuredGmail
AI & RAG Trigger: Event Nodes: 23 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #16983 — we link there as the canonical source.

This workflow follows the Chainllm → Gmail 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
{
  "name": "Log email receipts to Google Sheets with OpenAI and send monthly summaries",
  "nodes": [
    {
      "id": "f3a0s001-0000-4000-8000-000000000001",
      "name": "Sticky Note \u2014 read me first",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -840,
        -180
      ],
      "parameters": {
        "width": 720,
        "height": 920,
        "content": "## Email receipts in, clean expense ledger out\n\n### How it works\nForward any receipt to your own inbox with \"receipt\" in the subject (or change the Gmail filter to a label you prefer). The workflow picks up the attachment \u2014 photo or PDF \u2014 reads it with AI, and appends one clean row to your Google Sheets ledger: date, merchant, amount, currency and a category picked from **your** category list. A processed-flag guard means re-runs never create duplicate rows. On the 1st of each month it also emails you a summary of last month's spending by category.\n\nThis tool organizes records. It is **not tax advice** \u2014 categories and amounts should be reviewed by you or your accountant.\n\n### Setup\n1. Connect **Gmail** and **Google Sheets** (one-click Google sign-in on n8n Cloud).\n2. Add your **OpenAI API key** to both AI nodes.\n3. Create a sheet with headers: `processed_at, gmail_message_id, receipt_date, merchant, amount, currency, category, source_subject` and select it in all three Sheets nodes.\n4. In **\u2699\ufe0f Your settings**: set your email, currency default, and edit the category list to match your bookkeeping.\n5. Activate, then forward yourself a receipt with \"receipt\" in the subject to test.\n\n### Customization tips\nChange the Gmail search filter in the trigger (e.g. `label:receipts`). Edit the category list any time \u2014 the AI only picks from your list."
      },
      "typeVersion": 1
    },
    {
      "id": "f3a0s002-0000-4000-8000-000000000002",
      "name": "Sticky Note \u2014 section 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -80,
        -180
      ],
      "parameters": {
        "color": 7,
        "width": 740,
        "height": 920,
        "content": "## 1. Catch + guard\nTwo triggers share one settings node: receipts arrive by email, the monthly summary fires on the 1st. The ledger lookup + IF guard make re-runs safe \u2014 an email that is already in the ledger is never processed twice."
      },
      "typeVersion": 1
    },
    {
      "id": "f3a0s003-0000-4000-8000-000000000003",
      "name": "Sticky Note \u2014 section 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        680,
        -180
      ],
      "parameters": {
        "color": 7,
        "width": 700,
        "height": 920,
        "content": "## 2. Read the receipt\nPDFs get their text extracted directly; photos and scans go through AI vision transcription. Both paths converge into one structured extraction that returns date, merchant, amount, currency and a category **only from your list**."
      },
      "typeVersion": 1
    },
    {
      "id": "f3a0s004-0000-4000-8000-000000000004",
      "name": "Sticky Note \u2014 section 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1400,
        -180
      ],
      "parameters": {
        "color": 7,
        "width": 520,
        "height": 920,
        "content": "## 3. Ledger + monthly summary\nEach receipt becomes one row in Google Sheets. On the 1st of the month, last month's rows are aggregated by category and emailed to you \u2014 so you start every month knowing where the money went. (Not tax advice.)"
      },
      "typeVersion": 1
    },
    {
      "id": "f3a00001-0000-4000-8000-00000000000a",
      "name": "Receipt arrives (Gmail)",
      "type": "n8n-nodes-base.gmailTrigger",
      "position": [
        20,
        176
      ],
      "parameters": {
        "simple": false,
        "filters": {
          "q": "has:attachment subject:receipt"
        },
        "options": {
          "downloadAttachments": true,
          "dataPropertyAttachmentsPrefixName": "attachment_"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "f3a00002-0000-4000-8000-00000000000b",
      "name": "1st of the month, 8am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        20,
        360
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "months",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 8
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "f3a00003-0000-4000-8000-00000000000c",
      "name": "\u2699\ufe0f Your settings",
      "type": "n8n-nodes-base.set",
      "position": [
        160,
        176
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "c1",
              "name": "ownerEmail",
              "type": "string",
              "value": "you@example.com"
            },
            {
              "id": "c2",
              "name": "defaultCurrency",
              "type": "string",
              "value": "USD"
            },
            {
              "id": "c3",
              "name": "categories",
              "type": "string",
              "value": "Materials, Fuel & vehicle, Tools & equipment, Software & subscriptions, Meals, Office & admin, Marketing, Insurance, Other"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "f3a00004-0000-4000-8000-00000000000d",
      "name": "Receipt or monthly summary?",
      "type": "n8n-nodes-base.if",
      "position": [
        340,
        176
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c2",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $('Receipt arrives (Gmail)').isExecuted }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "f3a00005-0000-4000-8000-00000000000e",
      "name": "Check the ledger (already logged?)",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        520,
        160
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $('Receipt arrives (Gmail)').item.json.id }}",
              "lookupColumn": "gmail_message_id"
            }
          ]
        },
        "operation": "lookup",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "typeVersion": 4.5,
      "alwaysOutputData": true
    },
    {
      "id": "f3a00006-0000-4000-8000-00000000000f",
      "name": "Only continue if it is new",
      "type": "n8n-nodes-base.if",
      "position": [
        740,
        160
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c3",
              "operator": {
                "type": "string",
                "operation": "empty",
                "singleValue": true
              },
              "leftValue": "={{ $json.gmail_message_id || '' }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "f3a00007-0000-4000-8000-000000000010",
      "name": "Already logged \u2014 stop",
      "type": "n8n-nodes-base.noOp",
      "position": [
        880,
        360
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "f3a00008-0000-4000-8000-000000000011",
      "name": "PDF or photo?",
      "type": "n8n-nodes-base.switch",
      "position": [
        880,
        144
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "PDF",
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "s1",
                    "operator": {
                      "type": "string",
                      "operation": "contains"
                    },
                    "leftValue": "={{ $('Receipt arrives (Gmail)').item.binary.attachment_0.mimeType }}",
                    "rightValue": "pdf"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Image",
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "s2",
                    "operator": {
                      "type": "string",
                      "operation": "contains"
                    },
                    "leftValue": "={{ $('Receipt arrives (Gmail)').item.binary.attachment_0.mimeType }}",
                    "rightValue": "image"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "f3a00009-0000-4000-8000-000000000012",
      "name": "Extract text from PDF",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        1060,
        128
      ],
      "parameters": {
        "options": {},
        "operation": "pdf",
        "binaryPropertyName": "attachment_0"
      },
      "typeVersion": 1
    },
    {
      "id": "f3a00010-0000-4000-8000-000000000013",
      "name": "Normalize (PDF path)",
      "type": "n8n-nodes-base.set",
      "position": [
        1240,
        128
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "n1",
              "name": "receiptText",
              "type": "string",
              "value": "={{ $json.text }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "f3a00011-0000-4000-8000-000000000014",
      "name": "Transcribe photo with AI vision",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "maxTries": 3,
      "position": [
        1060,
        288
      ],
      "parameters": {
        "text": "Transcribe every piece of text visible on this receipt, preserving numbers exactly as printed. Output plain text only.",
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {},
        "resource": "image",
        "inputType": "base64",
        "operation": "analyze",
        "binaryPropertyName": "attachment_0"
      },
      "retryOnFail": true,
      "typeVersion": 1.8,
      "waitBetweenTries": 5000
    },
    {
      "id": "f3a00012-0000-4000-8000-000000000015",
      "name": "Normalize (photo path)",
      "type": "n8n-nodes-base.set",
      "position": [
        1240,
        288
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "n2",
              "name": "receiptText",
              "type": "string",
              "value": "={{ $json.content }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "f3a00013-0000-4000-8000-000000000016",
      "name": "Extract fields (AI)",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "maxTries": 3,
      "position": [
        1460,
        200
      ],
      "parameters": {
        "text": "=Extract structured expense data from this receipt text. Rules:\n- receipt_date in YYYY-MM-DD (if only partial date is printed, use the email date {{ $('Receipt arrives (Gmail)').item.json.date }} to complete it)\n- amount is the FINAL TOTAL paid (after tax and tip), as a plain number\n- currency as a 3-letter code; if not printed, use {{ $('\u2699\ufe0f Your settings').item.json.defaultCurrency }}\n- category MUST be exactly one of: {{ $('\u2699\ufe0f Your settings').item.json.categories }}\n- merchant is the business name, not the address\n\nRECEIPT TEXT:\n{{ $json.receiptText }}",
        "promptType": "define",
        "hasOutputParser": true
      },
      "retryOnFail": true,
      "typeVersion": 1.5,
      "waitBetweenTries": 5000
    },
    {
      "id": "f3a00014-0000-4000-8000-000000000017",
      "name": "OpenAI model (bring your own key)",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1440,
        440
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {
          "temperature": 0
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "f3a00015-0000-4000-8000-000000000018",
      "name": "Force clean JSON output",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        1640,
        440
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"receipt_date\": { \"type\": \"string\" },\n    \"merchant\": { \"type\": \"string\" },\n    \"amount\": { \"type\": \"number\" },\n    \"currency\": { \"type\": \"string\" },\n    \"category\": { \"type\": \"string\" }\n  },\n  \"required\": [\"receipt_date\", \"merchant\", \"amount\", \"currency\", \"category\"]\n}"
      },
      "typeVersion": 1.2
    },
    {
      "id": "f3a00016-0000-4000-8000-000000000019",
      "name": "Append to the expense ledger",
      "type": "n8n-nodes-base.googleSheets",
      "maxTries": 3,
      "position": [
        1760,
        200
      ],
      "parameters": {
        "columns": {
          "value": {
            "amount": "={{ $json.output.amount }}",
            "category": "={{ $json.output.category }}",
            "currency": "={{ $json.output.currency }}",
            "merchant": "={{ $json.output.merchant }}",
            "processed_at": "={{ $now.toISO() }}",
            "receipt_date": "={{ $json.output.receipt_date }}",
            "source_subject": "={{ $('Receipt arrives (Gmail)').item.json.subject }}",
            "gmail_message_id": "={{ $('Receipt arrives (Gmail)').item.json.id }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.5,
      "waitBetweenTries": 5000
    },
    {
      "id": "f3a00017-0000-4000-8000-00000000001a",
      "name": "Read the full ledger",
      "type": "n8n-nodes-base.googleSheets",
      "maxTries": 3,
      "position": [
        520,
        520
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.5,
      "waitBetweenTries": 5000
    },
    {
      "id": "f3a00018-0000-4000-8000-00000000001b",
      "name": "Total last month by category",
      "type": "n8n-nodes-base.code",
      "position": [
        740,
        520
      ],
      "parameters": {
        "jsCode": "// Aggregate LAST month's expenses by category.\nconst now = new Date();\nconst firstOfThisMonth = new Date(now.getFullYear(), now.getMonth(), 1);\nconst firstOfLastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);\nconst label = `${firstOfLastMonth.getFullYear()}-${String(firstOfLastMonth.getMonth() + 1).padStart(2, '0')}`;\n\nconst byCat = {};\nlet total = 0, count = 0, skipped = 0;\nfor (const item of $input.all()) {\n  const r = item.json;\n  const d = new Date(r.receipt_date);\n  if (isNaN(d.getTime())) { skipped++; continue; }\n  if (d < firstOfLastMonth || d >= firstOfThisMonth) continue;\n  const amt = Number(r.amount) || 0;\n  const cat = r.category || 'Uncategorized';\n  byCat[cat] = (byCat[cat] || 0) + amt;\n  total += amt; count++;\n}\n\nconst lines = Object.entries(byCat)\n  .sort((a, b) => b[1] - a[1])\n  .map(([c, v]) => `${c}: ${v.toFixed(2)}`);\n\nreturn [{ json: {\n  monthLabel: label,\n  total: total.toFixed(2),\n  count,\n  skipped,\n  breakdown: lines.join('\\n') || '(no expenses recorded)',\n} }];"
      },
      "typeVersion": 2
    },
    {
      "id": "f3a00019-0000-4000-8000-00000000001c",
      "name": "Email the monthly summary",
      "type": "n8n-nodes-base.gmail",
      "maxTries": 3,
      "position": [
        880,
        520
      ],
      "parameters": {
        "sendTo": "={{ $('\u2699\ufe0f Your settings').item.json.ownerEmail }}",
        "message": "=Expense summary for {{ $json.monthLabel }}\n\nTotal: {{ $json.total }} ({{ $json.count }} receipts{{ $json.skipped > 0 ? ', ' + $json.skipped + ' rows skipped for bad dates' : '' }})\n\nBy category:\n{{ $json.breakdown }}\n\n\u2014\nGenerated automatically from your receipt ledger. This is a bookkeeping aid, not tax advice.",
        "options": {},
        "subject": "=\ud83d\udcb0 {{ $json.monthLabel }} expense summary \u2014 {{ $json.count }} receipts, {{ $json.total }} total",
        "emailType": "text"
      },
      "retryOnFail": true,
      "typeVersion": 2.1,
      "waitBetweenTries": 5000
    }
  ],
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "PDF or photo?": {
      "main": [
        [
          {
            "node": "Extract text from PDF",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Transcribe photo with AI vision",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract fields (AI)": {
      "main": [
        [
          {
            "node": "Append to the expense ledger",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize (PDF path)": {
      "main": [
        [
          {
            "node": "Extract fields (AI)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read the full ledger": {
      "main": [
        [
          {
            "node": "Total last month by category",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u2699\ufe0f Your settings": {
      "main": [
        [
          {
            "node": "Receipt or monthly summary?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1st of the month, 8am": {
      "main": [
        [
          {
            "node": "\u2699\ufe0f Your settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract text from PDF": {
      "main": [
        [
          {
            "node": "Normalize (PDF path)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize (photo path)": {
      "main": [
        [
          {
            "node": "Extract fields (AI)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Force clean JSON output": {
      "ai_outputParser": [
        [
          {
            "node": "Extract fields (AI)",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Receipt arrives (Gmail)": {
      "main": [
        [
          {
            "node": "\u2699\ufe0f Your settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Only continue if it is new": {
      "main": [
        [
          {
            "node": "PDF or photo?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Already logged \u2014 stop",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receipt or monthly summary?": {
      "main": [
        [
          {
            "node": "Check the ledger (already logged?)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Read the full ledger",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Total last month by category": {
      "main": [
        [
          {
            "node": "Email the monthly summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Transcribe photo with AI vision": {
      "main": [
        [
          {
            "node": "Normalize (photo path)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI model (bring your own key)": {
      "ai_languageModel": [
        [
          {
            "node": "Extract fields (AI)",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Check the ledger (already logged?)": {
      "main": [
        [
          {
            "node": "Only continue if it is new",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow logs Gmail receipt attachments to Google Sheets by extracting structured fields with OpenAI (including category selection from your list), prevents duplicates by checking the ledger, and sends a monthly spending summary email via Gmail based on last month’s rows.…

Source: https://n8n.io/workflows/16983/ — 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

[](https://www.youtube.com/watch?v=rfu4MSvtpAw)

Lm Open Ai, Output Parser Structured, Chain Llm +7
AI & RAG

This workflow gives you the ability to reply to a long email with a voice note, rather than having to type everything out.

Output Parser Structured, OpenAI Chat, Telegram +5
AI & RAG

This n8n template automates targeted lead discovery, AI-driven data structuring, and personalized cold-email sending at controlled intervals. It’s ideal for sales teams, founders, and agencies that wa

Google Sheets, Form Trigger, Chain Llm +6
AI & RAG

Multi-Channel Email Intelligence Agent. Uses lmChatOpenAi, outputParserStructured, gmailTrigger, agent. Event-driven trigger; 18 nodes.

OpenAI Chat, Output Parser Structured, Gmail Trigger +6
AI & RAG

This workflow monitors Gmail for Google Business Profile “new review” alert emails, uses OpenAI to extract the rating and review text and draft a policy-safe reply, then emails the draft to you and lo

Gmail Trigger, Google Sheets, Chain Llm +3