This workflow corresponds to n8n.io template #skynetlabs-11 — we link there as the canonical source.
This workflow follows the Gmail Trigger → Google Drive 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 →
{
"name": "11 - Receipt OCR to Sheets to Tax",
"nodes": [
{
"parameters": {
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"simple": false,
"filters": {
"q": "to:(receipts+ocr@gmail.com) has:attachment newer_than:1d"
},
"options": {
"downloadAttachments": true
}
},
"id": "trigger-gmail",
"name": "Gmail receipts inbox",
"type": "n8n-nodes-base.gmailTrigger",
"typeVersion": 1.2,
"position": [
200,
300
],
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const out = [];\nfor (const item of $input.all()) {\n const atts = item.binary || {};\n for (const key of Object.keys(atts)) {\n const att = atts[key];\n if (!att.mimeType || !/image|pdf/i.test(att.mimeType)) continue;\n out.push({\n json: {\n message_id: item.json.id,\n from: item.json.from?.value?.[0]?.address || item.json.from,\n subject: item.json.subject,\n received_at: item.json.date,\n attachment_name: att.fileName,\n mime_type: att.mimeType\n },\n binary: { data: att }\n });\n }\n}\nreturn out;"
},
"id": "extract-attachments",
"name": "Extract image attachments",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
420,
300
]
},
{
"parameters": {
"name": "={{ $now.format('yyyy-MM-dd-HHmmss') }}-{{ $json.attachment_name }}",
"driveId": {
"__rl": true,
"value": "My Drive",
"mode": "list"
},
"folderId": {
"__rl": true,
"value": "REPLACE_ME_DRIVE_FOLDER",
"mode": "id"
},
"options": {}
},
"id": "drive-archive",
"name": "Archive to Drive",
"type": "n8n-nodes-base.googleDrive",
"typeVersion": 3,
"position": [
640,
300
],
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"modelId": {
"__rl": true,
"value": "claude-sonnet-4-6",
"mode": "list"
},
"messages": {
"values": [
{
"content": "=You are a bookkeeping OCR. Read the attached receipt image and return ONLY valid JSON. No prose.\n\nFields:\n{\n \"date\": \"YYYY-MM-DD or empty\",\n \"vendor\": \"merchant name\",\n \"total\": \"number, no currency symbol\",\n \"currency\": \"USD|EUR|GBP|PKR|other ISO\",\n \"vat\": \"number or 0\",\n \"category\": \"software|meals|travel|office|advertising|professional_services|utilities|hardware|other\",\n \"payment_method\": \"card last4 or 'cash' or 'unknown'\",\n \"confidence\": 0-100,\n \"notes\": \"anything weird or unclear\"\n}\n\nIf confidence < 60, set category to 'other' and add a note. Never invent numbers.",
"role": "user"
}
]
},
"options": {
"temperature": 0.1,
"maxTokens": 600,
"includeImages": true
}
},
"id": "claude-ocr",
"name": "Claude vision OCR",
"type": "@n8n/n8n-nodes-langchain.anthropic",
"typeVersion": 1.2,
"position": [
860,
300
],
"credentials": {
"anthropicApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const raw = $input.first().json.content?.[0]?.text || $input.first().json.text || '';\nconst match = raw.match(/\\{[\\s\\S]*\\}/);\nif (!match) {\n return [{ json: { error: 'no_json', raw, ...$('Extract image attachments').first().json } }];\n}\nlet parsed;\ntry { parsed = JSON.parse(match[0]); }\ncatch (e) { return [{ json: { error: 'bad_json', raw, ...$('Extract image attachments').first().json } }]; }\nconst meta = $('Extract image attachments').first().json;\nconst drive = $('Archive to Drive').first().json;\nreturn [{\n json: {\n ...parsed,\n raw_url: drive.webViewLink || '',\n source_email: meta.from,\n received_at: meta.received_at,\n attachment_name: meta.attachment_name,\n processed_at: new Date().toISOString()\n }\n}];"
},
"id": "parse-ocr",
"name": "Parse + enrich",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1080,
300
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "strict"
},
"conditions": [
{
"leftValue": "={{ $json.error }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "empty"
}
}
]
}
},
"id": "if-clean",
"name": "If parse OK",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1300,
300
]
},
{
"parameters": {
"operation": "append",
"documentId": {
"__rl": true,
"value": "REPLACE_ME_SHEET_ID",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "ledger",
"mode": "name"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"date": "={{ $json.date }}",
"vendor": "={{ $json.vendor }}",
"total": "={{ $json.total }}",
"currency": "={{ $json.currency }}",
"vat": "={{ $json.vat }}",
"category": "={{ $json.category }}",
"payment_method": "={{ $json.payment_method }}",
"raw_url": "={{ $json.raw_url }}",
"notes": "={{ $json.notes }} (conf {{ $json.confidence }})"
}
},
"options": {}
},
"id": "sheets-ledger",
"name": "Append to ledger",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
1520,
200
],
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "append",
"documentId": {
"__rl": true,
"value": "REPLACE_ME_SHEET_ID",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "errors",
"mode": "name"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"received_at": "={{ $json.received_at }}",
"attachment_name": "={{ $json.attachment_name }}",
"error": "={{ $json.error }}",
"raw": "={{ $json.raw }}"
}
},
"options": {}
},
"id": "sheets-errors",
"name": "Log error",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
1520,
400
],
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
}
],
"connections": {
"Gmail receipts inbox": {
"main": [
[
{
"node": "Extract image attachments",
"type": "main",
"index": 0
}
]
]
},
"Extract image attachments": {
"main": [
[
{
"node": "Archive to Drive",
"type": "main",
"index": 0
}
]
]
},
"Archive to Drive": {
"main": [
[
{
"node": "Claude vision OCR",
"type": "main",
"index": 0
}
]
]
},
"Claude vision OCR": {
"main": [
[
{
"node": "Parse + enrich",
"type": "main",
"index": 0
}
]
]
},
"Parse + enrich": {
"main": [
[
{
"node": "If parse OK",
"type": "main",
"index": 0
}
]
]
},
"If parse OK": {
"main": [
[
{
"node": "Append to ledger",
"type": "main",
"index": 0
}
],
[
{
"node": "Log error",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"meta": {
"templateId": "skynetlabs-11"
},
"tags": [
{
"name": "skynetlabs-pack"
},
{
"name": "ocr"
},
{
"name": "bookkeeping"
}
]
}
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.
anthropicApigmailOAuth2googleDriveOAuth2ApigoogleSheetsOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
11 - Receipt OCR to Sheets to Tax. Uses gmailTrigger, googleDrive, anthropic, googleSheets. Event-driven trigger; 8 nodes.
Source: https://github.com/waseemnasir2k26/skynet-automation-pack/blob/main/n8n/11-receipt-ocr-sheets-tax.json — 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 workflow converts emailed timesheets into structured invoice rows in Google Sheets and stores them in the correct Google Drive folder structure.
Overview
Small teams, solo operators, and security-conscious individuals who receive email attachments from external senders. Useful for freelancers, agencies, HR teams, and anyone handling CVs, invoices, or d
Gabarito Aula 4. Uses gmailTrigger, googleGemini, googleDrive, googleSheets. Event-driven trigger; 13 nodes.
Tax Document Organizer. Uses gmailTrigger, gmail, n8n-nodes-pdfvector, googleDrive. Event-driven trigger; 7 nodes.