{
  "id": "JZ8C3IFRtHrntshw",
  "name": "Approval Workflow Handler \u2013 SendGrid & Baserow",
  "tags": [],
  "nodes": [
    {
      "id": "d56b5b5a-2256-4126-b430-000c2d4a983d",
      "name": "\ud83d\udcdd Workflow Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -224,
        -80
      ],
      "parameters": {
        "width": 550,
        "height": 722,
        "content": "## How it works\nThis workflow orchestrates a complete approval lifecycle from submission to resolution. A manual trigger kicks things off, after which incoming request data is verified for completeness.  Invalid submissions immediately notify the requester and terminate gracefully.  Valid requests are routed to the correct approver, logged as **Pending** in Baserow, and the approver is emailed via SendGrid with a unique approval-link.  The workflow then suspends on a *Wait* node until that link is called, resuming with the approver\u2019s decision.  Depending on the response, the record is updated to **Approved** or **Rejected**, and all stakeholders receive a status email.  Finally, execution metadata is merged and the run is cleanly closed.\n\n## Setup steps\n1. Create a Baserow database/table with fields: Request ID, Requester, Department, Amount, Status, SubmittedAt, DecisionAt.\n2. Add SendGrid credentials in n8n and verify sender addresses.\n3. Replace all placeholder IDs (databaseId, tableId) and email addresses inside the nodes.\n4. Publish the workflow, set it *active*, and manually trigger once to generate sample data.\n5. Provide real request data to the Manual Trigger (or call via REST, e.g., the *Execute Workflow* API).\n6. Share the generated Approval Link with approvers\u2014clicking it will resume the workflow.\n7. Monitor Baserow for live status or extend with additional analytics as needed."
      },
      "typeVersion": 1
    },
    {
      "id": "82c16db2-2402-474d-81b9-9ef8d6eaaaaa",
      "name": "Section \u2022 Trigger & Validation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        432,
        -48
      ],
      "parameters": {
        "color": 7,
        "width": 658,
        "height": 782,
        "content": "## Trigger & Validation\nThis group covers the initial kick-off and data hygiene.  The Manual Trigger acts as the entry point\u2014ideal for testing or external API calls via the *Execute Workflow* endpoint.  Immediately after triggering, the **Validate Request Data** Code node inspects the payload for required properties (requestId, requesterName, department, amount).  An IF node branches the flow: valid data proceeds, while invalid data triggers a SendGrid alert and gracefully ends.  Feel free to add more sophisticated validation (regex, schema checks) or transform raw inputs in the Set node if your upstream system sends fields in different shapes."
      },
      "typeVersion": 1
    },
    {
      "id": "b047a5f7-ef49-46e4-8c36-fac14d6f5952",
      "name": "Section \u2022 Notify & Wait",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1088,
        -32
      ],
      "parameters": {
        "color": 7,
        "width": 930,
        "height": 782,
        "content": "## Notification & Await Response\nOnce a request is verified, we calculate the correct approver (e.g., based on department rules) and send a nicely formatted SendGrid email containing request details and a unique approval link.  The **Wait for Approval Response** node suspends the workflow execution, freeing resources until an approver clicks their link.  That link points to the automatically generated resume-URL of the Wait node, sending back JSON like `{ approved: true, comments: \"Looks good\" }`.  When the workflow resumes, the data is cleaned up and routed to the proper branch."
      },
      "typeVersion": 1
    },
    {
      "id": "dd65d721-f1ac-4303-a6a8-f4e89c6a0e61",
      "name": "Section \u2022 Post-Processing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2016,
        -32
      ],
      "parameters": {
        "color": 7,
        "width": 1074,
        "height": 782,
        "content": "## Post-Processing & Storage\nAfter resumption, an IF node evaluates the `approved` flag.  Approved requests update the Baserow row to **Approved**, rejected ones to **Rejected**.  Each path sends a follow-up email via SendGrid to inform both requester and approver of the final status.  A Merge node converges all terminal branches for unified logging or further actions (analytics, archival, etc.).  You can expand this section with Slack, Teams, or CRM hooks as business needs evolve."
      },
      "typeVersion": 1
    },
    {
      "id": "4efef463-7024-4e0c-a425-3d0660a21659",
      "name": "Start Workflow",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        432,
        304
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "2198a03b-33e3-48d6-9323-65421cd2f283",
      "name": "Validate Request Data",
      "type": "n8n-nodes-base.code",
      "position": [
        624,
        304
      ],
      "parameters": {
        "jsCode": "// Basic validation & normalization\nconst item = $json;\nconst required = ['requestId', 'requesterName', 'department', 'amount'];\nlet invalid = false;\nfor (const field of required) {\n  if (!item[field] || item[field] === '') invalid = true;\n}\nreturn [{\n  json: {\n    ...item,\n    invalid,\n    timestamp: new Date().toISOString()\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c7233538-6f32-4d57-8f89-94b9c5b74256",
      "name": "Is Request Valid?",
      "type": "n8n-nodes-base.if",
      "position": [
        832,
        304
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.invalid }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "f176b3f4-fb4e-4333-9c0f-d7f3e57385c8",
      "name": "Send Invalid Input Alert",
      "type": "n8n-nodes-base.sendGrid",
      "position": [
        1024,
        208
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "8f89825d-aa55-4139-99e9-1063c55e0458",
      "name": "Determine Approver",
      "type": "n8n-nodes-base.code",
      "position": [
        1024,
        416
      ],
      "parameters": {
        "jsCode": "// Simple router: choose approver by department\nconst dept = $json.department;\nlet approverEmail = 'user@example.com';\nif (dept === 'Finance') approverEmail = 'user@example.com';\nif (dept === 'HR') approverEmail = 'user@example.com';\nreturn [{ json: { ...$json, approverEmail } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "237ecb70-492f-4135-b648-6fec9f5de2cd",
      "name": "Notify Approver",
      "type": "n8n-nodes-base.sendGrid",
      "position": [
        1232,
        416
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "41d8e6e9-2139-4234-9492-90c5f7003328",
      "name": "Create Pending Record",
      "type": "n8n-nodes-base.baserow",
      "position": [
        1424,
        416
      ],
      "parameters": {
        "tableId": "{{YOUR_TABLE_ID}}",
        "operation": "create",
        "databaseId": "{{YOUR_DATABASE_ID}}"
      },
      "typeVersion": 1
    },
    {
      "id": "03ebac20-7f65-42d5-a8c8-451d1f93d312",
      "name": "Wait for Approval Response",
      "type": "n8n-nodes-base.wait",
      "position": [
        1632,
        416
      ],
      "parameters": {
        "resume": true
      },
      "typeVersion": 1
    },
    {
      "id": "e42434ae-916a-47b1-b425-22401469640b",
      "name": "Extract Approval Result",
      "type": "n8n-nodes-base.set",
      "position": [
        1824,
        416
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3.4
    },
    {
      "id": "37303efd-54c0-40e7-b976-9e423b1b763c",
      "name": "Approved?",
      "type": "n8n-nodes-base.if",
      "position": [
        2032,
        416
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.approved }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "9ce32909-83b8-4918-8e2e-746e06e73ba2",
      "name": "Update Record \u2013 Approved",
      "type": "n8n-nodes-base.baserow",
      "position": [
        2224,
        304
      ],
      "parameters": {
        "rowId": "={{ $json.requestId }}",
        "tableId": "{{YOUR_TABLE_ID}}",
        "operation": "update",
        "databaseId": "{{YOUR_DATABASE_ID}}"
      },
      "typeVersion": 1
    },
    {
      "id": "b2302eaf-d92e-4b11-a8c7-03c6b3bc4c27",
      "name": "Notify Requester \u2013 Approved",
      "type": "n8n-nodes-base.sendGrid",
      "position": [
        2432,
        304
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "c63597b1-3486-414e-a4d2-adfedad9c11c",
      "name": "Update Record \u2013 Rejected",
      "type": "n8n-nodes-base.baserow",
      "position": [
        2224,
        512
      ],
      "parameters": {
        "rowId": "={{ $json.requestId }}",
        "tableId": "{{YOUR_TABLE_ID}}",
        "operation": "update",
        "databaseId": "{{YOUR_DATABASE_ID}}"
      },
      "typeVersion": 1
    },
    {
      "id": "5f884da2-c06c-48fe-974c-7772e3dac49a",
      "name": "Notify Requester \u2013 Rejected",
      "type": "n8n-nodes-base.sendGrid",
      "position": [
        2432,
        512
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "c71ad01e-a594-46bc-9593-d57b852d30fa",
      "name": "Join End Paths",
      "type": "n8n-nodes-base.merge",
      "position": [
        2624,
        416
      ],
      "parameters": {
        "mode": "mergeInput1And2",
        "options": {}
      },
      "typeVersion": 2.1
    },
    {
      "id": "37feeb27-667c-4686-aec6-0de4063da06c",
      "name": "Finalize",
      "type": "n8n-nodes-base.code",
      "position": [
        2832,
        416
      ],
      "parameters": {
        "jsCode": "// Final housekeeping \u2013 could push audit logs or metrics\nreturn items;"
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "4bc9f3a1-5fcc-4d8c-86dc-2838b27dedd9",
  "connections": {
    "Approved?": {
      "main": [
        [
          {
            "node": "Update Record \u2013 Approved",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Update Record \u2013 Rejected",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Join End Paths": {
      "main": [
        [
          {
            "node": "Finalize",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start Workflow": {
      "main": [
        [
          {
            "node": "Validate Request Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notify Approver": {
      "main": [
        [
          {
            "node": "Create Pending Record",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Request Valid?": {
      "main": [
        [
          {
            "node": "Determine Approver",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Invalid Input Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Determine Approver": {
      "main": [
        [
          {
            "node": "Notify Approver",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Pending Record": {
      "main": [
        [
          {
            "node": "Wait for Approval Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Request Data": {
      "main": [
        [
          {
            "node": "Is Request Valid?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Approval Result": {
      "main": [
        [
          {
            "node": "Approved?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Invalid Input Alert": {
      "main": [
        [
          {
            "node": "Join End Paths",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Update Record \u2013 Approved": {
      "main": [
        [
          {
            "node": "Notify Requester \u2013 Approved",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Record \u2013 Rejected": {
      "main": [
        [
          {
            "node": "Notify Requester \u2013 Rejected",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for Approval Response": {
      "main": [
        [
          {
            "node": "Extract Approval Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notify Requester \u2013 Approved": {
      "main": [
        [
          {
            "node": "Join End Paths",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notify Requester \u2013 Rejected": {
      "main": [
        [
          {
            "node": "Join End Paths",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}