{
  "id": "jMCcPwioZ8wHrUY7",
  "meta": {
    "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": "## \ud83d\udd04 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": "## \u23f0 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": "## \ud83e\udd16 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": "## \ud83d\udcc5 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": "## \ud83d\udd10 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": {
          "name": "<your 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": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "485f5691-692c-4977-aaa6-bb2741a4a6ad",
      "name": "Send Slack Notification",
      "type": "n8n-nodes-base.slack",
      "position": [
        512,
        -80
      ],
      "parameters": {
        "text": "=\ud83d\udce6 *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\ud83d\udc49 *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": {
          "name": "<your 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": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "49211ded-eed6-4af1-a011-e65cd956694c",
      "name": "Send Email Confirmation",
      "type": "n8n-nodes-base.gmail",
      "position": [
        656,
        48
      ],
      "parameters": {
        "sendTo": "user@example.com",
        "message": "=\ud83d\udce6 *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\ud83d\udc49 *Schedule Here:* {{ $('Create Calendar Event').item.json.htmlLink }}",
        "options": {},
        "subject": "Supplier Follow-up Required"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "48acce26-e1d9-4969-9e8b-82e6bb782212",
      "name": "Initial Slack Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        -640,
        -128
      ],
      "parameters": {
        "text": "=\ud83d\udce6 *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\ud83d\udc49 *Schedule Here:* {{ $('Create Calendar Event').item.json.htmlLink }}\n",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09GNB90TED",
          "cachedResultName": "general-information"
        },
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "name": "<your 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": {
          "name": "<your 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\u20133 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
      ],
      "parameters": {
        "text": "\u26a0\ufe0f 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": {
          "name": "<your 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
      ],
      "parameters": {
        "text": "=\u274c *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": {
          "name": "<your 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": "## \ud83d\udea8 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,
  "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
          }
        ]
      ]
    }
  }
}