This workflow corresponds to n8n.io template #16118 — we link there as the canonical source.
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": "RkryKpZnunau0MJG",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Clean up stale Entra B2B guest accounts with Microsoft Graph, Teams and SharePoint",
"tags": [],
"nodes": [
{
"id": "e8380cea-b205-4236-acdd-4a6dbff5ec45",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
3408,
-256
],
"parameters": {
"width": 480,
"height": 640,
"content": "## Entra ID Governance: Cleanup Stale B2B Guest Accounts via Sign-In Activity\n\n### How it works\n\nThis workflow runs weekly to review Microsoft Entra ID B2B guest accounts, page through guest user data from Microsoft Graph, and identify accounts with stale sign-in activity. For each stale guest, it attempts to find the sponsor or manager, notifies them in Teams, waits 72 hours, then deletes the guest account if no intervention occurs. It logs successful deletions to SharePoint, alerts Teams on delete failures, and posts summary messages when there are no stale guests or when processing completes.\n\n### Setup steps\n\n- Configure Microsoft Graph credentials with permissions to read users and sign-in activity, read manager or sponsor information, and delete guest user accounts.\n- Set the SharePoint site and list IDs in the Config node so deletion audit records are written to the correct list.\n- Set the Teams team and channel IDs in the Config node for sponsor notifications, error alerts, and completion summaries.\n- Confirm the stale-account threshold and Graph query fields in the filtering and fetch nodes match your governance policy.\n- Validate that the weekly schedule and 72-hour wait period meet your organization\u2019s review and remediation requirements.\n\n### Customization\n\nAdjust the stale sign-in threshold, notification wording, wait duration, SharePoint audit fields, and Teams destinations to match internal guest lifecycle policies."
},
"typeVersion": 1
},
{
"id": "8e7e8856-3e7b-4343-b004-bc40ad7b4297",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
3936,
-16
],
"parameters": {
"color": 7,
"width": 640,
"height": 400,
"content": "## Schedule and configure\n\nStarts the workflow on a weekly schedule, loads tenant-specific SharePoint and Teams IDs, and initializes the pagination state for retrieving guest accounts."
},
"typeVersion": 1
},
{
"id": "f8ae9231-714c-4bd7-abd4-0cb8153104e7",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
4624,
-16
],
"parameters": {
"color": 7,
"width": 896,
"height": 400,
"content": "## Fetch guest pages\n\nRetrieves guest users from Microsoft Graph page by page, accumulates the returned guests, and loops with the next page URL while more results exist."
},
"typeVersion": 1
},
{
"id": "165cede5-3d70-4c8b-9d5e-a1bb06b7fe82",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
5600,
176
],
"parameters": {
"color": 7,
"width": 640,
"height": 464,
"content": "## Filter stale results\n\nApplies the stale guest criteria to the accumulated guest list, checks whether any stale accounts were found, and sends a Teams end message when none are present."
},
"typeVersion": 1
},
{
"id": "ce30733a-b200-4b22-946b-64c61f77f8ef",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
5712,
-208
],
"parameters": {
"color": 7,
"width": 416,
"height": 320,
"content": "## Create processing loop\n\nExpands the stale guest array into individual guest items and starts a batch loop so each stale account can be handled one at a time."
},
"typeVersion": 1
},
{
"id": "0043655b-4379-40a8-a14e-004163d2e574",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
6304,
-64
],
"parameters": {
"color": 7,
"width": 416,
"height": 352,
"content": "## Lookup guest sponsor\n\nFor each stale guest, queries Microsoft Graph for sponsor or manager details and stages the guest and sponsor fields used by downstream notifications and deletion steps."
},
"typeVersion": 1
},
{
"id": "b255c0e2-b8ca-4a59-965c-b27e3bc68070",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
6784,
-64
],
"parameters": {
"color": 7,
"width": 416,
"height": 352,
"content": "## Notify and wait\n\nSends a Teams notification to the responsible sponsor or manager, then pauses the workflow for 72 hours before continuing with remediation."
},
"typeVersion": 1
},
{
"id": "9594c189-9c74-4009-b390-e33b4243becc",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
7248,
-64
],
"parameters": {
"color": 7,
"width": 416,
"height": 352,
"content": "## Delete guest account\n\nAttempts to delete the stale guest account through Microsoft Graph and branches based on whether the delete operation succeeded."
},
"typeVersion": 1
},
{
"id": "006e94fb-4c82-4764-a237-77615cbecf2e",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
7712,
-64
],
"parameters": {
"color": 7,
"width": 320,
"height": 352,
"content": "## Alert delete failure\n\nSends a Teams alert when deletion fails so administrators can investigate the affected stale guest account manually."
},
"typeVersion": 1
},
{
"id": "b1bd9984-c5fa-4330-be33-c7c7a3ac076a",
"name": "Sticky Note9",
"type": "n8n-nodes-base.stickyNote",
"position": [
8096,
-64
],
"parameters": {
"color": 7,
"width": 272,
"height": 352,
"content": "## Log successful deletion\n\nWrites successful guest deletions to the configured SharePoint list as an audit record."
},
"typeVersion": 1
},
{
"id": "ce8bc54d-c84d-45d1-937e-f64553fe362d",
"name": "Sticky Note10",
"type": "n8n-nodes-base.stickyNote",
"position": [
8416,
-64
],
"parameters": {
"color": 7,
"width": 416,
"height": 336,
"content": "## Continue guest loop\n\nMerges the success and failure branches and routes control back to the batch loop to process the next stale guest."
},
"typeVersion": 1
},
{
"id": "a71832fa-7f36-4460-9742-80cc203af9d0",
"name": "Sticky Note11",
"type": "n8n-nodes-base.stickyNote",
"position": [
6304,
320
],
"parameters": {
"color": 7,
"width": 416,
"height": 320,
"content": "## Post completion summary\n\nHandles the end of the stale guest batch loop and posts a Teams summary after all stale guests have been processed."
},
"typeVersion": 1
},
{
"id": "77faed98-e1bd-4095-89b0-34bd1e74f4b2",
"name": "Weekly Trigger at 8am Monday",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
3984,
208
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 * * 1"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "2bcdafb0-650a-4495-9f23-3f63a5ac45ef",
"name": "Set Config Parameters",
"type": "n8n-nodes-base.set",
"position": [
4208,
208
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "c02-01",
"name": "sharepointSiteId",
"type": "string",
"value": "YOUR_SHAREPOINT_SITE_ID"
},
{
"id": "c02-02",
"name": "sharepointListId",
"type": "string",
"value": "YOUR_GUEST_AUDIT_LIST_ID"
},
{
"id": "c02-03",
"name": "teamsTeamId",
"type": "string",
"value": "YOUR_TEAMS_TEAM_ID"
},
{
"id": "c02-04",
"name": "teamsChannelId",
"type": "string",
"value": "YOUR_TEAMS_CHANNEL_ID"
},
{
"id": "c02-05",
"name": "inactivityThresholdDays",
"type": "number",
"value": 90
},
{
"id": "c02-06",
"name": "responseWindowHours",
"type": "number",
"value": 72
},
{
"id": "c02-07",
"name": "cutoffDate",
"type": "string",
"value": "={{ $now.minus({days: $json.inactivityThresholdDays}).toISO() }}"
},
{
"id": "c02-08",
"name": "runStartTime",
"type": "string",
"value": "={{ $now.toISO() }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "b1c8a8b9-7a64-46fc-aac8-c070868ee580",
"name": "Initialize Pagination Loop",
"type": "n8n-nodes-base.code",
"position": [
4432,
208
],
"parameters": {
"jsCode": "// Initialise the pagination loop.\n// Resets the shared global staticData key used across Collect Page and Filter Stale Guests.\n// NOTE: if a previous run was cancelled mid-pagination, this reset clears the partial\n// accumulation cleanly before the new run starts.\nconst state = $getWorkflowStaticData('global');\nstate.guestGovernance_allGuests = [];\nstate.guestGovernance_done = false;\n\nconst firstUrl = 'https://graph.microsoft.com/beta/users' +\n '?$filter=userType eq \\'Guest\\'' +\n '&$select=id,displayName,mail,userPrincipalName,createdDateTime,signInActivity' +\n '&$top=999';\n\nreturn [{ json: { nextUrl: firstUrl } }];"
},
"typeVersion": 2
},
{
"id": "1174a9da-043b-427d-82e3-1b752d6a5529",
"name": "Fetch Guest Page via URL",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"maxTries": 5,
"position": [
4672,
208
],
"parameters": {
"url": "={{ $json.nextUrl }}",
"options": {},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "oAuth2Api"
},
"retryOnFail": true,
"typeVersion": 4.2,
"continueOnFail": true,
"waitBetweenTries": 3000
},
{
"id": "5c3b9b36-5a19-47b4-941e-cc753fa780d7",
"name": "Accumulate Guests from Page",
"type": "n8n-nodes-base.code",
"position": [
4896,
208
],
"parameters": {
"jsCode": "// Accumulate guests from this page into global staticData.\n// Uses the 'global' scope so Init Pagination, Collect Page, and Filter Stale Guests\n// all read and write the same object.\nconst state = $getWorkflowStaticData('global');\nconst page = $input.item.json;\nconst newGuests = page.value || [];\n\nstate.guestGovernance_allGuests = (state.guestGovernance_allGuests || []).concat(newGuests);\nconst nextLink = page['@odata.nextLink'] || null;\nstate.guestGovernance_done = !nextLink;\n\nreturn [{\n json: {\n hasMore: !!nextLink,\n // Pass nextLink verbatim -- it is a fully-qualified URL from Graph and must not be\n // re-encoded. The HTTP Request node interpolates the expression value directly into\n // the URL field without additional encoding when used in an expression.\n nextUrl: nextLink,\n pageSize: newGuests.length,\n totalSoFar: state.guestGovernance_allGuests.length\n }\n}];"
},
"typeVersion": 2
},
{
"id": "498aacda-046b-4785-b4e7-30f7b9c4ca3c",
"name": "If More Pages Available",
"type": "n8n-nodes-base.if",
"position": [
5120,
208
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "has-more-pages",
"operator": {
"type": "boolean",
"operation": "equal"
},
"leftValue": "={{ $json.hasMore }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.2
},
{
"id": "1f4dff5b-f9cf-4c5f-b0f0-60372e84dabb",
"name": "Set Next URL",
"type": "n8n-nodes-base.set",
"position": [
5360,
112
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "next-url-pass",
"name": "nextUrl",
"type": "string",
"value": "={{ $json.nextUrl }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "9ce17f61-dddf-4842-9b1a-0d020aa19db7",
"name": "Filter Stale Guest Accounts",
"type": "n8n-nodes-base.code",
"position": [
5616,
304
],
"parameters": {
"jsCode": "// Read all accumulated guests from global staticData, apply stale filter,\n// then clear the state. Clearing AFTER reading means a crash here loses the\n// list for this run only -- the next run will re-fetch from Graph from scratch.\n// This is an acceptable trade-off; see setup notes.\nconst state = $getWorkflowStaticData('global');\nconst guests = state.guestGovernance_allGuests || [];\n\n// Clear now so the key does not grow indefinitely across runs\nstate.guestGovernance_allGuests = [];\nstate.guestGovernance_done = false;\n\nconst cutoffDate = new Date($('Set Config Parameters').item.json.cutoffDate);\n\nconst staleGuests = guests.filter(guest => {\n const signInActivity = guest.signInActivity;\n if (!signInActivity || !signInActivity.lastSignInDateTime) {\n const createdDate = new Date(guest.createdDateTime || '2000-01-01');\n return createdDate < cutoffDate;\n }\n return new Date(signInActivity.lastSignInDateTime) < cutoffDate;\n});\n\n// Emit exactly one summary item. Any Stale Guests? branches on hasStale.\n// The true branch leads to Split Stale Guests which unpacks staleGuests into\n// one item per guest for Loop Stale Guests.\nreturn [{\n json: {\n staleCount: staleGuests.length,\n totalScanned: guests.length,\n hasStale: staleGuests.length > 0,\n staleGuests: staleGuests,\n cutoffDate: cutoffDate.toISOString()\n }\n}];"
},
"typeVersion": 2
},
{
"id": "68bd2c61-4f3a-446f-85a2-f4b70a6d1c4b",
"name": "Check Stale Guests Presence",
"type": "n8n-nodes-base.if",
"position": [
5840,
304
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cond-stale",
"operator": {
"type": "boolean",
"operation": "equal"
},
"leftValue": "={{ $json.hasStale }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.2
},
{
"id": "066b98e7-cd98-4f0c-bb15-e5e63fadbb82",
"name": "Notify No Stale Guests",
"type": "n8n-nodes-base.microsoftTeams",
"position": [
6064,
432
],
"parameters": {
"teamId": "={{ $('Set Config Parameters').item.json.teamsTeamId }}",
"message": "=\u2705 Guest Account Governance: Weekly scan complete. No stale B2B guest accounts found (threshold: {{ $('Set Config Parameters').item.json.inactivityThresholdDays }} days). Total guests scanned: {{ $json.totalScanned }}",
"options": {},
"resource": "channelMessage",
"channelId": "={{ $('Set Config Parameters').item.json.teamsChannelId }}"
},
"typeVersion": 2,
"continueOnFail": true
},
{
"id": "bf9f454c-bbe8-44be-a506-511b157a75fb",
"name": "Unpack Stale Guests Array",
"type": "n8n-nodes-base.code",
"position": [
5760,
-48
],
"parameters": {
"jsCode": "// Unpack the staleGuests array from the summary item into one item per guest.\n// This is what feeds SplitInBatches with a proper list.\nconst summary = $input.item.json;\nconst staleGuests = summary.staleGuests || [];\n\nreturn staleGuests.map(guest => ({\n json: {\n staleCount: summary.staleCount,\n guestId: guest.id,\n guestEmail: guest.mail || guest.userPrincipalName,\n displayName: guest.displayName,\n lastSignIn: (\n guest.signInActivity && guest.signInActivity.lastSignInDateTime\n ? guest.signInActivity.lastSignInDateTime\n : 'Never'\n ),\n createdDateTime: guest.createdDateTime\n }\n}));"
},
"typeVersion": 2
},
{
"id": "7a751620-f197-47ca-aa91-6bac2c293d98",
"name": "Loop Over Stale Guests",
"type": "n8n-nodes-base.splitInBatches",
"position": [
5984,
-48
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "bb31398c-5e46-4976-99bd-f58a0e774836",
"name": "Fetch Guest Manager",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"maxTries": 5,
"position": [
6352,
112
],
"parameters": {
"url": "=https://graph.microsoft.com/v1.0/users/{{ $json.guestId }}/manager?$select=id,displayName,mail",
"options": {},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "oAuth2Api"
},
"retryOnFail": true,
"typeVersion": 4.2,
"continueOnFail": true,
"waitBetweenTries": 3000
},
{
"id": "651de2fe-2f12-438c-bbef-99be188f0136",
"name": "Prepare Guest and Sponsor Data",
"type": "n8n-nodes-base.set",
"position": [
6576,
112
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "gs-01",
"name": "guestId",
"type": "string",
"value": "={{ $('Loop Over Stale Guests').item.json.guestId }}"
},
{
"id": "gs-02",
"name": "guestEmail",
"type": "string",
"value": "={{ $('Loop Over Stale Guests').item.json.guestEmail }}"
},
{
"id": "gs-03",
"name": "displayName",
"type": "string",
"value": "={{ $('Loop Over Stale Guests').item.json.displayName }}"
},
{
"id": "gs-04",
"name": "lastSignIn",
"type": "string",
"value": "={{ $('Loop Over Stale Guests').item.json.lastSignIn }}"
},
{
"id": "gs-05",
"name": "sponsorEmail",
"type": "string",
"value": "={{ $json.mail || 'unknown' }}"
},
{
"id": "gs-06",
"name": "sponsorName",
"type": "string",
"value": "={{ $json.displayName || 'Unknown Sponsor' }}"
},
{
"id": "gs-07",
"name": "sharepointSiteId",
"type": "string",
"value": "={{ $('Set Config Parameters').item.json.sharepointSiteId }}"
},
{
"id": "gs-08",
"name": "sharepointListId",
"type": "string",
"value": "={{ $('Set Config Parameters').item.json.sharepointListId }}"
},
{
"id": "gs-09",
"name": "teamsTeamId",
"type": "string",
"value": "={{ $('Set Config Parameters').item.json.teamsTeamId }}"
},
{
"id": "gs-10",
"name": "teamsChannelId",
"type": "string",
"value": "={{ $('Set Config Parameters').item.json.teamsChannelId }}"
},
{
"id": "gs-11",
"name": "responseWindowHours",
"type": "number",
"value": "={{ $('Set Config Parameters').item.json.responseWindowHours }}"
},
{
"id": "gs-12",
"name": "runStartTime",
"type": "string",
"value": "={{ $('Set Config Parameters').item.json.runStartTime }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "1364052c-0599-4de1-9728-b5920eb682a8",
"name": "Send Notification to Sponsor",
"type": "n8n-nodes-base.microsoftTeams",
"position": [
6832,
112
],
"parameters": {
"teamId": "={{ $json.teamsTeamId }}",
"message": "=\u26a0\ufe0f **Stale B2B Guest Account Notice**\n\nHi {{ $json.sponsorName }},\n\nThe following B2B guest account has been inactive for >90 days and is scheduled for automatic deletion:\n\n**Guest:** {{ $json.displayName }}\n**Email:** {{ $json.guestEmail }}\n**Last Sign-In:** {{ $json.lastSignIn }}\n**Scheduled Deletion:** {{ $now.plus({hours: $json.responseWindowHours}).toFormat('yyyy-MM-dd HH:mm') }} UTC\n\nIf this account must be retained, contact IT Security **before the deletion date** with the guest's email address and business justification.\n\n_This is an automated notification from Entra ID Governance. Replying to this message will not affect the deletion._",
"options": {},
"resource": "channelMessage",
"channelId": "={{ $json.teamsChannelId }}"
},
"typeVersion": 2,
"continueOnFail": true
},
{
"id": "f2e6b733-b4d3-4762-850c-0e6346d7a455",
"name": "Wait 72 Hours for Reply",
"type": "n8n-nodes-base.wait",
"position": [
7056,
112
],
"parameters": {
"unit": "hours",
"amount": 72
},
"typeVersion": 1.1
},
{
"id": "76209b2d-1885-485e-ba47-3c076705f0d9",
"name": "Delete Stale Guest Account",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"maxTries": 5,
"position": [
7296,
112
],
"parameters": {
"url": "=https://graph.microsoft.com/v1.0/users/{{ $('Prepare Guest and Sponsor Data').item.json.guestId }}",
"method": "DELETE",
"options": {},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "oAuth2Api"
},
"retryOnFail": true,
"typeVersion": 4.2,
"continueOnFail": true,
"waitBetweenTries": 3000
},
{
"id": "be480b57-5c6b-43a5-bb62-9b2933df6102",
"name": "Check Deletion Success",
"type": "n8n-nodes-base.if",
"position": [
7520,
112
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cd01",
"operator": {
"type": "number",
"operation": "equal"
},
"leftValue": "={{ $response.statusCode }}",
"rightValue": 204
}
]
}
},
"typeVersion": 2.2
},
{
"id": "9126ec1e-367e-490a-b754-a77174790153",
"name": "Log Deletion in SharePoint",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"maxTries": 5,
"position": [
8144,
128
],
"parameters": {
"url": "=https://graph.microsoft.com/v1.0/sites/{{ $('Prepare Guest and Sponsor Data').item.json.sharepointSiteId }}/lists/{{ $('Prepare Guest and Sponsor Data').item.json.sharepointListId }}/items",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify({ fields: { GuestEmail: $('Prepare Guest and Sponsor Data').item.json.guestEmail, DisplayName: $('Prepare Guest and Sponsor Data').item.json.displayName, LastSignIn: $('Prepare Guest and Sponsor Data').item.json.lastSignIn, SponsorNotified: $('Prepare Guest and Sponsor Data').item.json.sponsorEmail, DeletedAt: $now.toISO(), DeletedBy: 'n8n-governance-workflow' } }) }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "oAuth2Api"
},
"retryOnFail": true,
"typeVersion": 4.2,
"continueOnFail": true,
"waitBetweenTries": 3000
},
{
"id": "1d1fc74d-206f-40aa-b4ab-2bac41e2c4ce",
"name": "Alert Deletion Error",
"type": "n8n-nodes-base.microsoftTeams",
"position": [
7824,
128
],
"parameters": {
"teamId": "={{ $('Prepare Guest and Sponsor Data').item.json.teamsTeamId }}",
"message": "=\u274c **Guest Deletion Error**\n\n**Guest:** {{ $('Prepare Guest and Sponsor Data').item.json.guestEmail }}\n**Error:** {{ $json.error ? $json.error.message : 'Delete API failed' }}\n**Time:** {{ $now.toISO() }}\n\nManual deletion required. Check Azure AD portal.",
"options": {},
"resource": "channelMessage",
"channelId": "={{ $('Prepare Guest and Sponsor Data').item.json.teamsChannelId }}"
},
"typeVersion": 2,
"continueOnFail": true
},
{
"id": "23b4fab8-f0e7-48b8-9338-97fcd7f57c3e",
"name": "Merge Data for Next Guest",
"type": "n8n-nodes-base.merge",
"position": [
8464,
112
],
"parameters": {},
"typeVersion": 3
},
{
"id": "ce51e95f-db61-4733-855d-170e89d9a153",
"name": "Proceed to Next Guest",
"type": "n8n-nodes-base.noOp",
"position": [
8688,
112
],
"parameters": {},
"typeVersion": 1
},
{
"id": "d6825263-98e2-43e1-ba1e-1e0b6337b50f",
"name": "Final Guest Processed",
"type": "n8n-nodes-base.noOp",
"position": [
6352,
464
],
"parameters": {},
"typeVersion": 1
},
{
"id": "ef1e40b0-b6cc-44b8-96dc-b4a1421bdc3d",
"name": "Post Process Summary in Teams",
"type": "n8n-nodes-base.microsoftTeams",
"position": [
6576,
464
],
"parameters": {
"teamId": "={{ $('Set Config Parameters').item.json.teamsTeamId }}",
"message": "=\u2705 **Guest Account Governance: Run Complete**\n\nAll stale B2B guest accounts have been processed.\n\n_Check the SharePoint GuestAccountAuditLog list for full deletion records._\n\n**Completed:** {{ $now.toISO() }}",
"options": {},
"resource": "channelMessage",
"channelId": "={{ $('Set Config Parameters').item.json.teamsChannelId }}"
},
"typeVersion": 2,
"continueOnFail": true
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "4c981a1b-6e41-46a7-935a-6cd0fd3c8729",
"connections": {
"Set Next URL": {
"main": [
[
{
"node": "Fetch Guest Page via URL",
"type": "main",
"index": 0
}
]
]
},
"Fetch Guest Manager": {
"main": [
[
{
"node": "Prepare Guest and Sponsor Data",
"type": "main",
"index": 0
}
]
]
},
"Alert Deletion Error": {
"main": [
[
{
"node": "Merge Data for Next Guest",
"type": "main",
"index": 1
}
]
]
},
"Final Guest Processed": {
"main": [
[
{
"node": "Post Process Summary in Teams",
"type": "main",
"index": 0
}
]
]
},
"Proceed to Next Guest": {
"main": [
[
{
"node": "Loop Over Stale Guests",
"type": "main",
"index": 0
}
]
]
},
"Set Config Parameters": {
"main": [
[
{
"node": "Initialize Pagination Loop",
"type": "main",
"index": 0
}
]
]
},
"Check Deletion Success": {
"main": [
[
{
"node": "Log Deletion in SharePoint",
"type": "main",
"index": 0
}
],
[
{
"node": "Alert Deletion Error",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Stale Guests": {
"main": [
[
{
"node": "Fetch Guest Manager",
"type": "main",
"index": 0
}
],
[
{
"node": "Final Guest Processed",
"type": "main",
"index": 0
}
]
]
},
"If More Pages Available": {
"main": [
[
{
"node": "Set Next URL",
"type": "main",
"index": 0
}
],
[
{
"node": "Filter Stale Guest Accounts",
"type": "main",
"index": 0
}
]
]
},
"Wait 72 Hours for Reply": {
"main": [
[
{
"node": "Delete Stale Guest Account",
"type": "main",
"index": 0
}
]
]
},
"Fetch Guest Page via URL": {
"main": [
[
{
"node": "Accumulate Guests from Page",
"type": "main",
"index": 0
}
]
]
},
"Merge Data for Next Guest": {
"main": [
[
{
"node": "Proceed to Next Guest",
"type": "main",
"index": 0
}
]
]
},
"Unpack Stale Guests Array": {
"main": [
[
{
"node": "Loop Over Stale Guests",
"type": "main",
"index": 0
}
]
]
},
"Delete Stale Guest Account": {
"main": [
[
{
"node": "Check Deletion Success",
"type": "main",
"index": 0
}
]
]
},
"Initialize Pagination Loop": {
"main": [
[
{
"node": "Fetch Guest Page via URL",
"type": "main",
"index": 0
}
]
]
},
"Log Deletion in SharePoint": {
"main": [
[
{
"node": "Merge Data for Next Guest",
"type": "main",
"index": 0
}
]
]
},
"Accumulate Guests from Page": {
"main": [
[
{
"node": "If More Pages Available",
"type": "main",
"index": 0
}
]
]
},
"Check Stale Guests Presence": {
"main": [
[
{
"node": "Unpack Stale Guests Array",
"type": "main",
"index": 0
}
],
[
{
"node": "Notify No Stale Guests",
"type": "main",
"index": 0
}
]
]
},
"Filter Stale Guest Accounts": {
"main": [
[
{
"node": "Check Stale Guests Presence",
"type": "main",
"index": 0
}
]
]
},
"Send Notification to Sponsor": {
"main": [
[
{
"node": "Wait 72 Hours for Reply",
"type": "main",
"index": 0
}
]
]
},
"Weekly Trigger at 8am Monday": {
"main": [
[
{
"node": "Set Config Parameters",
"type": "main",
"index": 0
}
]
]
},
"Prepare Guest and Sponsor Data": {
"main": [
[
{
"node": "Send Notification to Sponsor",
"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 weekly to find inactive Entra ID (Azure AD) B2B guest accounts using Microsoft Graph sign-in activity, notifies each guest’s sponsor via Microsoft Teams, waits 72 hours, deletes the accounts, and logs successful deletions to a SharePoint list. Runs every…
Source: https://n8n.io/workflows/16118/ — 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 template runs two scheduled workflows to govern Microsoft Entra ID (Azure AD) guest accounts by detecting stale users via Microsoft Graph, staging deletions in SharePoint with a 72-hour window, n
Automatically collect client feedback from Nicereply, analyze sentiment, and send it to the right Microsoft Teams channels — smartly split by team, tone, and comment presence.
GLPI Pending Tickets Notification to Microsoft Teams
This n8n workflow provides an automated notification system that monitors tickets in GLPI (Gestionnaire Libre de Parc Informatique) and sends proactive alerts through Microsoft Teams when tickets are
PF – WF-03 Social Media Auto-Post. Uses microsoftSharePoint, httpRequest, microsoftTeams. Scheduled trigger; 16 nodes.