{
  "name": "Document & Invoice Processing",
  "nodes": [
    {
      "parameters": {
        "pollTimes": {
          "item": [{ "mode": "everyMinute", "minute": 10 }]
        },
        "filters": {
          "readStatus": "unread",
          "hasAttachments": true
        },
        "simple": false,
        "downloadAttachments": true
      },
      "id": "gmail-trigger",
      "name": "Email with Attachment",
      "type": "n8n-nodes-base.gmailTrigger",
      "typeVersion": 1,
      "position": [240, 300]
    },
    {
      "parameters": {
        "jsCode": "const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  const email = item.json;\n  const attachments = email.payload?.parts?.filter(p => p.filename && p.filename.length > 0) || [];\n  const fromAddress = email.from?.value?.[0]?.address || '';\n  const fromName = email.from?.value?.[0]?.name || fromAddress.split('@')[0];\n  const subject = email.subject || '';\n  const bodyText = (email.text || email.snippet || '').substring(0, 1500);\n  const senderDomain = fromAddress.split('@')[1] || '';\n  \n  for (const att of attachments) {\n    const filename = att.filename || 'unknown';\n    const ext = filename.split('.').pop().toLowerCase();\n    \n    if (['pdf', 'png', 'jpg', 'jpeg', 'csv', 'xlsx', 'xls', 'doc', 'docx'].includes(ext)) {\n      results.push({\n        json: {\n          filename,\n          extension: ext,\n          fromAddress,\n          fromName,\n          senderDomain,\n          subject,\n          bodyText,\n          emailId: email.id,\n          receivedDate: new Date().toISOString()\n        }\n      });\n    }\n  }\n}\n\nif (results.length === 0) {\n  return [{ json: { skip: true } }];\n}\nreturn results;"
      },
      "id": "extract-attachments",
      "name": "Extract Attachments",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [460, 300]
    },
    {
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true },
          "conditions": [
            {
              "leftValue": "={{ $json.skip }}",
              "rightValue": true,
              "operator": { "type": "boolean", "operation": "notEquals" }
            }
          ]
        }
      },
      "id": "has-attachments",
      "name": "Has Valid Files?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [680, 300]
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first().json;\n\nconst requestBody = {\n  model: 'claude-sonnet-4-20250514',\n  max_tokens: 1024,\n  messages: [\n    {\n      role: 'user',\n      content: 'Classify this document attachment based on ALL available context.\\n\\nFILENAME: ' + item.filename + '\\nFILE TYPE: ' + item.extension + '\\nSENDER: ' + item.fromName + ' (' + item.fromAddress + ')\\nSENDER DOMAIN: ' + item.senderDomain + '\\nSUBJECT: ' + item.subject + '\\nEMAIL BODY PREVIEW: ' + item.bodyText.substring(0, 800) + '\\n\\nUse ALL the context above (sender domain tells you the company, email body often says what the attachment is, subject gives context).\\n\\nRespond with JSON only:\\n{\\n  \"document_type\": \"invoice\" | \"receipt\" | \"contract\" | \"proposal\" | \"report\" | \"statement\" | \"tax_form\" | \"other\",\\n  \"vendor_or_sender\": \"company or person name (use sender domain and name to determine)\",\\n  \"amount\": \"dollar amount if mentioned in email body, otherwise null\",\\n  \"due_date\": \"due date if mentioned, otherwise null\",\\n  \"action_needed\": \"none\" | \"pay\" | \"review\" | \"sign\" | \"file\",\\n  \"urgency\": \"low\" | \"medium\" | \"high\",\\n  \"notes\": \"one sentence about what this document is based on the full context\"\\n}'\n    }\n  ]\n};\n\nreturn [{ json: { requestBody, ...item } }];"
      },
      "id": "build-request",
      "name": "Build Claude Request",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [920, 240]
    },
    {
      "parameters": {
        "url": "https://api.anthropic.com/v1/messages",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "x-api-key", "value": "={{ $env.ANTHROPIC_API_KEY }}" },
            { "name": "anthropic-version", "value": "2023-06-01" },
            { "name": "content-type", "value": "application/json" }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.requestBody) }}",
        "options": {}
      },
      "id": "call-claude",
      "name": "Classify Document",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1140, 240]
    },
    {
      "parameters": {
        "jsCode": "const response = $input.first().json;\nconst content = response.content[0].text;\nconst prev = $('Build Claude Request').first().json;\n\nlet parsed;\ntry {\n  const jsonMatch = content.match(/\\{[\\s\\S]*\\}/);\n  parsed = JSON.parse(jsonMatch[0]);\n} catch (e) {\n  parsed = {\n    document_type: 'other',\n    vendor_or_sender: prev.fromName || 'Unknown',\n    amount: null,\n    due_date: null,\n    action_needed: 'review',\n    urgency: 'medium',\n    notes: 'Could not classify automatically'\n  };\n}\n\nreturn [{\n  json: {\n    ...parsed,\n    filename: prev.filename,\n    fromAddress: prev.fromAddress,\n    fromName: prev.fromName,\n    subject: prev.subject,\n    receivedDate: prev.receivedDate\n  }\n}];"
      },
      "id": "parse-classification",
      "name": "Parse Classification",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [1360, 240]
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first().json;\nreturn [{\n  json: {\n    Date: item.receivedDate,\n    Filename: item.filename,\n    Type: item.document_type,\n    Vendor: item.vendor_or_sender,\n    Amount: item.amount || '',\n    'Due Date': item.due_date || '',\n    Action: item.action_needed,\n    Urgency: item.urgency,\n    Notes: item.notes,\n    'Sender Email': item.fromAddress,\n    Subject: item.subject\n  }\n}];"
      },
      "id": "build-sheet-row",
      "name": "Build Sheet Row",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [1580, 160]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "={{ $env.GOOGLE_SHEET_ID }}"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Documents"
        },
        "columns": {
          "mappingMode": "autoMapInputData"
        },
        "options": {}
      },
      "id": "log-to-sheet",
      "name": "Log to Google Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [1800, 160]
    },
    {
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true },
          "conditions": [
            {
              "leftValue": "={{ $json.action_needed }}",
              "rightValue": "none",
              "operator": { "type": "string", "operation": "notEquals" }
            }
          ]
        }
      },
      "id": "needs-action",
      "name": "Action Needed?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [1580, 360]
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first().json;\nconst urgencyEmoji = item.urgency === 'high' ? '!!' : item.urgency === 'medium' ? '!' : '';\nreturn [{ json: { text: urgencyEmoji + ' Document needs action\\nFile: ' + item.filename + '\\nType: ' + item.document_type + '\\nFrom: ' + item.vendor_or_sender + '\\nAmount: ' + (item.amount || 'not specified') + '\\nDue: ' + (item.due_date || 'not specified') + '\\nAction: ' + item.action_needed + '\\nNotes: ' + item.notes } }];"
      },
      "id": "build-slack",
      "name": "Build Slack Alert",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [1800, 300]
    },
    {
      "parameters": {
        "url": "={{ $env.SLACK_WEBHOOK_URL }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json) }}",
        "options": {}
      },
      "id": "notify-action",
      "name": "Notify Action Needed",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [2020, 300]
    }
  ],
  "connections": {
    "Email with Attachment": { "main": [[{ "node": "Extract Attachments", "type": "main", "index": 0 }]] },
    "Extract Attachments": { "main": [[{ "node": "Has Valid Files?", "type": "main", "index": 0 }]] },
    "Has Valid Files?": {
      "main": [
        [{ "node": "Build Claude Request", "type": "main", "index": 0 }],
        []
      ]
    },
    "Build Claude Request": { "main": [[{ "node": "Classify Document", "type": "main", "index": 0 }]] },
    "Classify Document": { "main": [[{ "node": "Parse Classification", "type": "main", "index": 0 }]] },
    "Parse Classification": {
      "main": [[
        { "node": "Build Sheet Row", "type": "main", "index": 0 },
        { "node": "Action Needed?", "type": "main", "index": 0 }
      ]]
    },
    "Build Sheet Row": { "main": [[{ "node": "Log to Google Sheet", "type": "main", "index": 0 }]] },
    "Action Needed?": {
      "main": [
        [{ "node": "Build Slack Alert", "type": "main", "index": 0 }],
        []
      ]
    },
    "Build Slack Alert": { "main": [[{ "node": "Notify Action Needed", "type": "main", "index": 0 }]] }
  },
  "settings": { "executionOrder": "v1" },
  "meta": { "templateCredsSetupCompleted": false, "instanceId": "loomiq-template" },
  "tags": ["loomiq", "documents", "ai"]
}
