AutomationFlowsFinance › Process Emailed PDF Invoices Into Quickbooks Bills with Openai

Process Emailed PDF Invoices Into Quickbooks Bills with Openai

ByCodez & AI @codez on n8n.io

Automatically processes vendor invoices received by email, creates QuickBooks bills with full details, and attaches the original PDF. Small/medium businesses using QuickBooks Online Bookkeepers processing 20+ invoices/month Accounting firms managing multiple clients Anyone tired…

Event trigger★★★★☆ complexityAI-powered28 nodesGmail TriggerInformation ExtractorOpenAI ChatQuickBooksHTTP RequestGmail
Finance Trigger: Event Nodes: 28 Complexity: ★★★★☆ AI nodes: yes Added:

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

This workflow follows the Gmail → Gmail Trigger 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
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "39652963-fcf3-4a38-a624-494fff9c0ad2",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3248,
        -304
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 1100,
        "content": "## QuickBooks: AI Invoice Processor\nAutomatically processes vendor invoices received by email.\n\n### Who is this for?\n- Small/medium businesses using QuickBooks Online\n- Bookkeepers processing 20+ invoices/month\n- Accounting firms managing multiple clients\n\n### How it works\n1. **Gmail** monitors for new emails with PDF attachments\n2. **Extract from PDF** converts the PDF to text\n3. **AI (Information Extractor)** extracts structured invoice data and classifies if it's actually an invoice\n4. **QuickBooks** vendor search by name \u2192 gets Vendor ID\n5. **QuickBooks** creates a Bill with extracted data\n6. **PDF attachment** pipeline uploads the original invoice to the bill\n7. **Confirmation email** sent back to the sender\n\n### Setup checklist\n- [ ] Connect **Gmail** credentials\n- [ ] Connect **OpenAI** API credentials\n- [ ] Connect **QuickBooks OAuth2** credentials\n- [ ] Edit the **Config** node with your values:\n  - `realmId` \u2014 your QuickBooks Company ID\n  - `apTeamEmail` \u2014 AP team email for error notifications\n  - `defaultExpenseAccountId` \u2014 see steps below\n- [ ] (Optional) Add Gmail label filter\n\n### How to find `defaultExpenseAccountId`\n1. Log in to **QuickBooks Online**\n2. Go to **Settings** (gear icon) \u2192 **Chart of Accounts**\n3. Find your default expense account (e.g. \"Accounts Payable\", \"Office Supplies\", \"Professional Services\")\n4. Click on the account \u2192 look at the URL: `...?accountId=`**`123`**\n5. Copy the number and paste it into the **Config** node"
      },
      "typeVersion": 1
    },
    {
      "id": "80ffeb80-2ad7-4f75-81e8-b2f0ef11a3da",
      "name": "Gmail Trigger",
      "type": "n8n-nodes-base.gmailTrigger",
      "position": [
        -2624,
        464
      ],
      "parameters": {
        "simple": false,
        "filters": {
          "q": "filename:pdf"
        },
        "options": {
          "downloadAttachments": true
        },
        "pollTimes": {
          "item": [
            {
              "mode": "everyX",
              "unit": "minutes",
              "value": 15
            }
          ]
        }
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "831e4ca0-e94d-4aec-9938-351a14fff163",
      "name": "Config",
      "type": "n8n-nodes-base.set",
      "position": [
        -2400,
        464
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "realm-id",
              "name": "realmId",
              "type": "string",
              "value": "ADD_REALM_ID"
            },
            {
              "id": "ap-email",
              "name": "apTeamEmail",
              "type": "string",
              "value": "user@example.com"
            },
            {
              "id": "confirm-enabled",
              "name": "sendConfirmation",
              "type": "boolean",
              "value": false
            },
            {
              "id": "expense-account",
              "name": "defaultExpenseAccountId",
              "type": "string",
              "value": "ADD_EXPENSE_ACC_ID"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "57b2333f-236c-45fa-858f-075e0b5f61ae",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2704,
        -304
      ],
      "parameters": {
        "color": 4,
        "width": 540,
        "height": 340,
        "content": "## 1. Email Trigger & Config\nMonitors Gmail for new emails with PDF invoice attachments.\n\n**Config node** centralizes all settings:\n- `realmId` \u2014 Your QuickBooks Company ID (find in QB \u2192 Settings \u2192 Account)\n- `apTeamEmail` \u2014 Where error notifications go\n- `sendConfirmation` \u2014 Toggle confirmation emails on/off\n- `defaultExpenseAccountId` \u2014 QB expense account for line items\n\n**Gmail Setup:**\n- Connect your Gmail account\n- Optionally filter by label or sender\n- `Simplify` is set to false to get headers + binary attachments"
      },
      "typeVersion": 1
    },
    {
      "id": "d0090bee-8ac3-4800-9109-d8ab7becd36b",
      "name": "Extract Text from PDF",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        -2176,
        464
      ],
      "parameters": {
        "options": {
          "joinPages": true
        },
        "operation": "pdf",
        "binaryPropertyName": "attachment_0"
      },
      "typeVersion": 1
    },
    {
      "id": "1922f9d5-77cb-4d7f-be4c-2c3c7ba3fb1e",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1792,
        -320
      ],
      "parameters": {
        "color": 6,
        "width": 520,
        "height": 460,
        "content": "## 2. AI Invoice Classification & Extraction\nUses the **Information Extractor** node with OpenAI to:\n\n1. **Classify** if the PDF is actually an invoice (`is_invoice`)\n2. **Extract** structured data if it is:\n   - `vendor_name` \u2014 Company issuing the invoice\n   - `invoice_number` \u2014 Reference number\n   - `amount` \u2014 Total due\n   - `currency` \u2014 3-letter code (USD, EUR, etc.)\n   - `due_date` / `txn_date` \u2014 Dates in YYYY-MM-DD\n   - `line_items` \u2014 Array of {description, amount, quantity}\n\nNon-invoices (receipts, contracts, etc.) are silently skipped.\nInvalid extractions (missing vendor/amount) route to AP team."
      },
      "typeVersion": 1
    },
    {
      "id": "25001adb-7534-4591-b2c9-d281ce45dbb9",
      "name": "AI Extract Invoice Data",
      "type": "@n8n/n8n-nodes-langchain.informationExtractor",
      "position": [
        -1952,
        464
      ],
      "parameters": {
        "text": "={{ $json.text }}",
        "options": {},
        "schemaType": "fromJson",
        "jsonSchemaExample": "{\n  \"is_invoice\": true,\n  \"vendor_name\": \"Acme Corp\",\n  \"invoice_number\": \"INV-2024-001\",\n  \"amount\": 1500.00,\n  \"currency\": \"USD\",\n  \"due_date\": \"2024-12-31\",\n  \"txn_date\": \"2024-12-01\",\n  \"line_items\": [\n    {\n      \"description\": \"Consulting services\",\n      \"amount\": 1500.00,\n      \"quantity\": 1\n    }\n  ]\n}"
      },
      "typeVersion": 1.2
    },
    {
      "id": "630af547-c6aa-4970-8b25-6e15cde0ec8f",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -1888,
        688
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o",
          "cachedResultName": "gpt-4o"
        },
        "options": {
          "temperature": 0
        }
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "b4aed971-35b5-49dd-abc3-7b3b7a3efa13",
      "name": "Is Invoice?",
      "type": "n8n-nodes-base.if",
      "position": [
        -1600,
        464
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "invoice-check",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.output.is_invoice }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "abf0afc3-ff40-44b4-8333-1b81d1ce2ca9",
      "name": "Validate Extracted Data",
      "type": "n8n-nodes-base.code",
      "position": [
        -1376,
        464
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Validate required fields from AI extraction\nconst data = $json.output;\nconst errors = [];\n\nif (!data.vendor_name) errors.push('vendor_name is missing');\nif (!data.amount || data.amount <= 0) errors.push('amount is missing or invalid');\nif (!data.line_items || data.line_items.length === 0) errors.push('no line items found');\n\n// Normalize dates\nconst normalizeDate = (d) => {\n  if (!d) return null;\n  const parsed = new Date(d);\n  if (isNaN(parsed.getTime())) return null;\n  return parsed.toISOString().split('T')[0];\n};\n\nreturn {\n  json: {\n    valid: errors.length === 0,\n    errors: errors,\n    data: {\n      ...data,\n      due_date: normalizeDate(data.due_date),\n      txn_date: normalizeDate(data.txn_date),\n      currency: data.currency || 'USD'\n    }\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "696a8869-5b7b-494f-a438-ab4a8f096b01",
      "name": "Is Valid?",
      "type": "n8n-nodes-base.if",
      "position": [
        -1152,
        464
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "valid-check",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.valid }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "f4a8ba4c-523a-4080-a8f0-5ca820bd32fe",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -944,
        -336
      ],
      "parameters": {
        "color": 3,
        "width": 600,
        "height": 464,
        "content": "## 3. QuickBooks: Vendor Search & Bill Creation\nSearches QuickBooks for the vendor by name extracted by AI.\n- If no vendor found \u2192 error notification to AP team\n\n**Bill Creation (Native QB node):**\n- Uses native QuickBooks node for reliable OAuth2 auth\n- Single line item with total amount (native node limitation)\n- Invoice number included in line description\n- DueDate, TxnDate, TotalAmt from AI extraction\n- Returns Bill ID for PDF attachment step\n\n**Note:** Native QB node supports only one line item per bill.\nIndividual line items are combined into the description field."
      },
      "typeVersion": 1
    },
    {
      "id": "6a3cf6b3-4f64-45ab-872c-e3fb4b81893f",
      "name": "Vendor Found?",
      "type": "n8n-nodes-base.if",
      "position": [
        -704,
        368
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "vendor-exists",
              "operator": {
                "type": "string",
                "operation": "exists"
              },
              "leftValue": "={{ $json.Id }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "7df64e49-0f8a-4b0d-ba1f-4d879b89ecbb",
      "name": "Prepare Bill Data",
      "type": "n8n-nodes-base.code",
      "position": [
        -480,
        272
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const data = $('Validate Extracted Data').item.json.data;\nconst vendorId = $('Search vendor').item.json.Id;\nconst config = $('Config').item.json;\n\n// Build combined description from line items + invoice number\nconst lineDescriptions = (data.line_items || []).map(item => {\n  const qty = item.quantity && item.quantity > 1 ? ` x${item.quantity}` : '';\n  return `${item.description}${qty} - ${data.currency} ${item.amount}`;\n});\n\nlet description = '';\nif (data.invoice_number) description += `Invoice #${data.invoice_number}\\n`;\ndescription += lineDescriptions.join('\\n');\n\nreturn {\n  json: {\n    vendorId: vendorId,\n    amount: data.amount,\n    description: description.substring(0, 4000),\n    accountId: config.defaultExpenseAccountId,\n    dueDate: data.due_date || '',\n    txnDate: data.txn_date || '',\n    totalAmt: data.amount\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "f1ec4648-a509-4f54-aeb3-4102c663d1a1",
      "name": "Create Bill",
      "type": "n8n-nodes-base.quickbooks",
      "position": [
        -256,
        272
      ],
      "parameters": {
        "Line": [
          {
            "Amount": "={{ $json.amount }}",
            "accountId": "={{ $json.accountId }}",
            "DetailType": "AccountBasedExpenseLineDetail",
            "Description": "={{ $json.description }}"
          }
        ],
        "resource": "bill",
        "VendorRef": "={{ $json.vendorId }}",
        "operation": "create",
        "additionalFields": {
          "DueDate": "={{ $json.dueDate }}",
          "TxnDate": "={{ $json.txnDate }}",
          "TotalAmt": "={{ $json.totalAmt }}"
        }
      },
      "credentials": {
        "quickBooksOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "7db34b32-3a46-40a8-b0c3-8a139035fd11",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -128,
        -336
      ],
      "parameters": {
        "color": 2,
        "width": 1004,
        "height": 464,
        "content": "## 4. PDF Attachment Pipeline\nAttaches the original PDF invoice to the created QuickBooks bill.\n\n**Binary data flow:**\nBinary (PDF) is lost after the AI extraction step, so the attachment nodes reference it by name from **Config** using `$('Config').item.binary.attachment_0`.\n\nBoth nodes are connected to Create Bill for **execution order** (they must run after the bill exists), but get binary data via named reference.\n\n**The Force Inline Binary fix:**\nn8n v2 stores binary as DB streams without Content-Length. QuickBooks upload API requires it. The Code node converts streams to inline base64.\n\n**Pipeline:**\n1. Build metadata JSON (AttachableRef \u2192 Bill) \u2014 gets binary from Config by name\n2. Convert metadata to binary field (`myBinaryFile`)\n3. Fetch PDF Binary from Config by name\n4. Merge PDF binary + metadata binary into one item\n5. \u26a0\ufe0f Force Inline Binary (stream \u2192 base64)\n6. Upload multipart to QB `/upload` endpoint"
      },
      "typeVersion": 1
    },
    {
      "id": "fd3ac551-c596-4e60-a49c-ef28e138824d",
      "name": "Build Attachment Metadata",
      "type": "n8n-nodes-base.code",
      "position": [
        -32,
        176
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Binary is lost in the main pipeline after AI extraction.\n// Reference it by name from Config node which still has attachment_0.\nconst binaryData = $('Config').item.binary.attachment_0;\n\nreturn {\n  json: {\n    \"AttachableRef\": [{\n      \"EntityRef\": {\n        \"type\": \"Bill\",\n        \"value\": $('Create Bill').item.json.Id\n      }\n    }],\n    \"FileName\": binaryData.fileName,\n    \"ContentType\": binaryData.mimeType\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "c28826ab-acca-4d7e-86ed-ba02cf0b2f72",
      "name": "Metadata to Binary",
      "type": "n8n-nodes-base.code",
      "position": [
        192,
        176
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const jsonString = JSON.stringify($json);\n  const buffer = Buffer.from(jsonString, 'utf-8');\n\nreturn {\n    json: $json,\n    binary: {          \n      myBinaryFile: {\n        data: buffer.toString('base64'),\n        mimeType: 'application/json',\n        fileName: 'data.json'\n      }\n    }\n  };"
      },
      "typeVersion": 2
    },
    {
      "id": "18017675-b796-4d38-b5da-219a7baa10ec",
      "name": "Fetch PDF Binary",
      "type": "n8n-nodes-base.code",
      "position": [
        192,
        368
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Fetch the original PDF binary from Config node by name.\n// Binary is lost in the main pipeline after AI extraction,\n// but Config still has attachment_0.\nconst configBinary = $('Config').item.binary;\n\nreturn {\n  json: {},\n  binary: configBinary\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "07470c1c-506a-4499-a9d7-7b481db3af7e",
      "name": "Merge PDF + Metadata",
      "type": "n8n-nodes-base.merge",
      "position": [
        416,
        272
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.2
    },
    {
      "id": "c997f004-ac58-4128-9bc2-f74ba73e8573",
      "name": "Force Inline Binary",
      "type": "n8n-nodes-base.code",
      "position": [
        640,
        272
      ],
      "parameters": {
        "jsCode": "// CRITICAL: Force binary data from database stream to inline base64.\n// Without this, QuickBooks upload API fails because n8n v2 stores\n// binary as DB streams which lack Content-Length headers.\n\nconst items = $input.all();\n\nfor (let i = 0; i < items.length; i++) {\n  const item = items[i];\n\n  if (item.binary?.attachment_0) {\n    const buffer = await this.helpers.getBinaryDataBuffer(i, 'attachment_0');\n    const meta = item.binary.attachment_0;\n    item.binary.attachment_0 = {\n      data: buffer.toString('base64'),\n      mimeType: meta.mimeType,\n      fileName: meta.fileName,\n      fileExtension: meta.fileExtension || 'pdf',\n    };\n  }\n\n  if (item.binary?.myBinaryFile) {\n    const buffer = await this.helpers.getBinaryDataBuffer(i, 'myBinaryFile');\n    const meta = item.binary.myBinaryFile;\n    item.binary.myBinaryFile = {\n      data: buffer.toString('base64'),\n      mimeType: meta.mimeType,\n      fileName: meta.fileName,\n      fileExtension: meta.fileExtension || 'json',\n    };\n  }\n}\n\nreturn items;"
      },
      "typeVersion": 2
    },
    {
      "id": "9acd7686-2d20-4411-97e7-1c070a69d49e",
      "name": "Upload PDF to Bill",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        864,
        272
      ],
      "parameters": {
        "url": "=https://quickbooks.api.intuit.com/v3/company/{{ $('Config').item.json.realmId }}/upload",
        "method": "POST",
        "options": {
          "response": {
            "response": {
              "fullResponse": true
            }
          }
        },
        "sendBody": true,
        "contentType": "multipart-form-data",
        "authentication": "predefinedCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "file_metadata_01",
              "parameterType": "formBinaryData",
              "inputDataFieldName": "=myBinaryFile"
            },
            {
              "name": "file_content_01",
              "parameterType": "formBinaryData",
              "inputDataFieldName": "attachment_0"
            }
          ]
        },
        "nodeCredentialType": "quickBooksOAuth2Api"
      },
      "credentials": {
        "quickBooksOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "4943de3b-0962-4410-afbb-5f1b1e9b7b34",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1040,
        -336
      ],
      "parameters": {
        "color": 5,
        "width": 400,
        "height": 420,
        "content": "## 5. Confirmation & Error Handling\n\n**Success path:**\nSends confirmation email back to the invoice sender with bill details (toggleable via Config).\n\n**Skip paths:**\n- \u23ed\ufe0f **Not an invoice** \u2014 PDF is not an invoice, silently skipped\n\n**Error paths:**\n- \u274c **AI extraction failed** \u2014 Missing required fields, forwards to AP team\n- \u274c **Vendor not found** \u2014 Notifies AP team with the vendor name so they can create it in QuickBooks"
      },
      "typeVersion": 1
    },
    {
      "id": "6f3b3896-bfb0-4d1d-b044-764ad7ab9079",
      "name": "Send Confirmation?",
      "type": "n8n-nodes-base.if",
      "position": [
        1088,
        272
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "confirm-check",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $('Config').item.json.sendConfirmation }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "c4de8abb-56db-471a-b4ff-85f9193a79b4",
      "name": "Send Confirmation Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1312,
        272
      ],
      "parameters": {
        "sendTo": "={{ $('Gmail Trigger').item.json.from }}",
        "message": "=Hi,\n\nYour invoice has been processed automatically.\n\u2022 PDF: Attached to bill \u2713\n\nThis is an automated message from the Invoice Processor workflow.",
        "options": {},
        "subject": "=Invoice Processed: {{ $('Validate Extracted Data').item.json.data.invoice_number || 'N/A' }} \u2192 QuickBooks Bill #{{ $('Create Bill').item.json.Id }}",
        "emailType": "text"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "2f320970-0c12-4dec-bfc4-5ca4feaef46a",
      "name": "Error: Extraction Failed",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -928,
        560
      ],
      "parameters": {
        "sendTo": "={{ $('Config').item.json.apTeamEmail }}",
        "message": "=The AI could not extract valid invoice data from an email.\n\n\u2022 From: {{ $('Gmail Trigger').item.json.from }}\n\u2022 Subject: {{ $('Gmail Trigger').item.json.subject }}\n\u2022 Errors: {{ $json.errors.join(', ') }}\n\nPlease process this invoice manually in QuickBooks.\n\nOriginal email ID: {{ $('Gmail Trigger').item.json.messageId }}",
        "options": {},
        "subject": "=[Manual Processing] Could not extract invoice data from {{ $('Gmail Trigger').item.json.from }}",
        "emailType": "text"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "ce12f7ea-c81b-4751-9e13-39f11e53ddc6",
      "name": "Error: Vendor Not Found",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -480,
        464
      ],
      "parameters": {
        "sendTo": "={{ $('Config').item.json.apTeamEmail }}",
        "message": "=An invoice was received but the vendor was not found in QuickBooks.\n\n\u2022 Vendor Name (from invoice): {{ $('Validate Extracted Data').item.json.data.vendor_name }}\n\u2022 Invoice #: {{ $('Validate Extracted Data').item.json.data.invoice_number || 'N/A' }}\n\u2022 Amount: {{ $('Validate Extracted Data').item.json.data.currency }} {{ $('Validate Extracted Data').item.json.data.amount }}\n\u2022 From: {{ $('Gmail Trigger').item.json.from }}\n\n**Action needed:**\n1. Create the vendor in QuickBooks, OR\n2. Check if the vendor exists under a different name\n3. Process the invoice manually\n\nOriginal email ID: {{ $('Gmail Trigger').item.json.messageId }}",
        "options": {},
        "subject": "=[Action Required] Unknown vendor: {{ $('Validate Extracted Data').item.json.data.vendor_name }}",
        "emailType": "text"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "da38272a-a472-4af7-ac6c-a7e490d128b4",
      "name": "Search vendor",
      "type": "n8n-nodes-base.quickbooks",
      "position": [
        -928,
        368
      ],
      "parameters": {
        "limit": 1,
        "filters": {
          "query": "=WHERE DisplayName LIKE '%{{ $json.data.vendor_name }}%'"
        },
        "resource": "vendor",
        "operation": "getAll"
      },
      "credentials": {
        "quickBooksOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Config": {
      "main": [
        [
          {
            "node": "Extract Text from PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Valid?": {
      "main": [
        [
          {
            "node": "Search vendor",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error: Extraction Failed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Bill": {
      "main": [
        [
          {
            "node": "Build Attachment Metadata",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch PDF Binary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Invoice?": {
      "main": [
        [
          {
            "node": "Validate Extracted Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail Trigger": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search vendor": {
      "main": [
        [
          {
            "node": "Vendor Found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Vendor Found?": {
      "main": [
        [
          {
            "node": "Prepare Bill Data",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error: Vendor Not Found",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch PDF Binary": {
      "main": [
        [
          {
            "node": "Merge PDF + Metadata",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Extract Invoice Data",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Bill Data": {
      "main": [
        [
          {
            "node": "Create Bill",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Metadata to Binary": {
      "main": [
        [
          {
            "node": "Merge PDF + Metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Confirmation?": {
      "main": [
        [
          {
            "node": "Send Confirmation Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload PDF to Bill": {
      "main": [
        [
          {
            "node": "Send Confirmation?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Force Inline Binary": {
      "main": [
        [
          {
            "node": "Upload PDF to Bill",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge PDF + Metadata": {
      "main": [
        [
          {
            "node": "Force Inline Binary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Text from PDF": {
      "main": [
        [
          {
            "node": "AI Extract Invoice Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Extract Invoice Data": {
      "main": [
        [
          {
            "node": "Is Invoice?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Extracted Data": {
      "main": [
        [
          {
            "node": "Is Valid?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Attachment Metadata": {
      "main": [
        [
          {
            "node": "Metadata to Binary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

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.

Pro

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

About this workflow

Automatically processes vendor invoices received by email, creates QuickBooks bills with full details, and attaches the original PDF. Small/medium businesses using QuickBooks Online Bookkeepers processing 20+ invoices/month Accounting firms managing multiple clients Anyone tired…

Source: https://n8n.io/workflows/14085/ — original creator credit. Request a take-down →

More Finance workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Finance

How It Works Trigger: Watches for new emails in Gmail with PDF/image attachments. OCR: Sends the attachment to OCR.space API (https://ocr.space/OCRAPI) to extract invoice text. Parsing: Extracts key f

Gmail Trigger, Google Sheets, Slack +3
Finance

This workflow is a sophisticated, end-to-end solution that automates the entire billing lifecycle, from invoice creation to intelligent payment reminders and status tracking. It's designed to give you

Google Sheets Trigger, Google Docs, OpenAI +4
Finance

This is the ultimate sales-to-cash automation. When a deal in Airtable is marked "Approved for Invoicing," this workflow intelligently syncs customer data across QuickBooks and Stripe (creating them i

Airtable Trigger, Stripe, QuickBooks +2
Finance

Automated Stripe Payment to QuickBooks Sales Receipt

HTTP Request, Stripe, Stripe Trigger +1
Finance

This workflow allows you to quickly generate and send invoices by collecting missing billing details from clients through an automated form and email sequence. It integrates Gmail and QuickBooks Onlin

QuickBooks, Form Trigger, Gmail