Operations workflow

Automated Invoice Processing Workflow for Small Businesses

Streamline your invoice processing with this comprehensive n8n workflow that handles invoice receipt, data extraction, approval routing, and payment tracking automatically.

These prompts work best with Jasper AI or Copy.ai

Automated Invoice Processing Workflow for Small Businesses

Why This Automation Matters

Manual invoice processing is one of the most time-consuming tasks for small business operations teams. This workflow eliminates hours of repetitive work by automatically:

By automating these processes, you’ll reduce processing time by 80%, minimize data entry errors, and ensure consistent approval workflows while maintaining complete audit trails.

What You Need Before Starting

Required Integrations

Required Tools & Services

Permissions & Access

Complete Node-by-Node Build Instructions

1. Email Trigger Node

2. Filter Invoices Node

3. Extract Attachments Node

4. Save to Cloud Storage Node

5. OCR Data Extraction Node

6. Parse Invoice Data Node

7. Vendor Validation Node

8. Purchase Order Matching Node

9. Approval Routing Decision Node

10. Manager Approval Node (for mid-range amounts)

11. Senior Management Approval Node (for high amounts)

12. Update Accounting System Node

13. Payment Scheduling Node

14. Notification Node

15. Archive Processing Node

16. Error Handling Node

Full Workflow JSON Code

