{
  "id": "j9kvA2yDDC5D5Y3y",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Meeting Insight Extraction",
  "tags": [],
  "nodes": [
    {
      "id": "766e9d5f-ec86-492c-b3a3-d64e23e2b691",
      "name": "Outlook Trigger",
      "type": "n8n-nodes-base.microsoftOutlookTrigger",
      "position": [
        -64,
        1584
      ],
      "parameters": {
        "filters": {
          "sender": "user@example.com"
        },
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyX",
              "unit": "minutes",
              "value": 15
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1523ba0e-4434-4b10-a0d8-45ad2f50b3ce",
      "name": "Deduplicate Threads",
      "type": "n8n-nodes-base.code",
      "position": [
        160,
        1584
      ],
      "parameters": {
        "jsCode": "// Use n8n static data to remember previously processed Conversation IDs\nconst staticData = $getWorkflowStaticData('global');\nstaticData.processedIds = staticData.processedIds || [];\n\nconst newItems = [];\nfor (const item of $input.all()) {\n    const msgId = item.json.conversationId || item.json.id;\n    if (!staticData.processedIds.includes(msgId)) {\n        newItems.push(item);\n        staticData.processedIds.push(msgId);\n    }\n}\n\n// .slice(-100) is safer than splice() against n8n's static data proxy object\nstaticData.processedIds = staticData.processedIds.slice(-100);\n\nreturn newItems;"
      },
      "typeVersion": 2
    },
    {
      "id": "b6173f68-dd9c-4697-8e3b-cb379f263867",
      "name": "Strip HTML Bloat",
      "type": "n8n-nodes-base.html",
      "position": [
        400,
        1584
      ],
      "parameters": {
        "options": {},
        "operation": "extractHtmlContent",
        "extractionValues": {
          "values": [
            {
              "key": "plainTextTranscript",
              "cssSelector": "body"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "d3de77eb-f17b-44c6-b562-201e0dd21757",
      "name": "Extract Insights via AI",
      "type": "n8n-nodes-base.openAi",
      "position": [
        608,
        1584
      ],
      "parameters": {
        "model": "gpt-4o",
        "options": {},
        "requestOptions": {}
      },
      "typeVersion": 1
    },
    {
      "id": "8cdcf427-4108-4f30-aca4-35bba325f594",
      "name": "Parse & Route Arrays",
      "type": "n8n-nodes-base.code",
      "position": [
        832,
        1584
      ],
      "parameters": {
        "jsCode": "// Strip markdown code fences before parsing \u2014 OpenAI wraps JSON in ```json blocks even when asked not to\nlet aiResponse = {};\ntry {\n    let rawContent = $input.first().json.message.content;\n    let cleanContent = rawContent.replace(/```json/g, '').replace(/```/g, '').trim();\n    aiResponse = JSON.parse(cleanContent);\n} catch (e) {\n    aiResponse = { Strategic_Decisions: [], Action_Items: [] };\n}\n\nconst decisions = aiResponse.Strategic_Decisions || [];\nconst actions = aiResponse.Action_Items || [];\n\nconst decisionItems = decisions.map(d => ({ json: d }));\nconst actionItems = actions.map(a => ({ json: a }));\n\n// Output 0 -> SharePoint (Decisions) | Output 1 -> Normalize Due Date -> To Do (Actions)\n// IMPORTANT: Set Outputs to 2 in this node's Settings tab on the canvas\nreturn [decisionItems, actionItems];"
      },
      "typeVersion": 2
    },
    {
      "id": "714b9156-7619-4707-be96-84a291fa89ae",
      "name": "Log to SharePoint List",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "position": [
        1200,
        1392
      ],
      "parameters": {
        "resource": "listItem",
        "requestOptions": {}
      },
      "typeVersion": 1
    },
    {
      "id": "bf09413a-885e-4819-977b-49aea6081532",
      "name": "Normalize Due Date",
      "type": "n8n-nodes-base.code",
      "position": [
        1104,
        1712
      ],
      "parameters": {
        "jsCode": "// The LLM is forced to output YYYY-MM-DD or null \u2014 no freeform strings.\n// Converts that to the dateTimeTimeZone object Microsoft Graph requires.\n// Microsoft To Do rejects plain ISO strings; it needs { dateTime, timeZone }.\n//\n// Deadline = 14:00 UTC = 17:00 EEST (UTC+3, Finland summer time).\n// Adjust the hour constant below if your team operates in a different timezone.\n\nconst DEADLINE_HOUR_UTC = 14;\nconst items = $input.all();\n\nreturn items.map(item => {\n    const raw = item.json.due_date;\n    let dueDateTimeZone = null;\n\n    if (raw && raw !== 'null' && raw.trim() !== '') {\n        // Only accept strings the LLM formatted correctly as YYYY-MM-DD\n        const isValidFormat = /^\\d{4}-\\d{2}-\\d{2}$/.test(raw.trim());\n        if (isValidFormat) {\n            // Build the dateTimeTimeZone object Microsoft Graph / To Do requires\n            dueDateTimeZone = {\n                dateTime: `${raw.trim()}T${String(DEADLINE_HOUR_UTC).padStart(2, '0')}:00:00`,\n                timeZone: 'UTC'\n            };\n        }\n    }\n\n    return {\n        json: {\n            ...item.json,\n            due_date_graph: dueDateTimeZone\n        }\n    };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "454de741-16cd-4d2a-9321-1c50590fa7c8",
      "name": "Create To Do Tasks",
      "type": "n8n-nodes-base.microsoftToDo",
      "position": [
        1328,
        1712
      ],
      "parameters": {
        "title": "={{ $json.task }}",
        "operation": "create",
        "taskListId": "YOUR_TODO_LIST_ID",
        "additionalFields": {
          "content": "={{ 'Assignee: ' + $json.assignee }}",
          "dueDateTime": "={{ $json.due_date_graph }}"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "875cad86-1cda-4659-b2fd-91c90c5ca0f6",
      "name": "Error Trigger",
      "type": "n8n-nodes-base.errorTrigger",
      "position": [
        -48,
        1888
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "c5e0ebe7-9412-4f5f-8523-ca4d3d701a98",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -704,
        1264
      ],
      "parameters": {
        "width": 560,
        "height": 784,
        "content": "## Meeting Intelligence: Auto-Extract Decisions and Action Items to SharePoint and Microsoft To Do\n\n### AI-Powered Meeting Transcript Processing via Outlook\n\n### How it works\nThis workflow polls Outlook every 15 minutes for meeting summary emails sent by a configured bot address, extracts strategic decisions and action items using GPT-4o, logs decisions to a SharePoint list, and creates dated tasks in Microsoft To Do all without manual intervention.\n\n### Setup steps\n- [ ] Replace `bot@yourcompany.com` in the Outlook Trigger filter with the\n      actual sender address your meeting bot uses.\n- [ ] Connect your Microsoft Outlook OAuth2 credential to both the Outlook\n      Trigger node and the Send Error Email node. Replace\n      admin@yourcompany.com in the error node with your actual admin address.\n- [ ] Connect your OpenAI credential to the Extract Insights via AI node.\n- [ ] Connect your Microsoft SharePoint credential to the Log to SharePoint\n      List node and configure the target site and list.\n- [ ] Connect your Microsoft To Do credential to the Create To Do Tasks node\n      and replace YOUR_TODO_LIST_ID with your actual task list ID.\n- [ ] In the Parse & Route Arrays node settings, set the number of outputs\n      to 2 so the split routing works correctly.\n\n### Required credentials\n- **Microsoft Outlook OAuth2** (trigger + error email)\n- **OpenAI API** (insight extraction via GPT-4o)\n- **Microsoft SharePoint** (decision logging)\n- **Microsoft To Do** (action item task creation)\n\n### Notes\nThe deadline hour for To Do tasks defaults to 14:00 UTC (17:00 EEST).\nAdjust the DEADLINE_HOUR_UTC constant in the Normalize Due Date node to\nmatch your team's timezone."
      },
      "typeVersion": 1
    },
    {
      "id": "37a0b705-55dc-42e1-8ab6-05f29bf14a66",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -112,
        1424
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 320,
        "content": "### Trigger & Deduplication\nPolls Outlook every 15 minutes for emails from the configured bot sender and drops any conversation threads already processed in a previous run."
      },
      "typeVersion": 1
    },
    {
      "id": "8f79f606-ee6c-44aa-9e3c-a164e02b348c",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1008,
        1584
      ],
      "parameters": {
        "color": 7,
        "width": 624,
        "height": 288,
        "content": "### Action Item Task Creation\nNormalizes due dates to the Microsoft Graph dateTimeTimeZone format and creates a dated To Do task for each action item with assignee and deadline."
      },
      "typeVersion": 1
    },
    {
      "id": "dc8d8709-4c45-4c2f-b028-0232d746c7ba",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        336,
        1424
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 320,
        "content": "### AI Extraction & Routing\nStrips HTML from the email body, passes the clean transcript to GPT-4o to extract strategic decisions and action items, then splits the two arrays into separate output branches for independent downstream processing."
      },
      "typeVersion": 1
    },
    {
      "id": "252ba800-75f4-4b8f-8904-ac158a8e2161",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1008,
        1264
      ],
      "parameters": {
        "color": 7,
        "width": 624,
        "height": 288,
        "content": "### Decision Logging\nLogs each extracted strategic decision as a new item in the configured SharePoint list for permanent team-level record keeping."
      },
      "typeVersion": 1
    },
    {
      "id": "c4d72a6d-efa6-4f57-95e9-123e43e7e14d",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -112,
        1776
      ],
      "parameters": {
        "color": 7,
        "width": 528,
        "height": 272,
        "content": "### Error Handling\nGlobal error catcher that intercepts any node failure and sends a formatted alert email to the configured admin address with the workflow error details."
      },
      "typeVersion": 1
    },
    {
      "id": "46257cf8-c5d2-4819-8abb-ab07aa7c2de0",
      "name": "Send Error Email",
      "type": "n8n-nodes-base.microsoftOutlook",
      "position": [
        176,
        1888
      ],
      "parameters": {
        "subject": "\ud83d\udea8 n8n Workflow Error: Meeting Extraction",
        "toRecipients": [
          "user@example.com"
        ],
        "additionalFields": {}
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "58cba6d3-50a0-446c-baa0-f0400c38a846",
  "connections": {
    "Error Trigger": {
      "main": [
        [
          {
            "node": "Send Error Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Outlook Trigger": {
      "main": [
        [
          {
            "node": "Deduplicate Threads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Strip HTML Bloat": {
      "main": [
        [
          {
            "node": "Extract Insights via AI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Due Date": {
      "main": [
        [
          {
            "node": "Create To Do Tasks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Deduplicate Threads": {
      "main": [
        [
          {
            "node": "Strip HTML Bloat",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse & Route Arrays": {
      "main": [
        [
          {
            "node": "Log to SharePoint List",
            "type": "main",
            "index": 0
          },
          {
            "node": "Normalize Due Date",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Insights via AI": {
      "main": [
        [
          {
            "node": "Parse & Route Arrays",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}