I Stopped Manually Sorting Receipts — Here's the AI Workflow That Categorizes Every Expense
Every month, I'd spend an entire Sunday afternoon sorting receipts. Download from email, check the vendor, figure out the category, type it into a spreadsheet. 30–50 receipts. 4–8 hours gone.
Multiply that across every small business owner I know and you're looking at millions of hours wasted on what is fundamentally a pattern-matching problem. The vendor name, amount, and date are all right there in the email. The category follows predictable rules. This is exactly the kind of work AI should eat for breakfast.
So I built an n8n workflow that watches my receipt emails, extracts the key data, categorizes each expense, and logs everything to a Google Sheet. Time per month: 0 hours of manual sorting, maybe 15 minutes of spot-checking.
Here's the full build.
Why Expense Categorization Is a Bigger Problem Than You Think
Most small business owners handle expenses in one of two ways:
- Shoebox method — throw every receipt in a folder and panic at tax time
- Manual spreadsheet — spend hours each month typing line items
Both cost real money. The average small business owner spends 4–8 hours per month on expense management, according to a QuickBooks survey. At $75/hour opportunity cost, that's $300–$600/month in lost productivity.
And the stakes are higher than time. The IRS estimates that incorrect expense categorization triggers audits and missed deductions worth thousands. Misclassify a capital expense as an operating expense and you lose depreciation deductions. Put a meal with a client under "travel" and you lose the 50% deduction cap protection.
The right category matters. And getting it right shouldn't take your Sunday.
The 5 Categories That Cover 90% of Small Business Expenses
Before building the workflow, I mapped every expense from the past 12 months. Here's what I found:
| Category | % of Total | Common Vendors |
|---|---|---|
| Software & Subscriptions | 28% | AWS, Stripe, GitHub, Google Workspace |
| Office & Operations | 22% | Staples, Amazon, USPS |
| Marketing & Advertising | 19% | Meta Ads, Google Ads, Mailchimp |
| Travel & Meals | 14% | Uber, Hilton, restaurant receipts |
| Professional Services | 17% | Lawyer invoices, CPA fees, consultants |
Five categories. That's it. The long tail — office plants, random software, one-off purchases — accounted for less than 10% and almost always fell into "Office & Operations" as a catch-all.
This matters because AI categorization works best with a small, well-defined target set. You don't need 47 QuickBooks categories. You need 5–8 that map to your actual spending patterns.
The Workflow: Email → Extract → Categorize → Log
Here's the architecture:
Gmail Trigger (new receipt emails)
↓
AI Extract Node (vendor, amount, date, description)
↓
AI Categorize Node (maps to 5 categories)
↓
Google Sheets Node (log the row)
↓
Slack Notification (daily summary)
Step 1: Gmail Trigger
Set up a Gmail trigger that watches for emails matching:
subject:(receipt OR invoice OR order confirmation OR payment receipt)
from:(stripe OR amazon OR uber OR paypal OR square)
This catches 80%+ of automated receipt emails. Add your own vendors as you discover them.
Step 2: AI Extract Node
Use an OpenAI node with this system prompt:
You are an expense data extractor. Given an email body, extract:
1. vendor_name: The company name (e.g., "Amazon", "Stripe", "Uber")
2. amount: The total amount as a number (e.g., 49.99)
3. currency: ISO currency code, default USD
4. date: The transaction date in YYYY-MM-DD format
5. description: A 10-word summary of what was purchased
6. payment_method: Last 4 digits if visible, otherwise "unknown"
Return ONLY valid JSON with these fields. If a field cannot be determined, use null.
The key insight: ask for structured JSON output, not prose. This makes the next step deterministic.
Step 3: AI Categorize Node
A second OpenAI call with the extracted data:
You categorize business expenses into exactly one of these categories:
- software_subscriptions: SaaS, cloud services, domains, hosting
- office_operations: supplies, shipping, equipment, furniture
- marketing_advertising: ads, email tools, design, social media management
- travel_meals: flights, hotels, rideshares, client dinners
- professional_services: legal, accounting, consulting, freelance contractors
Given the vendor name, amount, and description, return ONLY the category key.
If uncertain, default to office_operations.
Why a second call instead of combining extraction and categorization? Separation of concerns. The extraction step needs to be accurate — wrong amounts are worse than wrong categories. The categorization step can tolerate fuzziness. Combining them makes both worse.
Step 4: Google Sheets Log
Map the combined output to a row:
| Date | Vendor | Amount | Category | Description | Payment |
|---|---|---|---|---|---|
| 2026-06-25 | AWS | 127.43 | software_subscriptions | Monthly cloud hosting | •••4821 |
Step 5: Daily Slack Summary
A scheduled node sends a daily digest:
📊 Today's Expenses (3 items, $412.67 total)
• AWS — $127.43 (software_subscriptions)
• Uber — $23.50 (travel_meals)
• Staples — $261.74 (office_operations)
This takes 5 seconds to scan. If something looks wrong, you fix it in the sheet. Otherwise, you're done.
The n8n Workflow JSON
{
"name": "Expense Categorizer",
"nodes": [
{
"parameters": {
"pollTimes": { "item": [{ "mode": "everyMinute", "value": 15 }] },
"filters": {
"from": "stripe OR amazon OR uber OR paypal OR square",
"subject": "receipt OR invoice OR order confirmation"
}
},
"name": "Gmail Trigger",
"type": "n8n-nodes-base.gmailTrigger",
"position": [250, 300]
},
{
"parameters": {
"model": "gpt-4o-mini",
"messages": {
"values": [
{
"role": "system",
"content": "You are an expense data extractor. Given an email body, extract:\n1. vendor_name: company name\n2. amount: total as number\n3. currency: ISO code, default USD\n4. date: YYYY-MM-DD\n5. description: 10-word summary\n6. payment_method: last 4 digits or 'unknown'\n\nReturn ONLY valid JSON."
},
{
"role": "user",
"content": "={{ $json.snippet }}"
}
]
},
"options": { "responseFormat": "json_object" }
},
"name": "Extract Data",
"type": "n8n-nodes-base.openAi",
"position": [470, 300]
},
{
"parameters": {
"model": "gpt-4o-mini",
"messages": {
"values": [
{
"role": "system",
"content": "Categorize this expense into exactly one:\n- software_subscriptions\n- office_operations\n- marketing_advertising\n- travel_meals\n- professional_services\n\nReturn ONLY the category key."
},
{
"role": "user",
"content": "={{ $json.vendor_name }} — ${{ $json.amount }} — {{ $json.description }}"
}
]
}
},
"name": "Categorize",
"type": "n8n-nodes-base.openAi",
"position": [690, 300]
},
{
"parameters": {
"operation": "append",
"documentId": "YOUR_SHEET_ID",
"range": "Expenses!A:F",
"columns": {
"mappingMode": "defineBelow",
"value": {
"Date": "={{ $('Extract Data').item.json.date }}",
"Vendor": "={{ $('Extract Data').item.json.vendor_name }}",
"Amount": "={{ $('Extract Data').item.json.amount }}",
"Category": "={{ $('Categorize').item.json.text }}",
"Description": "={{ $('Extract Data').item.json.description }}",
"Payment": "={{ $('Extract Data').item.json.payment_method }}"
}
}
},
"name": "Log to Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [910, 300]
},
{
"parameters": {
"channel": "#expenses",
"text": "📊 New expense: *{{ $('Extract Data').item.json.vendor_name }}* — ${{ $('Extract Data').item.json.amount }} ({{ $('Categorize').item.json.text }})"
},
"name": "Slack Alert",
"type": "n8n-nodes-base.slack",
"position": [1130, 300]
}
],
"connections": {
"Gmail Trigger": { "main": [[{ "node": "Extract Data", "type": "main", "index": 0 }]] },
"Extract Data": { "main": [[{ "node": "Categorize", "type": "main", "index": 0 }]] },
"Categorize": { "main": [[{ "node": "Log to Sheets", "type": "main", "index": 0 }]] },
"Log to Sheets": { "main": [[{ "node": "Slack Alert", "type": "main", "index": 0 }]] }
}
}
Copy this into n8n, add your Gmail and Google Sheets credentials, and you're running.
The Numbers: What This Actually Saves
For a business processing 50 expenses/month:
| Task | Before | After | Saved |
|---|---|---|---|
| Opening & reading receipts | 2.5 hrs | 0 hrs | 2.5 hrs |
| Categorizing expenses | 1.5 hrs | 0.25 hrs (spot-check) | 1.25 hrs |
| Data entry into sheets | 1.5 hrs | 0 hrs | 1.5 hrs |
| Finding missed receipts | 1 hr | 0.25 hrs | 0.75 hrs |
| Total | 6.5 hrs | 0.5 hrs | 6 hrs/month |
At $75/hour: $450/month saved. At scale with 200+ expenses: $1,200+/month.
And this is before tax-season benefits. When your CPA asks for expense categories, you hand them a clean spreadsheet instead of a shoebox. That alone can save $500–$1,000 in CPA fees.
3 Mistakes I Made Building This (So You Don't Have To)
Mistake 1: Combining extraction and categorization into one prompt.
The AI would sometimes hallucinate categories into the amount field, or combine vendor names with descriptions. Two separate calls with clear, single-purpose prompts fixed this completely.
Mistake 2: Too many categories.
I started with 15 categories matching our chart of accounts. The AI got confused between "office supplies" and "office equipment" and "office furniture." Reducing to 5 broad categories pushed accuracy from ~70% to ~95%.
Mistake 3: Not handling multi-item receipts.
Amazon receipts often have 3–5 items in one order. The workflow now splits multi-item receipts into separate rows, each with its own category. This required a small loop node but was worth the accuracy gain.
What This Connects To
Expense categorization is step one. Once your expenses are structured data, you can:
- Flag anomalous spending — "You spent 40% more on software this month"
- Auto-generate P&L reports — monthly profit & loss without manual rollups
- Predict cash flow — feed categorized expenses into a simple forecasting model
- Catch duplicate charges — same vendor, same amount, within 7 days = alert
Each of these is another n8n workflow layered on top of the same clean data foundation.
Start with categorization. Get your expenses into a shape a computer can reason about. Then stack the next automation on top.
If you're building expense automation or want the full n8n workflow with error handling and multi-item receipt parsing, drop a comment or reach out. I share complete workflows with working JSON — no gated PDFs.
Top comments (0)