This workflow corresponds to n8n.io template #16107 — we link there as the canonical source.
This workflow follows the HTTP Request → Microsoft Outlook 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 →
{
"id": "rdiQAFQuqDnIQ476",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Automated Pre-Meeting Context Briefings from SharePoint and Entra ID",
"tags": [],
"nodes": [
{
"id": "2bee2c8b-3fc9-4bdb-a30b-9091c65a13cb",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
14096,
7808
],
"parameters": {
"width": 480,
"height": 688,
"content": "## Automated Pre-Meeting Context Briefings from SharePoint and Entra ID\n\n### How it works\n\nThis workflow runs every 30 minutes to check configured VIP mailboxes for upcoming calendar events that still need pre-meeting briefings. For each eligible event, it enriches the meeting context with attendee profile data from Entra ID via Microsoft Graph and related SharePoint documents from Microsoft Search. It then compiles a briefing, sends it through Microsoft Teams, records successful delivery, alerts on delivery errors, and continues through all events and mailboxes.\n\n### Setup steps\n\n- Configure Microsoft credentials for Outlook, Microsoft Graph/Entra ID, SharePoint search, and Microsoft Teams in n8n.\n- Ensure the Graph app or OAuth connection has the required permissions, such as calendar read access, user/profile read access, SharePoint or Microsoft Search access, and Teams message-send permissions.\n- Update the Config node with the VIP mailbox list, briefing recipient user ID, SharePoint site ID, and the minutes-before-meeting window.\n- Verify the code nodes match your tenant\u2019s event fields, attendee fields, SharePoint search schema, and the mechanism used to mark events as already briefed.\n- Set the Teams destination for normal briefings and the error alert path before activating the schedule trigger.\n\n### Customization\n\nAdjust the meeting filter logic, SharePoint search query, attendee profile fields, briefing text format, recipient routing, and error-alert behavior to match your organization\u2019s briefing requirements."
},
"typeVersion": 1
},
{
"id": "3f972221-45e6-46bd-8799-0614458e42d2",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
14656,
7984
],
"parameters": {
"color": 7,
"width": 432,
"height": 416,
"content": "## Schedule and configuration\n\nStarts the workflow on a 30-minute schedule and loads the core settings used by the rest of the run."
},
"typeVersion": 1
},
{
"id": "6240d37d-2374-47e7-b6aa-6535e1b3b931",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
15152,
7984
],
"parameters": {
"color": 7,
"width": 480,
"height": 416,
"content": "## VIP mailbox loop\n\nIterates through the configured VIP mailboxes and provides the completion endpoint once every mailbox has been processed."
},
"typeVersion": 1
},
{
"id": "f9ede92c-fd48-4b2b-a087-a69aafde1a9b",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
15712,
7984
],
"parameters": {
"color": 7,
"width": 464,
"height": 416,
"content": "## Calendar event lookup\n\nExtracts the active mailbox from the loop item and retrieves its upcoming Outlook calendar events."
},
"typeVersion": 1
},
{
"id": "f61db57b-4d0e-4b45-bed9-8ea249070236",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
16256,
7984
],
"parameters": {
"color": 7,
"width": 512,
"height": 416,
"content": "## Briefing candidate filter\n\nFilters retrieved calendar events down to those that need a briefing and branches depending on whether any candidates remain."
},
"typeVersion": 1
},
{
"id": "7364bdb0-e129-41ab-a12f-e9c4d78045ac",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
16848,
7824
],
"parameters": {
"color": 7,
"width": 576,
"height": 752,
"content": "## Event loop control\n\nHandles event-level iteration, extracts each event for processing, and routes mailboxes with no eligible events back to the mailbox loop."
},
"typeVersion": 1
},
{
"id": "4ea69ad1-ccd4-4a29-857c-69f53a1abcbb",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
17488,
7808
],
"parameters": {
"color": 7,
"width": 480,
"height": 496,
"content": "## Attendee batch setup\n\nBuilds the Microsoft Graph batch payload for attendee profile lookups and checks whether the event has attendees to enrich."
},
"typeVersion": 1
},
{
"id": "675e671f-0dd7-412d-9270-d80a5f627323",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
18032,
7808
],
"parameters": {
"color": 7,
"width": 464,
"height": 496,
"content": "## Attendee profile enrichment\n\nFetches attendee profiles in a Graph batch when possible, skips the call when there are no attendees, and merges the result back into the event item."
},
"typeVersion": 1
},
{
"id": "29f47e5f-c570-4343-833c-0ecd1b387861",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
18560,
7808
],
"parameters": {
"color": 7,
"width": 640,
"height": 496,
"content": "## SharePoint document search\n\nBuilds a SharePoint/Microsoft Search query, searches for meeting-related documents, and attaches the search response to the running event context."
},
"typeVersion": 1
},
{
"id": "7bbc446e-ae12-4068-a468-1403534e962c",
"name": "Sticky Note9",
"type": "n8n-nodes-base.stickyNote",
"position": [
19264,
7808
],
"parameters": {
"color": 7,
"width": 640,
"height": 496,
"content": "## Compile and send briefing\n\nCreates the final briefing message, sends it through Microsoft Teams, and checks whether the send operation succeeded."
},
"typeVersion": 1
},
{
"id": "01f7eeed-3cc7-46e3-ad44-94dda844ffc9",
"name": "Sticky Note10",
"type": "n8n-nodes-base.stickyNote",
"position": [
19968,
7808
],
"parameters": {
"color": 7,
"width": 576,
"height": 496,
"content": "## Delivery outcome handling\n\nMarks successfully sent events as briefed, sends an error alert when delivery fails, and merges both outcomes before continuing."
},
"typeVersion": 1
},
{
"id": "18aabbf5-c205-4bac-882b-a17a8774ae8e",
"name": "Sticky Note11",
"type": "n8n-nodes-base.stickyNote",
"position": [
20608,
7808
],
"parameters": {
"color": 7,
"width": 400,
"height": 496,
"content": "## Advance to next event\n\nPasses control back to the event loop so the workflow can process the next eligible meeting."
},
"typeVersion": 1
},
{
"id": "2fcf20cd-3ffc-42d0-b5f5-e777e8336265",
"name": "When Every 30 Minutes",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
14704,
8224
],
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 30
}
]
}
},
"typeVersion": 1.2
},
{
"id": "445087ec-7823-4df6-ac60-31b479d4d421",
"name": "Set Config Parameters",
"type": "n8n-nodes-base.set",
"position": [
14928,
8224
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "c08-01",
"name": "vipMailboxes",
"type": "array",
"value": [
"user@example.com",
"user@example.com",
"user@example.com"
]
},
{
"id": "c08-02",
"name": "briefingRecipientUserId",
"type": "string",
"value": "YOUR_TEAMS_USER_ID_FOR_BRIEFINGS"
},
{
"id": "c08-03",
"name": "sharePointSiteId",
"type": "string",
"value": "YOUR_SHAREPOINT_SITE_ID"
},
{
"id": "c08-04",
"name": "minutesBefore",
"type": "number",
"value": 10
},
{
"id": "c08-05",
"name": "windowStart",
"type": "string",
"value": "={{ $now.toISO() }}"
},
{
"id": "c08-06",
"name": "windowEnd",
"type": "string",
"value": "={{ $now.plus({minutes: 40}).toISO() }}"
},
{
"id": "c08-07",
"name": "briefingChatId",
"type": "string",
"value": "YOUR_TEAMS_CHAT_ID"
},
{
"id": "c08-08",
"name": "teamsTeamId",
"type": "string",
"value": "OPTIONAL_TEAMS_TEAM_ID"
},
{
"id": "c08-09",
"name": "teamsChannelId",
"type": "string",
"value": "OPTIONAL_TEAMS_CHANNEL_ID"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "14dcb10c-313d-452c-8d66-761b7731f8a8",
"name": "Loop Over VIP Mailboxes",
"type": "n8n-nodes-base.splitInBatches",
"position": [
15200,
8224
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "ff56a5b2-7916-408f-867b-6f3345e4e18d",
"name": "Extract VIP Mailbox Data",
"type": "n8n-nodes-base.code",
"position": [
15792,
8208
],
"parameters": {
"jsCode": "const mailboxes = $('Set Config Parameters').item.json.vipMailboxes || [];\nconst idx = $('Loop Over VIP Mailboxes').context.currentRunIndex || 0;\nconst mailbox = mailboxes[idx];\nif (!mailbox) return [{ json: { skip: true } }];\nreturn [{ json: { mailbox, skip: false } }];"
},
"typeVersion": 2
},
{
"id": "ffaba837-64d4-4d6a-89d2-0d1c875d7112",
"name": "Fetch Calendar Events",
"type": "n8n-nodes-base.microsoftOutlook",
"onError": "continueRegularOutput",
"maxTries": 5,
"position": [
16016,
8208
],
"parameters": {
"limit": 10,
"filters": {},
"resource": "event"
},
"retryOnFail": true,
"typeVersion": 2,
"continueOnFail": true,
"waitBetweenTries": 3000
},
{
"id": "c2a6e385-6ebf-4900-9c99-d0e3ebff5810",
"name": "Filter Events for Briefing",
"type": "n8n-nodes-base.code",
"position": [
16368,
8208
],
"parameters": {
"jsCode": "const { DateTime } = require('luxon');\nconst events = $input.item.json.value || [];\nconst now = DateTime.now();\nconst minutesBefore = $('Set Config Parameters').item.json.minutesBefore || 10;\nconst mailbox = $('Extract VIP Mailbox Data').item.json.mailbox;\n\nconst qualifying = events.filter(evt => {\n const zone = (evt.start && evt.start.timeZone) ? evt.start.timeZone : 'UTC';\n const start = DateTime.fromISO(evt.start.dateTime, { zone });\n const minutesUntil = start.diff(now, 'minutes').minutes;\n return minutesUntil >= (minutesBefore - 5) && minutesUntil <= 40;\n});\n\n// Atomic read-check-and-optimistic-mark in one node execution.\n// Eliminates the cross-mailbox race: any event claimed here is immediately\n// stamped so a concurrent run won't also claim it.\n// Mark Event Briefed later overwrites with the confirmed send timestamp.\nconst staticData = $getWorkflowStaticData('global');\nconst seen = staticData['briefed_events'] || {};\nconst now_ts = Date.now();\n// Purge stale entries older than 2 hours\nObject.keys(seen).forEach(k => { if (now_ts - seen[k] > 7200000) delete seen[k]; });\n\nconst fresh = [];\nfor (const evt of qualifying) {\n if (seen[evt.id]) continue; // already claimed or sent\n seen[evt.id] = now_ts; // optimistic claim (atomic within this node)\n fresh.push({ ...evt, eventId: evt.id });\n}\nstaticData['briefed_events'] = seen; // single write-back\n\nreturn [{ json: { events: fresh, eventCount: fresh.length, mailbox } }];"
},
"typeVersion": 2
},
{
"id": "a679df5a-555c-4ff4-a8f0-25ee51f003a8",
"name": "Check Events to Brief",
"type": "n8n-nodes-base.if",
"position": [
16592,
8208
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cond-evts",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.eventCount }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2.2
},
{
"id": "8994e2f9-715b-407b-af83-360c67127e44",
"name": "No Events Switch Mailbox",
"type": "n8n-nodes-base.noOp",
"position": [
17056,
8336
],
"parameters": {},
"typeVersion": 1
},
{
"id": "071b7859-9aa9-4f69-b815-150b73712ad3",
"name": "Loop Through Events",
"type": "n8n-nodes-base.splitInBatches",
"position": [
17056,
8128
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "9eb0a957-1fb4-46c9-8e3c-e1ddb9db8b1c",
"name": "Extract Event Data",
"type": "n8n-nodes-base.code",
"position": [
17280,
8048
],
"parameters": {
"jsCode": "const events = $('Filter Events for Briefing').item.json.events;\nconst idx = $('Loop Through Events').context.currentRunIndex || 0;\nconst event = events[idx];\nif (!event) return [{ json: { skip: true } }];\n\nconst attendeeEmails = (event.attendees || [])\n .map(a => a.emailAddress ? a.emailAddress.address : null)\n .filter(Boolean);\n\nreturn [{\n json: {\n eventId: event.id,\n subject: event.subject,\n startTime: event.start.dateTime,\n startTimeZone: event.start.timeZone || 'UTC',\n endTime: event.end.dateTime,\n bodyPreview: event.bodyPreview || '',\n attendeeEmails,\n organizerEmail: event.organizer ? event.organizer.emailAddress.address : '',\n mailbox: $('Filter Events for Briefing').item.json.mailbox,\n skip: false\n }\n}];"
},
"typeVersion": 2
},
{
"id": "a1078632-5e1a-4a1c-805f-6fe69e5047fe",
"name": "Post to SharePoint Search API",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"maxTries": 5,
"position": [
18832,
8048
],
"parameters": {
"url": "https://graph.microsoft.com/v1.0/search/query",
"method": "POST",
"options": {},
"jsonBody": "={{ { \"requests\": [ { \"entityTypes\": [\"driveItem\"], \"query\": { \"queryString\": $input.item.json.spQuery }, \"from\": 0, \"size\": 5 } ] } }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "microsoftGraphOAuth2Api"
},
"retryOnFail": true,
"typeVersion": 4.2,
"continueOnFail": true,
"waitBetweenTries": 3000
},
{
"id": "7f93e565-35fc-48ed-8832-b0e351232112",
"name": "Build Attendee Request Payload",
"type": "n8n-nodes-base.code",
"position": [
17600,
8048
],
"parameters": {
"jsCode": "// Build Graph $batch request payload for attendee profile lookups.\n// Forward all event fields so the full context flows down the chain.\nconst event = $input.item.json;\nconst attendees = event.attendeeEmails || [];\nconst batchRequests = attendees.slice(0, 10).map((email, i) => ({\n id: String(i + 1),\n method: 'GET',\n url: `/users/${encodeURIComponent(email)}`\n + '?$select=id,displayName,jobTitle,department,mail,officeLocation'\n}));\n// Flag drives the bypass: Graph $batch rejects an empty requests array with 400\nreturn [{ json: { ...event, batchRequests, hasBatchRequests: batchRequests.length > 0 } }];"
},
"typeVersion": 2
},
{
"id": "d438235d-4aa2-4002-bafc-132bb223d784",
"name": "Post to Graph API for Attendees",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"maxTries": 5,
"position": [
18080,
7952
],
"parameters": {
"url": "https://graph.microsoft.com/v1.0/$batch",
"method": "POST",
"options": {},
"jsonBody": "={{ { \"requests\": $input.item.json.batchRequests } }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "microsoftGraphOAuth2Api"
},
"retryOnFail": true,
"typeVersion": 4.2,
"continueOnFail": true,
"waitBetweenTries": 3000
},
{
"id": "4724d562-f132-44e9-a22e-54da476dd823",
"name": "Compile Event Briefing",
"type": "n8n-nodes-base.code",
"position": [
19296,
8048
],
"parameters": {
"jsCode": "const { DateTime } = require('luxon');\n// All data arrives on a single item via the sequential enrichment chain.\n// No cross-node references: eliminates all stale-context risk.\nconst item = $input.item.json;\n\nconst warnings = [];\nconst batchData = item.batchData || {};\nconst searchData = item.searchData || {};\nif (batchData.error) warnings.push(\n '\u26a0\ufe0f Attendee profile lookup failed: ' + (batchData.error.message || 'unknown'));\nif (searchData.error) warnings.push(\n '\u26a0\ufe0f SharePoint search failed: ' + (searchData.error.message || 'unknown'));\n\nconst batchResponses = batchData.responses || [];\nconst profiles = batchResponses\n .filter(r => r.status === 200 && r.body)\n .map(r => {\n const p = r.body;\n return `- ${p.displayName || 'Unknown'} | ${p.jobTitle || 'No title'} | ${p.department || 'No dept'} | ${p.mail || ''}`;\n }).join('\\n') || 'No profile data available';\n\nconst hits = searchData.value?.[0]?.hitsContainers?.[0]?.hits || [];\nconst docs = hits.map(h => {\n const r = h.resource || {};\n const modified = r.lastModifiedDateTime ? r.lastModifiedDateTime.substring(0, 10) : 'N/A';\n return `- ${r.name || 'Unknown'} (Modified: ${modified}) ${r.webUrl || ''}`;\n}).join('\\n') || 'No recent documents found';\n\nconst startFormatted = DateTime.fromISO(\n item.startTime, { zone: item.startTimeZone || 'UTC' }\n).toFormat('hh:mm a');\n\nconst warningBlock = warnings.length ? '\\n\\n' + warnings.join('\\n') : '';\n\nreturn [{\n json: {\n eventId: item.eventId,\n subject: item.subject,\n recipientUserId: $('Set Config Parameters').item.json.briefingRecipientUserId,\n briefing: `\ud83d\udccb PRE-MEETING BRIEFING${warningBlock}\\n\\n\ud83d\uddd3 Meeting: ${item.subject}\\n\u23f0 Start: ${startFormatted}\\n\ud83d\udc64 Calendar: ${item.mailbox}\\n\\n\ud83d\udc65 ATTENDEES:\\n${profiles}\\n\\n\ud83d\udcc1 RELATED DOCUMENTS:\\n${docs}\\n\\n\ud83d\udcdd MEETING CONTEXT:\\n${item.bodyPreview || 'No description'}`\n }\n}];"
},
"typeVersion": 2
},
{
"id": "f0572228-1b7b-4deb-bbdc-7988f094386d",
"name": "Dispatch Briefing to Teams",
"type": "n8n-nodes-base.microsoftTeams",
"position": [
19520,
8048
],
"parameters": {
"chatId": "={{ $('Set Config Parameters').item.json.briefingChatId }}",
"message": "={{ $('Compile Event Briefing').item.json.briefing }}",
"options": {},
"resource": "chatMessage"
},
"typeVersion": 2,
"continueOnFail": true
},
{
"id": "47168375-02b5-45e0-8743-728f94c34b84",
"name": "Proceed to Next Event",
"type": "n8n-nodes-base.noOp",
"position": [
20768,
8048
],
"parameters": {},
"typeVersion": 1
},
{
"id": "a80fdf97-8261-40f9-a8ce-adda6c0f151c",
"name": "All Mailboxes Completed",
"type": "n8n-nodes-base.noOp",
"position": [
15424,
8144
],
"parameters": {},
"typeVersion": 1
},
{
"id": "f3ab4f77-7916-401b-9509-91076f22977f",
"name": "Teams Briefing Error Notice",
"type": "n8n-nodes-base.microsoftTeams",
"position": [
20160,
8144
],
"parameters": {
"chatId": "={{ $('Set Config Parameters').item.json.briefingChatId }}",
"message": "={{ `\u26a0\ufe0f **Pre-Meeting Briefing Failed**\n\n**Time:** ${$now.toISO()}\n**Meeting:** ${$('Extract Event Data').item.json.subject || 'Unknown'}\n**Error:** ${$json.error ? $json.error.message : 'Unknown briefing error'}\n\nCheck n8n execution log.` }}",
"options": {},
"resource": "chatMessage"
},
"typeVersion": 2,
"continueOnFail": true
},
{
"id": "672db101-0e65-4b34-8c8a-6ebddbac7651",
"name": "Check Briefing Sent Status",
"type": "n8n-nodes-base.if",
"position": [
19744,
8048
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cb01",
"operator": {
"type": "string",
"operation": "notEmpty"
},
"leftValue": "={{ $json.id }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "ae394820-362d-461c-a29c-cf592ae3a103",
"name": "Merge Data Before Next Event",
"type": "n8n-nodes-base.merge",
"position": [
20384,
8048
],
"parameters": {},
"typeVersion": 3
},
{
"id": "9fe7c33f-64b4-48f7-bb5a-3e52bee572e2",
"name": "Mark Event as Briefed",
"type": "n8n-nodes-base.code",
"position": [
20160,
7952
],
"parameters": {
"jsCode": "// Write the seen flag only after a briefing is confirmed sent\nconst staticData = $getWorkflowStaticData('global');\nconst seen = staticData['briefed_events'] || {};\n// Read from Compile Briefing which reliably carries eventId forward\nconst eventId = $('Compile Event Briefing').item.json.eventId;\nif (eventId) seen[eventId] = Date.now();\nstaticData['briefed_events'] = seen;\nreturn $input.all();"
},
"typeVersion": 2
},
{
"id": "c9d69d4a-24bb-43f5-b7c1-b530fdb3aaae",
"name": "Combine Batch API Response",
"type": "n8n-nodes-base.code",
"position": [
18304,
7952
],
"parameters": {
"jsCode": "// Merge the batch API response back onto the running event item.\n// $input.item.json is the raw Graph $batch response (or an error object).\n// Retrieve the full event context from the prior chain node.\nconst batchApiResponse = $input.item.json;\nconst event = $('Build Attendee Request Payload').item.json;\n// Strip batchRequests (no longer needed) and attach batch response\nconst { batchRequests, ...eventFields } = event;\nreturn [{ json: { ...eventFields, batchData: batchApiResponse } }];"
},
"typeVersion": 2
},
{
"id": "548c2076-83ab-4a22-b05f-ce1071d7ccc6",
"name": "Combine SP Search Response",
"type": "n8n-nodes-base.code",
"position": [
19056,
8048
],
"parameters": {
"jsCode": "// Merge the SharePoint search response back onto the running event item.\n// Read event context from Build SP Query (immediately upstream of Search SP).\nconst spApiResponse = $input.item.json;\nconst event = $('Build SharePoint Query').item.json;\nreturn [{ json: { ...event, searchData: spApiResponse } }];"
},
"typeVersion": 2
},
{
"id": "d112e0f9-2cdd-4c55-a225-78489f47bfb5",
"name": "Check for Attendee Requests",
"type": "n8n-nodes-base.if",
"position": [
17824,
8048
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "aa-01",
"operator": {
"type": "boolean",
"operation": "true"
},
"leftValue": "={{ $json.hasBatchRequests }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.2
},
{
"id": "e92b3df0-cea7-468a-bc44-85e40387f3e4",
"name": "Skip Attendee Batch Processing",
"type": "n8n-nodes-base.code",
"position": [
18080,
8144
],
"parameters": {
"jsCode": "// No attendees on this event -- skip the Graph $batch call entirely.\n// Emit the event item with an empty batchData so the chain shape is consistent.\nconst { batchRequests, hasBatchRequests, ...eventFields } = $input.item.json;\nreturn [{ json: { ...eventFields, batchData: { responses: [] } } }];"
},
"typeVersion": 2
},
{
"id": "ae55f7c1-06da-411b-ab62-920b6327cc62",
"name": "Build SharePoint Query",
"type": "n8n-nodes-base.code",
"position": [
18608,
8048
],
"parameters": {
"jsCode": "const { DateTime } = require('luxon');\nconst item = $input.item.json;\n\n// Base term: meeting subject (sanitised)\nconst subject = (item.subject || '').replace(/[\\\"]/g, '').trim();\n\n// Enrichment 1: restrict to organizer's domain if subject is short/generic\n// (heuristic: subject shorter than 15 chars or matches common generic titles)\nconst genericTitles = /^(sync|1[:\\-\\/]1|one.on.one|standup|stand.up|check.in|catch.up|meeting|call|chat)$/i;\nconst isGeneric = subject.length < 15 || genericTitles.test(subject.trim());\nlet queryParts = [subject ? `\"${subject}\"` : ''];\n\nif (isGeneric && item.organizerEmail) {\n const domain = item.organizerEmail.split('@')[1];\n if (domain) queryParts.push(`path:https://${domain.split('.')[0]}.sharepoint.com`);\n}\n\n// Enrichment 2: last-modified within 30 days (KQL syntax)\nconst cutoff = DateTime.now().minus({ days: 30 }).toFormat('yyyy-MM-dd');\nqueryParts.push(`lastModifiedTime>=${cutoff}`);\n\nconst spQuery = queryParts.filter(Boolean).join(' ');\nreturn [{ json: { ...item, spQuery } }];"
},
"typeVersion": 2
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "f5639b44-8d8e-44f7-8f9e-d7b11150d158",
"connections": {
"Extract Event Data": {
"main": [
[
{
"node": "Build Attendee Request Payload",
"type": "main",
"index": 0
}
]
]
},
"Loop Through Events": {
"main": [
[
{
"node": "Extract Event Data",
"type": "main",
"index": 0
}
],
[
{
"node": "Loop Over VIP Mailboxes",
"type": "main",
"index": 0
}
]
]
},
"Check Events to Brief": {
"main": [
[
{
"node": "Loop Through Events",
"type": "main",
"index": 0
}
],
[
{
"node": "No Events Switch Mailbox",
"type": "main",
"index": 0
}
]
]
},
"Fetch Calendar Events": {
"main": [
[
{
"node": "Filter Events for Briefing",
"type": "main",
"index": 0
}
]
]
},
"Mark Event as Briefed": {
"main": [
[
{
"node": "Merge Data Before Next Event",
"type": "main",
"index": 0
}
]
]
},
"Proceed to Next Event": {
"main": [
[
{
"node": "Loop Through Events",
"type": "main",
"index": 0
}
]
]
},
"Set Config Parameters": {
"main": [
[
{
"node": "Loop Over VIP Mailboxes",
"type": "main",
"index": 0
}
]
]
},
"When Every 30 Minutes": {
"main": [
[
{
"node": "Set Config Parameters",
"type": "main",
"index": 0
}
]
]
},
"Build SharePoint Query": {
"main": [
[
{
"node": "Post to SharePoint Search API",
"type": "main",
"index": 0
}
]
]
},
"Compile Event Briefing": {
"main": [
[
{
"node": "Dispatch Briefing to Teams",
"type": "main",
"index": 0
}
]
]
},
"Loop Over VIP Mailboxes": {
"main": [
[
{
"node": "Extract VIP Mailbox Data",
"type": "main",
"index": 0
}
],
[
{
"node": "All Mailboxes Completed",
"type": "main",
"index": 0
}
]
]
},
"Extract VIP Mailbox Data": {
"main": [
[
{
"node": "Fetch Calendar Events",
"type": "main",
"index": 0
}
]
]
},
"No Events Switch Mailbox": {
"main": [
[
{
"node": "Loop Over VIP Mailboxes",
"type": "main",
"index": 0
}
]
]
},
"Check Briefing Sent Status": {
"main": [
[
{
"node": "Mark Event as Briefed",
"type": "main",
"index": 0
}
],
[
{
"node": "Teams Briefing Error Notice",
"type": "main",
"index": 0
}
]
]
},
"Combine Batch API Response": {
"main": [
[
{
"node": "Build SharePoint Query",
"type": "main",
"index": 0
}
]
]
},
"Combine SP Search Response": {
"main": [
[
{
"node": "Compile Event Briefing",
"type": "main",
"index": 0
}
]
]
},
"Dispatch Briefing to Teams": {
"main": [
[
{
"node": "Check Briefing Sent Status",
"type": "main",
"index": 0
}
]
]
},
"Filter Events for Briefing": {
"main": [
[
{
"node": "Check Events to Brief",
"type": "main",
"index": 0
}
]
]
},
"Check for Attendee Requests": {
"main": [
[
{
"node": "Post to Graph API for Attendees",
"type": "main",
"index": 0
}
],
[
{
"node": "Skip Attendee Batch Processing",
"type": "main",
"index": 0
}
]
]
},
"Teams Briefing Error Notice": {
"main": [
[
{
"node": "Merge Data Before Next Event",
"type": "main",
"index": 1
}
]
]
},
"Merge Data Before Next Event": {
"main": [
[
{
"node": "Proceed to Next Event",
"type": "main",
"index": 0
}
]
]
},
"Post to SharePoint Search API": {
"main": [
[
{
"node": "Combine SP Search Response",
"type": "main",
"index": 0
}
]
]
},
"Build Attendee Request Payload": {
"main": [
[
{
"node": "Check for Attendee Requests",
"type": "main",
"index": 0
}
]
]
},
"Skip Attendee Batch Processing": {
"main": [
[
{
"node": "Build SharePoint Query",
"type": "main",
"index": 0
}
]
]
},
"Post to Graph API for Attendees": {
"main": [
[
{
"node": "Combine Batch API Response",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow runs every 30 minutes to scan VIP Microsoft Outlook calendars, look up attendee details in Microsoft Entra ID via Microsoft Graph, search SharePoint for related documents, and send a pre-meeting context briefing to a Microsoft Teams chat. Runs every 30 minutes and…
Source: https://n8n.io/workflows/16107/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow runs monthly to audit Microsoft 365 licensing via Microsoft Graph, identifies inactive users holding premium SKUs (excluding an exemption group and recent hires), optionally downgrades t
This workflow automatically connects to the Square API and generates a monthly sales summary report for all your Square locations. The report matches the figures displayed in Square Dashboard > Rep
This workflow automatically connects to the Square API and generates a weekly sales summary report for all your Square locations. The report matches the figures displayed in Square Dashboard > Repo
This workflow automatically connects to the Square API and generates a daily sales summary report for all your Square locations. The report matches the figures displayed in Square Dashboard > Repor
Mailhog Automation. Uses httpRequest, microsoftOutlook. Scheduled trigger; 13 nodes.