{
  "id": "jMCcPwioZ8wHrUY7",
  "meta": {
    "instanceId": "8443f10082278c46aa5cf3acf8ff0f70061a2c58bce76efac814b16290845177",
    "templateCredsSetupCompleted": true
  },
  "name": "Schedule supplier follow-ups from Airtable purchase orders to Google Calendar, Slack, and Gmail",
  "tags": [],
  "nodes": [
    {
      "id": "713ca09a-2d27-4b59-9660-f13991ac8c90",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1456,
        -688
      ],
      "parameters": {
        "width": 466,
        "height": 528,
        "content": "## 🔄 Automated Supplier Follow-up System\n\n### How it works\nThis workflow monitors overdue purchase orders in Airtable and automatically schedules supplier follow-ups. It uses AI to generate smart meeting agendas, creates calendar events, and sends notifications via Slack and Gmail. The system runs daily on weekdays at 10 AM, processing only open POs older than 30 days without existing follow-ups.\n\n### Setup steps\n1. Connect Airtable credentials and configure your base/table IDs\n2. Add Google Calendar, Slack, and Gmail OAuth credentials\n3. Set up OpenAI API key for AI agenda generation\n4. Update Slack channel ID and Gmail recipient address\n5. Test with a single overdue PO record\n6. Activate the workflow to run on schedule\n\n**Required fields in Airtable:** PO ID, Supplier Name, Supplier Email, Status, PO Date, Follow-up Link, Follow-up Status, Notes"
      },
      "typeVersion": 1
    },
    {
      "id": "843bf4c0-3efa-441c-bc6b-87a619b123a6",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1424,
        -80
      ],
      "parameters": {
        "color": 2,
        "width": 515,
        "height": 335,
        "content": "## ⏰ Trigger & Data Fetch\nRuns every weekday at 10 AM to pull overdue purchase orders from Airtable that need supplier follow-up."
      },
      "typeVersion": 1
    },
    {
      "id": "73f26d56-313e-428f-b153-8049bf510b1a",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -704,
        -352
      ],
      "parameters": {
        "color": 2,
        "width": 709,
        "height": 637,
        "content": "## 🤖 AI Processing & Validation\nFilters POs with valid supplier emails, then uses GPT-4 to generate contextual meeting agendas and action steps for each follow-up."
      },
      "typeVersion": 1
    },
    {
      "id": "8a0996ee-8348-4a5e-840e-7150470fe5dc",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        32,
        -208
      ],
      "parameters": {
        "color": 2,
        "width": 789,
        "height": 407,
        "content": "## 📅 Calendar & Notifications\nCreates Google Calendar events with AI-generated agendas, saves links back to Airtable, and notifies team via Slack and Gmail."
      },
      "typeVersion": 1
    },
    {
      "id": "b29f16a1-3867-42c1-958e-57ca848bdb78",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        848,
        64
      ],
      "parameters": {
        "color": 3,
        "width": 348.9736842105262,
        "height": 166.70394736842104,
        "content": "## 🔐 Credentials & Security\nUse OAuth2 for Google Calendar, Slack, and Gmail. Airtable requires Personal Access Token. OpenAI uses API key authentication. Replace `example@gmail.com` with your actual recipient address before deploying."
      },
      "typeVersion": 1
    },
    {
      "id": "176b525f-5e66-4a49-b0e6-47d9ab321c21",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -1312,
        64
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 10 * * 1-5"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "ab86a141-9013-4edd-9bce-44a12bd2542a",
      "name": "Fetch Overdue POs from Airtable",
      "type": "n8n-nodes-base.airtable",
      "position": [
        -1088,
        64
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "appadCQdMQkDFd6Rh",
          "cachedResultUrl": "https://airtable.com/appadCQdMQkDFd6Rh",
          "cachedResultName": "N8n Content "
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tbljnudRYQrW7ASRM",
          "cachedResultUrl": "https://airtable.com/appadCQdMQkDFd6Rh/tbljnudRYQrW7ASRM",
          "cachedResultName": "Purchase Orders"
        },
        "options": {},
        "operation": "search",
        "filterByFormula": "AND(   {Status} = \"Open\",   IS_BEFORE({PO Date}, DATEADD(TODAY(), -30, 'days')),   OR(     {Follow-up Link} = \"\",     {Follow-up Link} = BLANK()   ) )"
      },
      "credentials": {
        "airtableTokenApi": {
          "id": "credential-id",
          "name": "airtableTokenApi Credential"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "7821ff3f-9ac7-4c95-af97-f617598c488b",
      "name": "Process Each PO Individually",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -864,
        64
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "8e936398-7512-45aa-a6be-0f700a070d6f",
      "name": "Save Calendar Link to Airtable",
      "type": "n8n-nodes-base.airtable",
      "position": [
        288,
        -80
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "appadCQdMQkDFd6Rh",
          "cachedResultUrl": "https://airtable.com/appadCQdMQkDFd6Rh",
          "cachedResultName": "N8n Content "
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tbljnudRYQrW7ASRM",
          "cachedResultUrl": "https://airtable.com/appadCQdMQkDFd6Rh/tbljnudRYQrW7ASRM",
          "cachedResultName": "Purchase Orders"
        },
        "columns": {
          "value": {
            "id": "={{ $('Process Each PO Individually').item.json.id }}",
            "Follow-up Link": "={{ $json.htmlLink }}",
            "Follow-up Status": "Pending"
          },
          "schema": [
            {
              "id": "id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": true,
              "required": false,
              "displayName": "id",
              "defaultMatch": true
            },
            {
              "id": "PO ID",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "PO ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Supplier Name",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Supplier Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Supplier Email",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Supplier Email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "options",
              "display": true,
              "options": [
                {
                  "name": "Open",
                  "value": "Open"
                },
                {
                  "name": "Closed",
                  "value": "Closed"
                }
              ],
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "PO Date",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "PO Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Follow-up Link",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Follow-up Link",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Follow-up Status",
              "type": "options",
              "display": true,
              "options": [
                {
                  "name": "Pending",
                  "value": "Pending"
                },
                {
                  "name": "Scheduled",
                  "value": "Scheduled"
                },
                {
                  "name": "Not Required",
                  "value": "Not Required"
                }
              ],
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Follow-up Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Notes",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Notes",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update"
      },
      "credentials": {
        "airtableTokenApi": {
          "id": "credential-id",
          "name": "airtableTokenApi Credential"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "485f5691-692c-4977-aaa6-bb2741a4a6ad",
      "name": "Send Slack Notification",
      "type": "n8n-nodes-base.slack",
      "position": [
        512,
        -80
      ],
      "webhookId": "102990ae-d72c-4fa6-82fe-c9187f1a5d43",
      "parameters": {
        "text": "=📦 *Supplier Follow-up Triggered*\n\n*PO ID:* {{ $json.fields[\"PO ID\"] }}\n*Supplier:* {{ $json.fields[\"Supplier Name\"] }}\n*Email:* {{ $json.fields[\"Supplier Email\"] }}\n*PO Date:* {{ $json.fields[\"PO Date\"] }}\n👉 *Schedule Here:* {{ $('Create Calendar Event').item.json.htmlLink }} \n\n\nAgenda :{{ $('AI Agent: Agent Writer').item.json.output.agendaSummary }} \n \nNext steps to do:{{ $('AI Agent: Agent Writer').item.json.output.nextSteps }} \n\n",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09GNB90TED",
          "cachedResultName": "general-information"
        },
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "id": "credential-id",
          "name": "slackApi Credential"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "f31c5656-35ec-4e04-8584-9abe0f2b3fd2",
      "name": "Create Calendar Event",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        64,
        -80
      ],
      "parameters": {
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "user@example.com",
          "cachedResultName": "user@example.com"
        },
        "additionalFields": {}
      },
      "credentials": {
        "googleCalendarOAuth2Api": {
          "id": "credential-id",
          "name": "googleCalendarOAuth2Api Credential"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "49211ded-eed6-4af1-a011-e65cd956694c",
      "name": "Send Email Confirmation",
      "type": "n8n-nodes-base.gmail",
      "position": [
        656,
        48
      ],
      "webhookId": "8441b6d7-7cd0-4d6b-94ba-b34c5ca26be7",
      "parameters": {
        "sendTo": "user@example.com",
        "message": "=📦 *Supplier Follow-up Triggered*\n\n*PO ID:* {{ $('Save Calendar Link to Airtable').item.json.fields['PO ID'] }}\n*Supplier:* {{ $('Save Calendar Link to Airtable').item.json.fields['Supplier Name'] }}\n*Email:* {{ $('Save Calendar Link to Airtable').item.json.fields['Supplier Email'] }}\n*PO Date:* {{ $('Save Calendar Link to Airtable').item.json.fields['PO Date'] }} \n\nAgenda :{{ $('AI Agent: Agent Writer').item.json.output.agendaSummary }} \n \nNext steps to do:{{ $('AI Agent: Agent Writer').item.json.output.nextSteps }} \n\n\n\n👉 *Schedule Here:* {{ $('Create Calendar Event').item.json.htmlLink }}",
        "options": {},
        "subject": "Supplier Follow-up Required"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "credential-id",
          "name": "gmailOAuth2 Credential"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "48acce26-e1d9-4969-9e8b-82e6bb782212",
      "name": "Initial Slack Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        -640,
        -128
      ],
      "webhookId": "e01e7e3b-b8e4-4b11-934f-009f47d310d1",
      "parameters": {
        "text": "=📦 *Supplier Follow-up Triggered*  \n*PO ID:* {{ $json.fields[\"PO ID\"] }}\n*Supplier:* {{ $json.fields[\"Supplier Name\"] }} \n*Email:* {{ $json.fields[\"Supplier Email\"] }}\n*PO Date:* {{ $json.fields[\"PO Date\"] }} \n👉 *Schedule Here:* {{ $('Create Calendar Event').item.json.htmlLink }}\n",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09GNB90TED",
          "cachedResultName": "general-information"
        },
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "id": "credential-id",
          "name": "slackApi Credential"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "0b890412-062b-4154-acf7-0f7e901ce977",
      "name": "If",
      "type": "n8n-nodes-base.if",
      "position": [
        -640,
        64
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "01788ec3-bcfa-43ea-b8b1-c03dcabdebf2",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json[\"Supplier Email\"] }}",
              "rightValue": "="
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "53ee6663-7282-4931-8689-77f58c3bc3c4",
      "name": "Parse AI Output (Structured JSON)",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        -160,
        48
      ],
      "parameters": {
        "jsonSchemaExample": "{\n  \"agendaSummary\": \"Brief summary of the situation\",\n  \"nextSteps\": [\n    \"Step 1 clearly defined\",\n    \"Step 2 clearly defined\",\n    \"Step 3 clearly defined\"\n  ],\n  \"status\": \"Pending | Scheduled | Missing Info\",\n  \"poId\": \"same as incoming PO ID\",\n  \"followUpReason\": \"Short justification why follow-up is required\"\n}\n"
      },
      "typeVersion": 1.3
    },
    {
      "id": "b13dd8d7-21a2-49e3-9b3e-9f5b1209d309",
      "name": "AI Model: GPT-4 Screening Engine",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -416,
        48
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "gpt-4o-mini"
        },
        "options": {
          "temperature": 0.7
        }
      },
      "credentials": {
        "openAiApi": {
          "id": "credential-id",
          "name": "openAiApi Credential"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "5fc101d7-042d-4c29-a196-34cb25230fa6",
      "name": "AI Memory: Candidate Context",
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "position": [
        -288,
        48
      ],
      "parameters": {
        "sessionKey": "=\"AI Agenda\"",
        "sessionIdType": "customKey"
      },
      "typeVersion": 1.3
    },
    {
      "id": "48c7adba-f3d0-4188-868b-a846a6f74be2",
      "name": "AI Agent: Agent Writer",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -352,
        -192
      ],
      "parameters": {
        "text": "=PO Details:\n- PO ID: {{ $json[\"PO ID\"] }}\n- Supplier: {{ $json[\"Supplier Name\"] }}\n- Email: {{ $json[\"Supplier Email\"] }}\n- Status: {{ $json[\"Status\"] }}\n- PO Date: {{ $json[\"PO Date\"] }}\n- Current Follow-up Status: {{ $json[\"Follow-up Status\"] }}\n- Notes: {{ $json[\"Notes\"] }}\n",
        "options": {
          "systemMessage": "=You are an AI assistant that writes short actionable supplier follow-up agendas for procurement teams.\n\nRULES:\n- Only respond in valid JSON as per the schema.\n- Do NOT include any extra text outside JSON.\n- Keep agenda professional, concise, and supplier-friendly.\n- Summarize the current situation based on the PO details.\n- Suggest 2–3 clear next steps for the purchasing team.\n- If supplier email is missing, set status to \"Missing Info\" and leave agenda blank.\n- Always ensure dates are ISO formatted (YYYY-MM-DD).\n"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 2.1
    },
    {
      "id": "3fafd43b-dfef-4fbc-8246-a35a325e2b9d",
      "name": "Send a message",
      "type": "n8n-nodes-base.slack",
      "position": [
        -288,
        224
      ],
      "webhookId": "53cce406-bac2-4a8a-97d0-c0110c464dfb",
      "parameters": {
        "text": "⚠️ Supplier follow-up blocked: Missing email address. Please update contact details in Airtable.",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09GNB90TED",
          "cachedResultName": "general-information"
        },
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "id": "credential-id",
          "name": "slackApi Credential"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "c3902a3a-2d3c-4582-8cd1-1425af11347d",
      "name": "Error Trigger",
      "type": "n8n-nodes-base.errorTrigger",
      "position": [
        -1408,
        608
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "f3850dbc-ea8f-473e-810c-18282922a5a3",
      "name": "Slack - Error Alert",
      "type": "n8n-nodes-base.slack",
      "notes": "Sends an error alert message if Topic Classifier, FAQ Generator, or Notion fails.",
      "position": [
        -1136,
        608
      ],
      "webhookId": "32778282-35eb-4e4c-97cf-0217a8fb754a",
      "parameters": {
        "text": "=❌ *FAQ Automation Error Detected!*\n\n*Node:* {{ $json.node.name }}\n*Error:* {{ $json.error.message }}\n*Time:* {{ $json.timestamp }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": ""
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "id": "credential-id",
          "name": "slackApi Credential"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "61d0c47c-df77-443c-bbf0-c53e066c8f78",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1440,
        400
      ],
      "parameters": {
        "color": 2,
        "width": 496,
        "height": 352,
        "content": "## 🚨 Error Monitoring  \n\n\nCatches errors from key nodes (AI, Notion, Sheets) and sends a Slack alert with details for quick debugging.  \nHelps ensure the automation runs reliably.\n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "f512e4fd-e9c4-4459-927d-09d08a766972",
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "AI Agent: Agent Writer",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Error Trigger": {
      "main": [
        [
          {
            "node": "Slack - Error Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Fetch Overdue POs from Airtable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Calendar Event": {
      "main": [
        [
          {
            "node": "Save Calendar Link to Airtable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent: Agent Writer": {
      "main": [
        [
          {
            "node": "Create Calendar Event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Email Confirmation": {
      "main": [
        [
          {
            "node": "Process Each PO Individually",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Slack Notification": {
      "main": [
        [
          {
            "node": "Send Email Confirmation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Memory: Candidate Context": {
      "ai_memory": [
        [
          {
            "node": "AI Agent: Agent Writer",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Process Each PO Individually": {
      "main": [
        [
          {
            "node": "Initial Slack Alert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Calendar Link to Airtable": {
      "main": [
        [
          {
            "node": "Send Slack Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Overdue POs from Airtable": {
      "main": [
        [
          {
            "node": "Process Each PO Individually",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Model: GPT-4 Screening Engine": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent: Agent Writer",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI Output (Structured JSON)": {
      "ai_outputParser": [
        [
          {
            "node": "AI Agent: Agent Writer",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    }
  }
}