{
  "name": "Invoice Processing Automation",
  "nodes": [
    {
      "parameters": {
        "protocol": "imap",
        "server": "imap.gmail.com",
        "port": 993,
        "secure": true,
        "email": "={{ $credentials.email }}",
        "password": "={{ $credentials.password }}",
        "folder": "INBOX",
        "allowUnauthorizedCerts": false,
        "markSeen": true,
        "downloadAttachments": true
      },
      "name": "Email Trigger",
      "type": "n8n-nodes-base.emailReadImap",
      "typeVersion": 2,
      "position": [240, 300],
      "id": "email-trigger"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "invoice-filter",
              "leftValue": "={{ $json.subject.toLowerCase() }}",
              "rightValue": "invoice",
              "operator": {
                "type": "string",
                "operation": "contains"
              }
            },
            {
              "id": "attachment-check",
              "leftValue": "={{ $json.attachments ? $json.attachments.length : 0 }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "name": "Filter Invoices",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [460, 300],
      "id": "filter-invoices"
    },
    {
      "parameters": {
        "jsCode": "// Extract and process attachments\nconst attachments = $input.first().json.attachments || [];\nconst invoiceAttachments = [];\n\nfor (const attachment of attachments) {\n  const filename = attachment.filename.toLowerCase();\n  if (filename.includes('.pdf') || filename.includes('.png') || filename.includes('.jpg')) {\n    const timestamp = new Date().toISOString().split('T')[0];\n    const processedFilename = `${timestamp}_${attachment.filename}`;\n    \n    invoiceAttachments.push({\n      originalName: attachment.filename,\n      processedName: processedFilename,\n      data: attachment.data,\n      mimeType: attachment.mimeType,\n      size: attachment.size\n    });\n  }\n}\n\nreturn invoiceAttachments.map(attachment => ({\n  json: {\n    ...attachment,\n    emailSubject: $input.first().json.subject,\n    emailFrom: $input.first().json.from,\n    emailDate: $input.first().json.date,\n    processingId: `INV_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`\n  },\n  binary: {\n    data: {\n      data: attachment.data,\n      mimeType: attachment.mimeType,\n      fileName: attachment.processedName\n    }\n  }\n}));"
      },
      "name": "Extract Attachments",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [680, 300],
      "id": "extract-attachments"
    },
    {
      "parameters": {
        "operation": "upload",
        "fileId": {
          "__rl": true,
          "value": "1234567890abcdef",
          "mode": "list",
          "cachedResultName": "Invoices/Incoming"
        },
        "fileName": "={{ $json.processedName }}",
        "binaryData": true,
        "options": {
          "parents": ["1234567890abcdef"]
        }
      },
      "name": "Save to Google Drive",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [900, 300],
      "id": "save-to-drive"
    },
    {
      "parameters": {
        "operation": "textDetection",
        "binaryPropertyName": "data"
      },
      "name": "OCR Data Extraction",
      "type": "n8n-nodes-base.googleCloudVision",
      "typeVersion": 1,
      "position": [1120, 300],
      "id": "ocr-extraction"
    },
    {
      "parameters": {
        "jsCode": "// Parse OCR results and extract invoice data\nconst ocrText = $json.fullTextAnnotation?.text || '';\nconst invoiceData = {\n  processingId: $json.processingId,\n  rawText: ocrText\n};\n\n// Extract invoice number\nconst invoiceNumberMatch = ocrText.match(/(?:invoice|inv)\\s*#?\\s*:?\\s*([A-Z0-9\\-]+)/i);\ninvoiceData.invoiceNumber = invoiceNumberMatch ? invoiceNumberMatch[1] : null;\n\n// Extract vendor name (usually at the top)\nconst lines = ocrText.split('\\n').filter(line => line.trim());\ninvoiceData.vendorName = lines[0]?.trim() || null;\n\n// Extract total amount\nconst amountMatch = ocrText.match(/(?:total|amount due)\\s*:?\\s*\\$?([0-9,]+\\.\\d{2})/i);\ninvoiceData.totalAmount = amountMatch ? parseFloat(amountMatch[1].replace(',', '')) : null;\n\n// Extract date\nconst dateMatch = ocrText.match(/(\\d{1,2}[-\\/]\\d{1,2}[-\\/]\\d{2,4})/);\ninvoiceData.invoiceDate = dateMatch ? new Date(dateMatch[1]).toISOString().split('T')[0] : null;\n\n// Extract tax amount\nconst taxMatch = ocrText.match(/tax\\s*:?\\s*\\$?([0-9,]+\\.\\d{2})/i);\ninvoiceData.taxAmount = taxMatch ? parseFloat(taxMatch[1].replace(',', '')) : null;\n\n// Validation flags\ninvoiceData.isValid = !!(invoiceData.invoiceNumber && invoiceData.vendorName && invoiceData.totalAmount);\ninvoiceData.missingFields = [];\n\nif (!invoiceData.invoiceNumber) invoiceData.missingFields.push('Invoice Number');\nif (!invoiceData.vendorName) invoiceData.missingFields.push('Vendor Name');\nif (!invoiceData.totalAmount) invoiceData.missingFields.push('Total Amount');\nif (!invoiceData.invoiceDate) invoiceData.missingFields.push('Invoice Date');\n\nreturn [{ json: invoiceData }];"
      },
      "name": "Parse Invoice Data",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [1340, 300],
      "id": "parse-invoice-data"
    },
    {
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "value": "1234567890abcdef",
          "mode": "list",
          "cachedResultName": "Approved Vendors"
        },
        "sheetName": "Sheet1",
        "columns": "A:E",
        "filters": {
          "conditions": [
            {
              "column": "Vendor Name",
              "condition": "contains",
              "value": "={{ $json.vendorName }}"
            }
          ]
        }
      },
      "name": "Vendor Validation",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [1560, 300],
      "id": "vendor-validation"
    },
    {
      "parameters": {
        "url": "={{ $credentials.quickbooks.baseUrl }}/v3/companyid/{{ $credentials.quickbooks.companyId }}/purchaseorders",
        "authentication": "oAuth2Api",
        "httpHeaderAuth": {
          "__rl": true,
          "value": "quickbooks",
          "mode": "list",
          "cachedResultName": "QuickBooks API"
        },
        "qs": {
          "query": "SELECT * FROM PurchaseOrder WHERE Vendor = '{{ $json.vendorName }}' AND TotalAmt = {{ $json.totalAmount }}"
        }
      },
      "name": "Purchase Order Matching",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [1780, 300],
      "id": "po-matching"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive":

Want the complete prompt pack?

500+ prompts, organised and ready to use.

Download the Full Pack — $19