AutomationFlowsEmail & Gmail › Automated Multi-tier Expense Approval & Reimbursement with Jotform.

Automated Multi-tier Expense Approval & Reimbursement with Jotform.

ByJitesh Dugar @jiteshdugar on n8n.io

Eliminate weeks of waiting and mountains of paperwork with intelligent expense automation that processes reimbursements in 72 hours instead of 2–3 weeks — delivering 90% reduction in manual processing time.

Event trigger★★★★☆ complexity21 nodesSlackGoogle SheetsGmailJot Form Trigger
Email & Gmail Trigger: Event Nodes: 21 Complexity: ★★★★☆ Added:

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

This workflow follows the Gmail → Google Sheets 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": "0798c2b1-7ed5-45ce-a26c-2e8f7ada6a43",
      "name": "Parse Form Data",
      "type": "n8n-nodes-base.code",
      "position": [
        -864,
        -16
      ],
      "parameters": {
        "jsCode": "// Parse form data and prepare for processing\nconst formData = $input.first().json;\n\nreturn {\n  json: {\n    submissionId: formData.submissionID || Date.now().toString(),\n    employeeName: formData.employeeName || formData.q3_employeeName,\n    employeeEmail: formData.employeeEmail || formData.q4_employeeEmail,\n    employeeId: formData.employeeId || formData.q5_employeeId,\n    amount: parseFloat(formData.amount || formData.q6_amount),\n    category: formData.category || formData.q7_category,\n    merchant: formData.merchant || formData.q8_merchant,\n    date: formData.date || formData.q9_date,\n    description: formData.description || formData.q10_description,\n    receiptUrl: formData.receiptUrl || formData.q11_receipt,\n    submittedAt: new Date().toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "df2085e9-dc08-4f25-a99e-009e2340b47c",
      "name": "Validate Policy",
      "type": "n8n-nodes-base.code",
      "position": [
        -672,
        -16
      ],
      "parameters": {
        "jsCode": "// Company policy rules and validation\nconst data = $input.first().json;\n\nconst policy = {\n  maxAmounts: {\n    meals: 75,\n    travel: 500,\n    office_supplies: 200,\n    software: 1000,\n    entertainment: 150,\n    other: 100\n  },\n  approvedCategories: ['meals', 'travel', 'office_supplies', 'software', 'entertainment'],\n  autoApproveThreshold: 100,\n  managerApprovalThreshold: 500\n};\n\nconst amount = data.amount;\nconst category = data.category;\n\nlet policyViolations = [];\nlet requiresApproval = 'auto';\n\nif (!policy.approvedCategories.includes(category)) {\n  policyViolations.push('Category not approved');\n}\n\nif (policy.maxAmounts[category] && amount > policy.maxAmounts[category]) {\n  policyViolations.push('Amount exceeds category limit');\n}\n\nif (amount < policy.autoApproveThreshold && policyViolations.length === 0) {\n  requiresApproval = 'auto';\n} else if (amount >= policy.autoApproveThreshold && amount < policy.managerApprovalThreshold) {\n  requiresApproval = 'manager';\n} else if (amount >= policy.managerApprovalThreshold) {\n  requiresApproval = 'director';\n}\n\nreturn {\n  json: {\n    ...data,\n    policyViolations: policyViolations,\n    isCompliant: policyViolations.length === 0,\n    requiresApproval: requiresApproval,\n    status: 'pending'\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "4ac52b34-7a64-49f5-b368-43d8b99bcab3",
      "name": "Check Violations",
      "type": "n8n-nodes-base.if",
      "position": [
        -464,
        -16
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "condition1",
              "operator": {
                "type": "number",
                "operation": "equals"
              },
              "leftValue": "={{ $json.policyViolations.length }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "9d132c2f-7f99-4b36-b543-27131702460c",
      "name": "Set Rejection",
      "type": "n8n-nodes-base.set",
      "position": [
        -272,
        144
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "status",
              "name": "status",
              "type": "string",
              "value": "rejected"
            },
            {
              "id": "rejectionReason",
              "name": "rejectionReason",
              "type": "string",
              "value": "=Policy violations: {{ $json.policyViolations.join(', ') }}"
            }
          ]
        }
      },
      "typeVersion": 3.3
    },
    {
      "id": "47566677-5342-4b26-9974-793526a59bd9",
      "name": "Route Auto",
      "type": "n8n-nodes-base.if",
      "position": [
        -272,
        -176
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "condition1",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.requiresApproval }}",
              "rightValue": "auto"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "c0b971bc-a5ce-48b6-978c-54ae02112505",
      "name": "Auto Approve",
      "type": "n8n-nodes-base.set",
      "position": [
        -64,
        -272
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "status",
              "name": "status",
              "type": "string",
              "value": "approved"
            },
            {
              "id": "approvedBy",
              "name": "approvedBy",
              "type": "string",
              "value": "System (Auto-Approved)"
            },
            {
              "id": "approvedAt",
              "name": "approvedAt",
              "type": "string",
              "value": "={{ new Date().toISOString() }}"
            }
          ]
        }
      },
      "typeVersion": 3.3
    },
    {
      "id": "eef387ab-bee8-4570-8cca-26ccbd30abeb",
      "name": "Route Manager",
      "type": "n8n-nodes-base.if",
      "position": [
        -64,
        -80
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "condition1",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.requiresApproval }}",
              "rightValue": "manager"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "74152681-2cf6-4e76-aabb-87ae00a86048",
      "name": "Slack Manager",
      "type": "n8n-nodes-base.slack",
      "position": [
        144,
        -224
      ],
      "parameters": {
        "text": "=New Expense Approval Required\nEmployee: {{ $json.employeeName }}\nAmount: ${{ $json.amount }}\nCategory: {{ $json.category }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C01234567"
        },
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "e05fba8b-60e5-4438-9f7a-cd828bace8b8",
      "name": "Slack Director",
      "type": "n8n-nodes-base.slack",
      "position": [
        144,
        -16
      ],
      "parameters": {
        "text": "=High-Value Expense - Director Approval\nEmployee: {{ $json.employeeName }}\nAmount: ${{ $json.amount }}\nCategory: {{ $json.category }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C98765432"
        },
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "b7a58104-fc21-485a-8c93-edb1dc253b2b",
      "name": "Log to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        336,
        -64
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [
            "submissionId"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "1af29bbe-b3ee-42ee-91a9-388b70659c48",
      "name": "Is Approved",
      "type": "n8n-nodes-base.if",
      "position": [
        544,
        -64
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "condition1",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.status }}",
              "rightValue": "approved"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "0e8c5d12-fee3-4c7d-bc16-77a79e39befe",
      "name": "Rejection Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        752,
        0
      ],
      "parameters": {
        "sendTo": "={{ $json.employeeEmail }}",
        "message": "=Hi, Your expense for {{ $json.amount}} has been rejected as it did not matches with the company's validation policy.",
        "options": {},
        "subject": "=Expense Rejected - ${{ $json.amount }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "1b5d88c6-fe8f-4984-9e9e-19789ba76c60",
      "name": "Approved",
      "type": "n8n-nodes-base.gmail",
      "position": [
        752,
        -192
      ],
      "parameters": {
        "sendTo": "={{ $json.employeeEmail }}",
        "message": "Hi, Your expense for {{ $json.Amount }} has been approved",
        "options": {},
        "subject": "=Expense Approved - ${{ $json.amount }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "6382a19b-08de-4466-9013-696d5ffac4db",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1200,
        -208
      ],
      "parameters": {
        "height": 432,
        "content": "\ud83d\udce9 Trigger: New Expense Submission\nCaptures employee expense details \nand receipt upload from Jotform.\nCreate your form for free on [Jotform using this link](https://www.jotform.com/?partner=mediajade) \n"
      },
      "typeVersion": 1
    },
    {
      "id": "5b890f09-277e-41e6-9628-dd6907b14e5f",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -912,
        -160
      ],
      "parameters": {
        "width": 192,
        "content": "\ud83e\uddfe Parse & Normalize Data\nExtracts key fields (name, email, amount, \ncategory, merchant, receipt URL) \nfor further processing.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "fe3ea799-b6f3-48f4-bbc4-96623e1e8256",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -720,
        112
      ],
      "parameters": {
        "width": 208,
        "height": 192,
        "content": "\u2699\ufe0f Policy Validation\nChecks compliance against company rules:\n- Allowed categories\n- Max amount limits\n- Auto/Manager/Director approval routing\n"
      },
      "typeVersion": 1
    },
    {
      "id": "11112b1f-af67-4c7c-86cc-b4ccfe83a249",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -512,
        -160
      ],
      "parameters": {
        "width": 176,
        "content": "\ud83d\udeab Compliance Check\nIf violations exist \u2192 reject\nElse \u2192 continue for approval routing\n"
      },
      "typeVersion": 1
    },
    {
      "id": "054e826f-590f-4de0-8ccd-d299d2f6e044",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -288,
        -368
      ],
      "parameters": {
        "width": 576,
        "height": 496,
        "content": "\u26a1 Routing Decision\nDetermines if expense should be:\n- Auto-approved\n- Sent for manager approval\n- Sent for director approval\n"
      },
      "typeVersion": 1
    },
    {
      "id": "6ebce30b-f8d7-4bf2-bdf3-6f33b8363a55",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        320,
        -224
      ],
      "parameters": {
        "width": 208,
        "height": 384,
        "content": "\ud83d\udcca Audit Logging\nAppends expense status, \napprover details, and decision \nto Google Sheets for tracking.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "f5e44753-7705-41e1-bdac-0158deea26ab",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        544,
        -384
      ],
      "parameters": {
        "width": 384,
        "height": 576,
        "content": "\ud83d\udd0d Approval Check\nBranches workflow to send \napproval or rejection emails \nbased on final status.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "2eb6d9f2-7f9d-453f-a548-cf2660f7248c",
      "name": "Jotform Trigger",
      "type": "n8n-nodes-base.jotFormTrigger",
      "position": [
        -1120,
        -16
      ],
      "parameters": {
        "form": "252860582136459"
      },
      "credentials": {
        "jotFormApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Route Auto": {
      "main": [
        [
          {
            "node": "Auto Approve",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Route Manager",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Approved": {
      "main": [
        [
          {
            "node": "Approved",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Rejection Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Auto Approve": {
      "main": [
        [
          {
            "node": "Log to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log to Sheets": {
      "main": [
        [
          {
            "node": "Is Approved",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route Manager": {
      "main": [
        [
          {
            "node": "Slack Manager",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack Director",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Rejection": {
      "main": [
        [
          {
            "node": "Log to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack Manager": {
      "main": [
        [
          {
            "node": "Log to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack Director": {
      "main": [
        [
          {
            "node": "Log to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Jotform Trigger": {
      "main": [
        [
          {
            "node": "Parse Form Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Form Data": {
      "main": [
        [
          {
            "node": "Validate Policy",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Policy": {
      "main": [
        [
          {
            "node": "Check Violations",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Violations": {
      "main": [
        [
          {
            "node": "Route Auto",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Set Rejection",
            "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

Eliminate weeks of waiting and mountains of paperwork with intelligent expense automation that processes reimbursements in 72 hours instead of 2–3 weeks — delivering 90% reduction in manual processing time.

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

More Email & Gmail workflows → · Browse all categories →

Related workflows

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

Email & Gmail

Categories: Payments, Project Operations, Client Onboarding

Stripe Trigger, Google Drive, ClickUp +4
Email & Gmail

Sync your Google Calendar events with Google Sheets and get daily Slack summaries with meeting statistics. FEATURES:

Google Calendar Trigger, Google Sheets, Slack +3
Email & Gmail

Stop chasing blurry receipts and manually typing expense data. This workflow creates an intelligent, "snap-and-submit" reimbursement pipeline that hosts photos via UploadToURL, extracts deep data via

Form Trigger, N8N Nodes Uploadtourl, HTTP Request +3
Email & Gmail

This template automates internal equipment and supply purchase requests for operations, HR, and IT teams. Requests are submitted via a built-in n8n form, automatically approved for small amounts, and

Form Trigger, Google Sheets, Slack +1
Email & Gmail

Automate daily KPI tracking and reporting by integrating ClickUp tasks and Google Sheets lead data into a unified dashboard. This workflow computes performance metrics, analyzes sentiment, and deliver

Error Trigger, Slack, ClickUp +2