{
  "id": "Q4L6iBFAv06dOl2C",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Entra ID Stale Guest Auto-Cleanup",
  "tags": [],
  "nodes": [
    {
      "id": "d02d1b98-d37a-4a8f-9716-f538fe6ec42e",
      "name": "Scanner Section ",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        11040,
        5312
      ],
      "parameters": {
        "color": 7,
        "width": 853,
        "height": 517,
        "content": "## Notify sponsor or security\n\nValidates whether a queued account has a usable sponsor email, then posts either a sponsor notification or an IT Security alert to Teams."
      },
      "typeVersion": 1
    },
    {
      "id": "4633f2b3-7130-48fd-833b-a86376f139d6",
      "name": "Split Pending Items",
      "type": "n8n-nodes-base.code",
      "position": [
        10064,
        6016
      ],
      "parameters": {
        "jsCode": "// Native microsoftSharePoint getAll returns items as array directly\n// Each item has: id (list item ID) and the field values at top level\n// (the node flattens fields for us \u2014 no .fields wrapper needed)\nconst raw = $input.item.json;\nconst items = Array.isArray(raw) ? raw : (raw.value || []);\n\nreturn items.map(item => ({\n\u00a0 json: {\n\u00a0 \u00a0 listItemId: item.id,\n\u00a0 \u00a0 guestId: item.GuestId || item.fields?.GuestId,\n\u00a0 \u00a0 guestEmail: item.GuestEmail || item.fields?.GuestEmail || '',\n\u00a0 \u00a0 guestEmailODataSafe: (item.GuestEmail || item.fields?.GuestEmail || '').replace(/'/g, \"''\"),\n\u00a0 \u00a0 displayName: item.DisplayName || item.fields?.DisplayName,\n\u00a0 \u00a0 lastSignIn: item.LastSignIn || item.fields?.LastSignIn,\n\u00a0 \u00a0 sponsorEmail: item.SponsorEmail || item.fields?.SponsorEmail,\n\u00a0 \u00a0 sponsorName: item.SponsorName || item.fields?.SponsorName,\n\u00a0 \u00a0 scheduledDeletionTime: item.ScheduledDeletionTime || item.fields?.ScheduledDeletionTime\n\u00a0 }\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "e6c42cfd-1b09-4878-96cd-fb87e926d65c",
      "name": "Executioner Section 5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        11712,
        5888
      ],
      "parameters": {
        "color": 7,
        "width": 786,
        "height": 516,
        "content": "## Record deletion outcomes\n\nRecords successful deletions to the SharePoint audit list and marks pending records deleted, while failed deletions generate a Teams alert and mark the pending item as errored."
      },
      "typeVersion": 1
    },
    {
      "id": "d3e31c0b-a2a5-4d26-a302-839123abc558",
      "name": "Executioner Section 4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        10896,
        5888
      ],
      "parameters": {
        "color": 7,
        "width": 769,
        "height": 517,
        "content": "## Delete or retain account\n\nHandles the closely placed deletion and retention outcomes: eligible guests are deleted in Entra ID and checked for success, while exception-matched records are marked retained and logged to Teams."
      },
      "typeVersion": 1
    },
    {
      "id": "b1193a0d-7fdd-4c2a-9b7a-6fedb617d132",
      "name": "Executioner Section 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        10288,
        5888
      ],
      "parameters": {
        "color": 7,
        "width": 563,
        "height": 515,
        "content": "## Check retention exceptions\n\nLooks up each overdue guest in the retention-exceptions list and branches based on whether deletion should be blocked."
      },
      "typeVersion": 1
    },
    {
      "id": "b83c3f1e-a634-4dc8-8542-165548937ce0",
      "name": "Executioner Section 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        9568,
        5888
      ],
      "parameters": {
        "color": 7,
        "width": 673,
        "height": 516,
        "content": "## Collect overdue deletions\n\nGets overdue pending-deletion records from SharePoint, checks whether any are due, splits due records for item-by-item processing, or ends when nothing is due."
      },
      "typeVersion": 1
    },
    {
      "id": "b147c809-c080-4bc6-a23f-b5582120fed0",
      "name": "Executioner Section 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        9008,
        5888
      ],
      "parameters": {
        "color": 7,
        "width": 513,
        "height": 518,
        "content": "## Daily execution setup\n\nStarts the daily deletion execution run and loads configuration for pending deletions, audit logging, and retention exceptions."
      },
      "typeVersion": 1
    },
    {
      "id": "5446a9e1-a62a-4e6d-83e4-7342d5d41e3d",
      "name": "Scanner Section 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        10224,
        5312
      ],
      "parameters": {
        "color": 7,
        "width": 773,
        "height": 517,
        "content": "## Queue new candidates\n\nChecks the SharePoint pending-deletions list to avoid duplicates, queues newly identified stale guests, and skips accounts that are already pending."
      },
      "typeVersion": 1
    },
    {
      "id": "83af243d-d12d-4903-8e75-3ee78e65f1d0",
      "name": "Scanner Section 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        9616,
        5312
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 517,
        "content": "## Find stale guests\n\nRetrieves guest users from Microsoft Graph and filters each paginated response to identify stale accounts that should be evaluated for deletion."
      },
      "typeVersion": 1
    },
    {
      "id": "beb86f97-cda8-41a3-9bea-39356f4537ee",
      "name": "Scanner Section 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        9008,
        5312
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 517,
        "content": "## Weekly scan setup\n\nStarts the weekly stale guest discovery run and loads configuration values for SharePoint and Teams destinations."
      },
      "typeVersion": 1
    },
    {
      "id": "5fd82058-501b-479c-8572-fc0f7f3fc6d1",
      "name": "Mark Pending as Error",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "position": [
        12208,
        6240
      ],
      "parameters": {
        "list": {
          "mode": "id",
          "value": "={{ $('Config (Executioner)').item.json.pendingDeletionsListId }}"
        },
        "site": {
          "mode": "id",
          "value": "={{ $('Config (Executioner)').item.json.sharepointSiteId }}"
        },
        "columns": {
          "value": {
            "id": "={{ $('Split Pending Items').item.json.listItemId }}",
            "Status": "Error",
            "ErrorAt": "={{ $now.toISO() }}",
            "ErrorDetails": "Manual Action Required \u2014 automated deletion failed. Check Azure AD portal."
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ]
        },
        "resource": "item",
        "operation": "update",
        "requestOptions": {}
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "1472c607-04c1-4ab0-b62a-12af4772b4f9",
      "name": "Log Retention to Teams",
      "type": "n8n-nodes-base.microsoftTeams",
      "position": [
        11296,
        6208
      ],
      "parameters": {
        "teamId": "={{ $('Config (Executioner)').item.json.teamsTeamId }}",
        "message": "=\u2139\ufe0f **Guest Retention Exception Applied**\n\n**Guest:** {{ $('Split Pending Items').item.json.displayName }}\n**Email:** {{ $('Split Pending Items').item.json.guestEmail }}\n**Sponsor:** {{ $('Split Pending Items').item.json.sponsorName }}\n**Time:** {{ $now.toISO() }}\n\nRetention exception found. Deletion skipped and record marked Retained.",
        "options": {},
        "resource": "channelMessage",
        "channelId": "={{ $('Config (Executioner)').item.json.teamsChannelId }}"
      },
      "typeVersion": 2,
      "continueOnFail": true
    },
    {
      "id": "77166012-4feb-4ddf-a3cf-33082d0c1163",
      "name": "Mark Pending as Retained",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "position": [
        11072,
        6208
      ],
      "parameters": {
        "list": {
          "mode": "id",
          "value": "={{ $('Config (Executioner)').item.json.pendingDeletionsListId }}"
        },
        "site": {
          "mode": "id",
          "value": "={{ $('Config (Executioner)').item.json.sharepointSiteId }}"
        },
        "columns": {
          "value": {
            "id": "={{ $('Split Pending Items').item.json.listItemId }}",
            "Status": "Retained",
            "RetainedAt": "={{ $now.toISO() }}"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ]
        },
        "resource": "item",
        "operation": "update",
        "requestOptions": {}
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "f0628022-c7f1-4125-bb59-5f0d3de00c7d",
      "name": "Delete Error Alert",
      "type": "n8n-nodes-base.microsoftTeams",
      "position": [
        11936,
        6240
      ],
      "parameters": {
        "teamId": "={{ $('Config (Executioner)').item.json.teamsTeamId }}",
        "message": "=\u274c **Guest Deletion Error \u2014 Manual Action Required**\n\n**Guest:** {{ $('Split Pending Items').item.json.guestEmail }}\n**Display Name:** {{ $('Split Pending Items').item.json.displayName }}\n**Error:** {{ $json.error ? $json.error.message : 'Delete API call failed' }}\n**Time:** {{ $now.toISO() }}\n\nPlease delete this account manually in the Azure AD portal.",
        "options": {},
        "resource": "channelMessage",
        "channelId": "={{ $('Config (Executioner)').item.json.teamsChannelId }}"
      },
      "typeVersion": 2,
      "continueOnFail": true
    },
    {
      "id": "1c82e29a-59ac-4192-b329-6c18ea151f98",
      "name": "Mark Pending as Deleted",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "position": [
        12192,
        6000
      ],
      "parameters": {
        "list": {
          "mode": "id",
          "value": "={{ $('Config (Executioner)').item.json.pendingDeletionsListId }}"
        },
        "site": {
          "mode": "id",
          "value": "={{ $('Config (Executioner)').item.json.sharepointSiteId }}"
        },
        "columns": {
          "value": {
            "id": "={{ $('Split Pending Items').item.json.listItemId }}",
            "Status": "Deleted",
            "DeletedAt": "={{ $now.toISO() }}"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ]
        },
        "resource": "item",
        "operation": "update",
        "requestOptions": {}
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "f91b2e89-d9ad-408f-b728-c424a37cc5c7",
      "name": "Log Deletion to Audit List",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "position": [
        11920,
        6000
      ],
      "parameters": {
        "list": {
          "mode": "id",
          "value": "={{ $('Config (Executioner)').item.json.auditLogListId }}"
        },
        "site": {
          "mode": "id",
          "value": "={{ $('Config (Executioner)').item.json.sharepointSiteId }}"
        },
        "columns": {
          "value": {
            "DeletedAt": "={{ $now.toISO() }}",
            "GuestEmail": "={{ $('Split Pending Items').item.json.guestEmail }}",
            "LastSignIn": "={{ $('Split Pending Items').item.json.lastSignIn }}",
            "DisplayName": "={{ $('Split Pending Items').item.json.displayName }}",
            "SponsorNotified": "={{ $('Split Pending Items').item.json.sponsorEmail }}"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": []
        },
        "resource": "item",
        "operation": "create",
        "requestOptions": {}
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "51af025c-56f2-4018-908f-359dec678d71",
      "name": "Delete Succeeded?",
      "type": "n8n-nodes-base.if",
      "position": [
        11296,
        6016
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "del-ok",
              "operator": {
                "type": "boolean",
                "operation": "equal"
              },
              "leftValue": "={{ $json.deleted }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "1f126bee-cc4d-4f94-b1a9-6a6914e5e129",
      "name": "Delete Stale Guest Account",
      "type": "n8n-nodes-base.microsoftEntra",
      "position": [
        11072,
        6016
      ],
      "parameters": {
        "user": {
          "mode": "id",
          "value": "={{ $('Split Pending Items').item.json.guestId }}"
        },
        "operation": "delete",
        "requestOptions": {}
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "43c204ec-0209-4f82-b45e-9a76ef86c1b0",
      "name": "Retention Exception?",
      "type": "n8n-nodes-base.if",
      "position": [
        10624,
        6128
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "ret-check",
              "operator": {
                "type": "number",
                "operation": "equal"
              },
              "leftValue": "={{ Array.isArray($json) ? $json.length : ($json.value || []).length }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "e3c8c941-b8db-4744-8f80-cf286f65713e",
      "name": "Check Retention Exception",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "position": [
        10400,
        6128
      ],
      "parameters": {
        "list": {
          "mode": "id",
          "value": "={{ $('Config').item.json.retentionExceptionsListId }}"
        },
        "site": {
          "mode": "id",
          "value": "={{ $('Config').item.json.sharepointSiteId }}"
        },
        "limit": 1,
        "filter": "={{ \"fields/GuestEmail eq '\" + $json.guestEmailODataSafe + \"'\" }}",
        "options": {},
        "resource": "item",
        "simplify": false,
        "requestOptions": {}
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "460829f0-e8a9-4491-b1ce-3c341e5bd7c2",
      "name": "Nothing Due Today",
      "type": "n8n-nodes-base.noOp",
      "position": [
        10064,
        6208
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "48c9233c-843e-4fec-af24-a3f2a0f33ac7",
      "name": "Any Due For Deletion?",
      "type": "n8n-nodes-base.if",
      "position": [
        9840,
        6112
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "has-work",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ Array.isArray($json) ? $json.length : ($json.value || []).length }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "2caf74e0-b071-4c0f-ad53-161950e12d04",
      "name": "Get Overdue Pending Deletions",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "position": [
        9616,
        6112
      ],
      "parameters": {
        "list": {
          "mode": "id",
          "value": "={{ $json.pendingDeletionsListId }}"
        },
        "site": {
          "mode": "id",
          "value": "={{ $json.sharepointSiteId }}"
        },
        "filter": "={{ \"fields/Status eq 'Pending' and fields/ScheduledDeletionTime le '\" + $json.nowISO + \"'\" }}",
        "options": {},
        "resource": "item",
        "simplify": false,
        "returnAll": true,
        "requestOptions": {}
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "f3c262e2-effe-4d48-8879-4bab6fec4c5b",
      "name": "Config (Executioner)",
      "type": "n8n-nodes-base.set",
      "position": [
        9328,
        6112
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "e-c01",
              "name": "sharepointSiteId",
              "type": "string",
              "value": "YOUR_SHAREPOINT_SITE_ID"
            },
            {
              "id": "e-c02",
              "name": "pendingDeletionsListId",
              "type": "string",
              "value": "YOUR_PENDING_DELETIONS_LIST_ID"
            },
            {
              "id": "e-c03",
              "name": "auditLogListId",
              "type": "string",
              "value": "YOUR_GUEST_AUDIT_LIST_ID"
            },
            {
              "id": "e-c04",
              "name": "retentionExceptionsListId",
              "type": "string",
              "value": "YOUR_RETENTION_EXCEPTIONS_LIST_ID"
            },
            {
              "id": "e-c05",
              "name": "teamsTeamId",
              "type": "string",
              "value": "YOUR_TEAMS_TEAM_ID"
            },
            {
              "id": "e-c06",
              "name": "teamsChannelId",
              "type": "string",
              "value": "YOUR_IT_SECURITY_CHANNEL_ID"
            },
            {
              "id": "e-c07",
              "name": "nowISO",
              "type": "string",
              "value": "={{ $now.toISO() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "6a57f230-87f6-4f1c-8e1e-f69dc44f8928",
      "name": "Daily Cron Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        9104,
        6112
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * *"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "d728f7ac-a36f-40fa-9c20-a8bbf378f695",
      "name": "Skip - Already Queued",
      "type": "n8n-nodes-base.noOp",
      "position": [
        10800,
        5632
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "76c30bc9-07fe-40e0-9540-737850e995f5",
      "name": "Queue Pending Deletion",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "position": [
        10800,
        5456
      ],
      "parameters": {
        "list": {
          "mode": "id",
          "value": "={{ $('Config').item.json.pendingDeletionsListId }}"
        },
        "site": {
          "mode": "id",
          "value": "={{ $('Config').item.json.sharepointSiteId }}"
        },
        "columns": {
          "value": {
            "Status": "Pending",
            "GuestId": "={{ $('Filter Stale Per Page').item.json.guestId }}",
            "GuestEmail": "={{ $('Filter Stale Per Page').item.json.guestEmail }}",
            "LastSignIn": "={{ $('Filter Stale Per Page').item.json.lastSignIn }}",
            "DisplayName": "={{ $('Filter Stale Per Page').item.json.displayName }}",
            "SponsorName": "={{ $('Filter Stale Per Page').item.json.sponsorName }}",
            "SponsorEmail": "={{ $('Filter Stale Per Page').item.json.sponsorEmail }}",
            "ScheduledDeletionTime": "={{ $('Config').item.json.scheduledDeletionTime }}"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": []
        },
        "resource": "item",
        "operation": "create",
        "requestOptions": {}
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "72424ac4-d49a-4e2c-86b3-ef643bf9ea99",
      "name": "Not Yet Queued?",
      "type": "n8n-nodes-base.if",
      "position": [
        10560,
        5536
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "already-queued",
              "operator": {
                "type": "number",
                "operation": "equal"
              },
              "leftValue": "={{ Array.isArray($json) ? $json.length : ($json.value || []).length }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "b5c9cc9c-4af7-43fd-af32-2a60bdc93ba9",
      "name": "Already Queued?",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "position": [
        10336,
        5536
      ],
      "parameters": {
        "list": {
          "mode": "id",
          "value": "={{ $('Config').item.json.pendingDeletionsListId }}"
        },
        "site": {
          "mode": "id",
          "value": "={{ $('Config').item.json.sharepointSiteId }}"
        },
        "limit": 1,
        "filter": "={{ \"fields/GuestEmail eq '\" + $('Filter Stale Per Page').item.json.guestEmailODataSafe + \"'\" }}",
        "options": {},
        "resource": "item",
        "simplify": false,
        "requestOptions": {}
      },
      "typeVersion": 1,
      "continueOnFail": true,
      "alwaysOutputData": true
    },
    {
      "id": "03d8c78e-6fc4-417c-aee7-a03fd5896570",
      "name": "Alert IT Security - No Sponsor",
      "type": "n8n-nodes-base.microsoftTeams",
      "position": [
        11456,
        5632
      ],
      "parameters": {
        "teamId": {
          "mode": "id",
          "value": "={{ $('Config').item.json.teamsTeamId }}"
        },
        "message": "=\u26a0\ufe0f **Stale Guest \u2014 No Manager Assigned**\n\n**Guest:** {{ $('Filter Stale Per Page').item.json.displayName }}\n**Email:** {{ $('Filter Stale Per Page').item.json.guestEmail }}\n**Last Sign-In:** {{ $('Filter Stale Per Page').item.json.lastSignIn }}\n**Scheduled Deletion:** {{ $('Config').item.json.scheduledDeletionTime }}\n\nNo manager/sponsor is assigned in Entra ID for this guest. The account is queued for automatic deletion. Please assign a sponsor or add to `GuestRetentionExceptions` to retain.\n\n_Automated message \u2014 Entra ID Governance_",
        "options": {},
        "resource": "channelMessage",
        "channelId": {
          "mode": "id",
          "value": "={{ $('Config').item.json.teamsChannelId }}"
        }
      },
      "typeVersion": 2,
      "continueOnFail": true
    },
    {
      "id": "7bf87662-5c05-47a2-8fb5-52c94c78995b",
      "name": "Notify Sponsor via Teams Channel",
      "type": "n8n-nodes-base.microsoftTeams",
      "position": [
        11456,
        5440
      ],
      "parameters": {
        "teamId": {
          "mode": "id",
          "value": "={{ $('Config').item.json.teamsTeamId }}"
        },
        "message": "=<at>{{{{ $('Filter Stale Per Page').item.json.sponsorName }}}}</at> \u26a0\ufe0f <strong>Stale B2B Guest Account \u2014 Action Required</strong><br><br>The following guest account has been <strong>inactive for >{{{{ $('Config').item.json.inactivityThresholdDays }}}} days</strong> and is scheduled for automatic deletion in <strong>{{{{ $('Config').item.json.responseWindowHours }}}} hours</strong>.<br><br><table><tr><td><strong>Guest</strong></td><td>{{{{ $('Filter Stale Per Page').item.json.displayName }}}}</td></tr><tr><td><strong>Email</strong></td><td>{{{{ $('Filter Stale Per Page').item.json.guestEmail }}}}</td></tr><tr><td><strong>Last Sign-In</strong></td><td>{{{{ $('Filter Stale Per Page').item.json.lastSignIn }}}}</td></tr><tr><td><strong>Deletion Scheduled</strong></td><td>{{{{ $('Config').item.json.scheduledDeletionTime }}}}</td></tr></table><br><strong>To retain this account:</strong> Add <code>{{{{ $('Filter Stale Per Page').item.json.guestEmail }}}}</code> to the <code>GuestRetentionExceptions</code> SharePoint list before the deadline.<br><br><em>Automated message \u2014 Entra ID Governance</em>",
        "options": {},
        "resource": "channelMessage",
        "channelId": {
          "mode": "id",
          "value": "={{ $('Config').item.json.teamsChannelId }}"
        },
        "contentType": "html"
      },
      "typeVersion": 2,
      "continueOnFail": true
    },
    {
      "id": "53e0a695-f02c-4bcc-847c-9512ef3532af",
      "name": "Valid Sponsor Email?",
      "type": "n8n-nodes-base.if",
      "position": [
        11232,
        5536
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "email-valid",
              "operator": {
                "type": "string",
                "operation": "contains"
              },
              "leftValue": "={{ $('Filter Stale Per Page').item.json.sponsorEmail }}",
              "rightValue": "@"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "f1ea5865-f3cb-4bbb-a756-0ae4910eb172",
      "name": "Filter Stale Per Page",
      "type": "n8n-nodes-base.code",
      "position": [
        9968,
        5536
      ],
      "parameters": {
        "jsCode": "// Built-in HTTP pagination means $input.item.json contains\n// the LAST page response, but n8n has already accumulated all items\n// via requestWithAuthenticationPaginated. The full guest array is in value[].\nconst response = $input.item.json;\nconst allUsers = response.value || [];\nconst cutoffDate = new Date($('Config').item.json.cutoffDate);\n\nreturn allUsers\n\u00a0 .filter(guest => {\n\u00a0 \u00a0 const signIn = guest.signInActivity;\n\u00a0 \u00a0 if (!signIn || !signIn.lastSignInDateTime) {\n\u00a0 \u00a0 \u00a0 return new Date(guest.createdDateTime || '2000-01-01') < cutoffDate;\n\u00a0 \u00a0 }\n\u00a0 \u00a0 return new Date(signIn.lastSignInDateTime) < cutoffDate;\n\u00a0 })\n\u00a0 .map(guest => {\n\u00a0 \u00a0 const manager = guest.manager || null;\n\u00a0 \u00a0 const rawEmail = guest.mail || guest.userPrincipalName || '';\n\u00a0 \u00a0 return {\n\u00a0 \u00a0 \u00a0 json: {\n\u00a0 \u00a0 \u00a0 \u00a0 guestId:\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0guest.id,\n\u00a0 \u00a0 \u00a0 \u00a0 guestEmail:\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 rawEmail,\n\u00a0 \u00a0 \u00a0 \u00a0 guestEmailODataSafe: rawEmail.replace(/'/g, \"''\"),\n\u00a0 \u00a0 \u00a0 \u00a0 displayName:\u00a0 \u00a0 \u00a0 \u00a0 \u00a0guest.displayName || 'Unknown',\n\u00a0 \u00a0 \u00a0 \u00a0 lastSignIn:\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 (guest.signInActivity && guest.signInActivity.lastSignInDateTime)\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0? guest.signInActivity.lastSignInDateTime\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0: 'Never',\n\u00a0 \u00a0 \u00a0 \u00a0 createdDateTime:\u00a0 \u00a0 \u00a0guest.createdDateTime,\n\u00a0 \u00a0 \u00a0 \u00a0 sponsorEmail:\u00a0 \u00a0 \u00a0 \u00a0 manager ? (manager.mail || '') : '',\n\u00a0 \u00a0 \u00a0 \u00a0 sponsorName:\u00a0 \u00a0 \u00a0 \u00a0 \u00a0manager ? (manager.displayName || '') : ''\n\u00a0 \u00a0 \u00a0 }\n\u00a0 \u00a0 };\n\u00a0 });"
      },
      "typeVersion": 2
    },
    {
      "id": "9749c3c0-0ee6-42d2-afb0-7ed10650495a",
      "name": "Get Guest Users",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "maxTries": 5,
      "position": [
        9744,
        5536
      ],
      "parameters": {
        "url": "https://graph.microsoft.com/v1.0/users?$filter=userType eq 'Guest'&$select=id,displayName,mail,userPrincipalName,createdDateTime,signInActivity&$expand=manager($select=id,displayName,mail)&$top=999",
        "options": {
          "pagination": {
            "pagination": {
              "nextURL": "={{ $response.body['@odata.nextLink'] }}",
              "paginationMode": "responseContainsNextURL",
              "completeExpression": "={{ !$response.body['@odata.nextLink'] }}",
              "paginationCompleteWhen": "other"
            }
          }
        },
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "oAuth2Api"
      },
      "retryOnFail": true,
      "typeVersion": 4.2,
      "continueOnFail": true,
      "waitBetweenTries": 3000
    },
    {
      "id": "0b4613f9-749b-410a-8a43-eb7ec75600eb",
      "name": "Config",
      "type": "n8n-nodes-base.set",
      "position": [
        9296,
        5536
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "s-c01",
              "name": "sharepointSiteId",
              "type": "string",
              "value": "YOUR_SHAREPOINT_SITE_ID"
            },
            {
              "id": "s-c02",
              "name": "pendingDeletionsListId",
              "type": "string",
              "value": "YOUR_PENDING_DELETIONS_LIST_ID"
            },
            {
              "id": "s-c03",
              "name": "teamsTeamId",
              "type": "string",
              "value": "YOUR_TEAMS_TEAM_ID"
            },
            {
              "id": "s-c04",
              "name": "teamsChannelId",
              "type": "string",
              "value": "YOUR_IT_SECURITY_CHANNEL_ID"
            },
            {
              "id": "s-c05",
              "name": "inactivityThresholdDays",
              "type": "number",
              "value": 90
            },
            {
              "id": "s-c06",
              "name": "responseWindowHours",
              "type": "number",
              "value": 72
            },
            {
              "id": "s-c07",
              "name": "cutoffDate",
              "type": "string",
              "value": "={{ $now.minus({days: 90}).toISO() }}"
            },
            {
              "id": "s-c08",
              "name": "scheduledDeletionTime",
              "type": "string",
              "value": "={{ $now.plus({hours: 72}).toISO() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "c28acf22-4647-44d5-afd8-eb67b07a3e13",
      "name": "Weekly Cron Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        9072,
        5536
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "9f4374ce-deb3-4245-9c91-6ac3e0cb53b7",
      "name": "SETUP INSTRUCTIONS",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        8272,
        5600
      ],
      "parameters": {
        "width": 649,
        "height": 623,
        "content": "## Entra ID Guest Account Governance: Automated Stale Account Detection, Sponsor Alerts, and Deletion\n\n### How it works\n\nThis workflow governs Microsoft Entra ID guest accounts using two scheduled paths. A weekly process finds stale guest users, checks whether they are already queued in SharePoint, queues new pending deletions, and alerts either the sponsor or IT Security in Teams. A daily process reviews overdue pending deletions, honors retention exceptions, deletes eligible guest accounts, and records success, retention, or errors back to SharePoint and Teams.\n\n### Setup steps\n\n- Configure Microsoft Graph, Microsoft Entra ID, Microsoft SharePoint, Microsoft Teams, and Microsoft Graph Security credentials with permissions to read users, delete guest accounts, read/write SharePoint lists, and post Teams messages.\n- Update the Config nodes with the correct SharePoint site ID, pending deletions list ID, audit log list ID, retention exceptions list ID, Teams team ID, and Teams channel ID.\n- Create or verify SharePoint lists for pending deletions, audit logs, and retention exceptions with fields matching the workflow expressions, such as guest user ID, sponsor email, due date, status, and error details.\n- Set the weekly and daily cron schedules to match the organization\u2019s governance cadence and review the stale-account threshold used in the filtering code.\n- Confirm Teams channels and sponsor-routing logic are appropriate before enabling account deletion in production.\n\n### Customization\n\nAdjust the stale-account age threshold, grace period before deletion, sponsor validation rules, Teams notification text, SharePoint list schema, and retention-exception criteria to match internal governance policy."
      },
      "typeVersion": 1
    },
    {
      "id": "9e411d48-8631-4302-8186-99896751d2b2",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        10272,
        7728
      ],
      "parameters": {
        "width": 480,
        "height": 896,
        "content": "## Entra ID Guest Account Governance: Automated Stale Account Detection, Sponsor Alerts, and Deletion\n\n### How it works\n\nThis workflow governs Microsoft Entra ID guest accounts using two scheduled paths. A weekly process finds stale guest users, checks whether they are already queued in SharePoint, queues new pending deletions, and alerts either the sponsor or IT Security in Teams. A daily process reviews overdue pending deletions, honors retention exceptions, deletes eligible guest accounts, and records success, retention, or errors back to SharePoint and Teams.\n\n### Setup steps\n\n- Configure Microsoft Graph, Microsoft Entra ID, Microsoft SharePoint, Microsoft Teams, and Microsoft Graph Security credentials with permissions to read users, delete guest accounts, read/write SharePoint lists, and post Teams messages.\n- Update the Config nodes with the correct SharePoint site ID, pending deletions list ID, audit log list ID, retention exceptions list ID, Teams team ID, and Teams channel ID.\n- Create or verify SharePoint lists for pending deletions, audit logs, and retention exceptions with fields matching the workflow expressions, such as guest user ID, sponsor email, due date, status, and error details.\n- Set the weekly and daily cron schedules to match the organization\u2019s governance cadence and review the stale-account threshold used in the filtering code.\n- Confirm Teams channels and sponsor-routing logic are appropriate before enabling account deletion in production.\n\n### Customization\n\nAdjust the stale-account age threshold, grace period before deletion, sponsor validation rules, Teams notification text, SharePoint list schema, and retention-exception criteria to match internal governance policy."
      },
      "typeVersion": 1
    },
    {
      "id": "b23151cc-24fc-477b-bfe3-d08aafcbb858",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        10832,
        8224
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 320,
        "content": "## Weekly scan setup\n\nStarts the weekly stale guest discovery run and loads configuration values for SharePoint and Teams destinations."
      },
      "typeVersion": 1
    },
    {
      "id": "83e08c1e-1c15-4f49-af7c-626daf604609",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        11504,
        8208
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 336,
        "content": "## Find stale guests\n\nRetrieves guest users from Microsoft Graph and filters each paginated response to identify stale accounts that should be evaluated for deletion."
      },
      "typeVersion": 1
    },
    {
      "id": "107a416e-f040-4d97-833f-7896f9521290",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        12096,
        8144
      ],
      "parameters": {
        "color": 7,
        "width": 656,
        "height": 496,
        "content": "## Queue new candidates\n\nChecks the SharePoint pending-deletions list to avoid duplicates, queues newly identified stale guests, and skips accounts that are already pending."
      },
      "typeVersion": 1
    },
    {
      "id": "368bfd75-57be-46ac-bfbd-0b4f0fc552e7",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        12992,
        8128
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 512,
        "content": "## Notify sponsor or security\n\nValidates whether a queued account has a usable sponsor email, then posts either a sponsor notification or an IT Security alert to Teams."
      },
      "typeVersion": 1
    },
    {
      "id": "56f2d950-fd4f-4e1f-b19e-845e4b1a4ffa",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        10864,
        8800
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 320,
        "content": "## Daily execution setup\n\nStarts the daily deletion execution run and loads configuration for pending deletions, audit logging, and retention exceptions."
      },
      "typeVersion": 1
    },
    {
      "id": "1f969072-af8f-49d6-a7b1-3d04737275a1",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        11376,
        8704
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 512,
        "content": "## Collect overdue deletions\n\nGets overdue pending-deletion records from SharePoint, checks whether any are due, splits due records for item-by-item processing, or ends when nothing is due."
      },
      "typeVersion": 1
    },
    {
      "id": "01cb3167-5b22-4720-914b-5cb9ab7ea8e0",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        12160,
        8816
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 320,
        "content": "## Check retention exceptions\n\nLooks up each overdue guest in the retention-exceptions list and branches based on whether deletion should be blocked."
      },
      "typeVersion": 1
    },
    {
      "id": "433d8565-47de-4331-82e1-453bac0d64d0",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        12832,
        8688
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 560,
        "content": "## Delete or retain account\n\nHandles the closely placed deletion and retention outcomes: eligible guests are deleted in Entra ID and checked for success, while exception-matched records are marked retained and logged to Teams."
      },
      "typeVersion": 1
    },
    {
      "id": "cb531026-55f5-477e-8850-9a1a98386a95",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        13680,
        8672
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 576,
        "content": "## Record deletion outcomes\n\nRecords successful deletions to the SharePoint audit list and marks pending records deleted, while failed deletions generate a Teams alert and mark the pending item as errored."
      },
      "typeVersion": 1
    },
    {
      "id": "133b460b-2900-493a-b035-f0ca674b6037",
      "name": "When Weekly Triggered at 8 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        10880,
        8384
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "83797387-c8a4-494c-bae0-07d502768ac7",
      "name": "Set Configuration Parameters",
      "type": "n8n-nodes-base.set",
      "position": [
        11104,
        8384
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "s-c01",
              "name": "sharepointSiteId",
              "type": "string",
              "value": "YOUR_SHAREPOINT_SITE_ID"
            },
            {
              "id": "s-c02",
              "name": "pendingDeletionsListId",
              "type": "string",
              "value": "YOUR_PENDING_DELETIONS_LIST_ID"
            },
            {
              "id": "s-c03",
              "name": "teamsTeamId",
              "type": "string",
              "value": "YOUR_TEAMS_TEAM_ID"
            },
            {
              "id": "s-c04",
              "name": "teamsChannelId",
              "type": "string",
              "value": "YOUR_IT_SECURITY_CHANNEL_ID"
            },
            {
              "id": "s-c05",
              "name": "inactivityThresholdDays",
              "type": "number",
              "value": 90
            },
            {
              "id": "s-c06",
              "name": "responseWindowHours",
              "type": "number",
              "value": 72
            },
            {
              "id": "s-c07",
              "name": "cutoffDate",
              "type": "string",
              "value": "={{ $now.minus({days: 90}).toISO() }}"
            },
            {
              "id": "s-c08",
              "name": "scheduledDeletionTime",
              "type": "string",
              "value": "={{ $now.plus({hours: 72}).toISO() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "4028109a-fc51-4522-817a-aaae5c124a39",
      "name": "Fetch Guest Users from Graph",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "maxTries": 5,
      "position": [
        11552,
        8384
      ],
      "parameters": {
        "url": "https://graph.microsoft.com/v1.0/users?$filter=userType eq 'Guest'&$select=id,displayName,mail,userPrincipalName,createdDateTime,signInActivity&$expand=manager($select=id,displayName,mail)&$top=999",
        "options": {
          "pagination": {
            "pagination": {
              "nextURL": "={{ $response.body['@odata.nextLink'] }}",
              "paginationMode": "responseContainsNextURL",
              "completeExpression": "={{ !$response.body['@odata.nextLink'] }}",
              "paginationCompleteWhen": "other"
            }
          }
        },
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "oAuth2Api"
      },
      "retryOnFail": true,
      "typeVersion": 4.2,
      "continueOnFail": true,
      "waitBetweenTries": 3000
    },
    {
      "id": "2f392b39-d790-4f28-b49f-b15759c56936",
      "name": "Filter Stale Accounts",
      "type": "n8n-nodes-base.code",
      "position": [
        11776,
        8384
      ],
      "parameters": {
        "jsCode": "// Built-in HTTP pagination means $input.item.json contains\n// the LAST page response, but n8n has already accumulated all items\n// via requestWithAuthenticationPaginated. The full guest array is in value[].\nconst response = $input.item.json;\nconst allUsers = response.value || [];\nconst cutoffDate = new Date($('Set Configuration Parameters').item.json.cutoffDate);\n\nreturn allUsers\n\u00a0 .filter(guest => {\n\u00a0 \u00a0 const signIn = guest.signInActivity;\n\u00a0 \u00a0 if (!signIn || !signIn.lastSignInDateTime) {\n\u00a0 \u00a0 \u00a0 return new Date(guest.createdDateTime || '2000-01-01') < cutoffDate;\n\u00a0 \u00a0 }\n\u00a0 \u00a0 return new Date(signIn.lastSignInDateTime) < cutoffDate;\n\u00a0 })\n\u00a0 .map(guest => {\n\u00a0 \u00a0 const manager = guest.manager || null;\n\u00a0 \u00a0 const rawEmail = guest.mail || guest.userPrincipalName || '';\n\u00a0 \u00a0 return {\n\u00a0 \u00a0 \u00a0 json: {\n\u00a0 \u00a0 \u00a0 \u00a0 guestId:\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0guest.id,\n\u00a0 \u00a0 \u00a0 \u00a0 guestEmail:\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 rawEmail,\n\u00a0 \u00a0 \u00a0 \u00a0 guestEmailODataSafe: rawEmail.replace(/'/g, \"''\"),\n\u00a0 \u00a0 \u00a0 \u00a0 displayName:\u00a0 \u00a0 \u00a0 \u00a0 \u00a0guest.displayName || 'Unknown',\n\u00a0 \u00a0 \u00a0 \u00a0 lastSignIn:\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 (guest.signInActivity && guest.signInActivity.lastSignInDateTime)\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0? guest.signInActivity.lastSignInDateTime\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0: 'Never',\n\u00a0 \u00a0 \u00a0 \u00a0 createdDateTime:\u00a0 \u00a0 \u00a0guest.createdDateTime,\n\u00a0 \u00a0 \u00a0 \u00a0 sponsorEmail:\u00a0 \u00a0 \u00a0 \u00a0 manager ? (manager.mail || '') : '',\n\u00a0 \u00a0 \u00a0 \u00a0 sponsorName:\u00a0 \u00a0 \u00a0 \u00a0 \u00a0manager ? (manager.displayName || '') : ''\n\u00a0 \u00a0 \u00a0 }\n\u00a0 \u00a0 };\n\u00a0 });"
      },
      "typeVersion": 2
    },
    {
      "id": "47a58031-9808-4447-ae3b-b5977c85773d",
      "name": "Check for Valid Sponsor Email",
      "type": "n8n-nodes-base.if",
      "position": [
        13040,
        8384
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "email-valid",
              "operator": {
                "type": "string",
                "operation": "contains"
              },
              "leftValue": "={{ $('Filter Stale Accounts').item.json.sponsorEmail }}",
              "rightValue": "@"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "adf943dd-a564-4ff9-a8ea-ac5e258859db",
      "name": "Notify Sponsor via Teams",
      "type": "n8n-nodes-base.microsoftTeams",
      "position": [
        13264,
        8288
      ],
      "parameters": {
        "teamId": {
          "mode": "id",
          "value": "={{ $('Set Configuration Parameters').item.json.teamsTeamId }}"
        },
        "message": "=<at>{{{{ $('Filter Stale Accounts').item.json.sponsorName }}}}</at> \u26a0\ufe0f <strong>Stale B2B Guest Account \u2014 Action Required</strong><br><br>The following guest account has been <strong>inactive for >{{{{ $('Set Configuration Parameters').item.json.inactivityThresholdDays }}}} days</strong> and is scheduled for automatic deletion in <strong>{{{{ $('Set Configuration Parameters').item.json.responseWindowHours }}}} hours</strong>.<br><br><table><tr><td><strong>Guest</strong></td><td>{{{{ $('Filter Stale Accounts').item.json.displayName }}}}</td></tr><tr><td><strong>Email</strong></td><td>{{{{ $('Filter Stale Accounts').item.json.guestEmail }}}}</td></tr><tr><td><strong>Last Sign-In</strong></td><td>{{{{ $('Filter Stale Accounts').item.json.lastSignIn }}}}</td></tr><tr><td><strong>Deletion Scheduled</strong></td><td>{{{{ $('Set Configuration Parameters').item.json.scheduledDeletionTime }}}}</td></tr></table><br><strong>To retain this account:</strong> Add <code>{{{{ $('Filter Stale Accounts').item.json.guestEmail }}}}</code> to the <code>GuestRetentionExceptions</code> SharePoint list before the deadline.<br><br><em>Automated message \u2014 Entra ID Governance</em>",
        "options": {},
        "resource": "channelMessage",
        "channelId": {
          "mode": "id",
          "value": "={{ $('Set Configuration Parameters').item.json.teamsChannelId }}"
        },
        "contentType": "html"
      },
      "typeVersion": 2,
      "continueOnFail": true
    },
    {
      "id": "33fe5f34-f7df-4f87-89d4-bc9bf8720aaa",
      "name": "Alert IT of Missing Sponsor",
      "type": "n8n-nodes-base.microsoftTeams",
      "position": [
        13264,
        8480
      ],
      "parameters": {
        "teamId": {
          "mode": "id",
          "value": "={{ $('Set Configuration Parameters').item.json.teamsTeamId }}"
        },
        "message": "=\u26a0\ufe0f **Stale Guest \u2014 No Manager Assigned**\n\n**Guest:** {{ $('Filter Stale Accounts').item.json.displayName }}\n**Email:** {{ $('Filter Stale Accounts').item.json.guestEmail }}\n**Last Sign-In:** {{ $('Filter Stale Accounts').item.json.lastSignIn }}\n**Scheduled Deletion:** {{ $('Set Configuration Parameters').item.json.scheduledDeletionTime }}\n\nNo manager/sponsor is assigned in Entra ID for this guest. The account is queued for automatic deletion. Please assign a sponsor or add to `GuestRetentionExceptions` to retain.\n\n_Automated message \u2014 Entra ID Governance_",
        "options": {},
        "resource": "channelMessage",
        "channelId": {
          "mode": "id",
          "value": "={{ $('Set Configuration Parameters').item.json.teamsChannelId }}"
        }
      },
      "typeVersion": 2,
      "continueOnFail": true
    },
    {
      "id": "a778dd07-19aa-4ce8-8938-948a55a69cb2",
      "name": "Check if Already Queued",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "position": [
        12144,
        8384
      ],
      "parameters": {
        "list": {
          "mode": "id",
          "value": "={{ $('Set Configuration Parameters').item.json.pendingDeletionsListId }}"
        },
        "site": {
          "mode": "id",
          "value": "={{ $('Set Configuration Parameters').item.json.sharepointSiteId }}"
        },
        "limit": 1,
        "filter": "={{ \"fields/GuestEmail eq '\" + $('Filter Stale Accounts').item.json.guestEmailODataSafe + \"'\" }}",
        "options": {},
        "resource": "item",
        "simplify": false,
        "requestOptions": {}
      },
      "typeVersion": 1,
      "continueOnFail": true,
      "alwaysOutputData": true
    },
    {
      "id": "67803789-388d-4396-9aad-83a8d781f819",
      "name": "Check Not Yet Queued",
      "type": "n8n-nodes-base.if",
      "position": [
        12368,
        8384
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "already-queued",
              "operator": {
                "type": "number",
                "operation": "equal"
              },
              "leftValue": "={{ Array.isArray($json) ? $json.length : ($json.value || []).length }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "505fd1c7-d754-4944-a13d-fac94c47cb5c",
      "name": "Queue for Pending Deletion",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "position": [
        12608,
        8304
      ],
      "parameters": {
        "list": {
          "mode": "id",
          "value": "={{ $('Set Configuration Parameters').item.json.pendingDeletionsListId }}"
        },
        "site": {
          "mode": "id",
          "value": "={{ $('Set Configuration Parameters').item.json.sharepointSiteId }}"
        },
        "columns": {
          "value": {
            "Status": "Pending",
            "GuestId": "={{ $('Filter Stale Accounts').item.json.guestId }}",
            "GuestEmail": "={{ $('Filter Stale Accounts').item.json.guestEmail }}",
            "LastSignIn": "={{ $('Filter Stale Accounts').item.json.lastSignIn }}",
            "DisplayName": "={{ $('Filter Stale Accounts').item.json.displayName }}",
            "SponsorName": "={{ $('Filter Stale Accounts').item.json.sponsorName }}",
            "SponsorEmail": "={{ $('Filter Stale Accounts').item.json.sponsorEmail }}",
            "ScheduledDeletionTime": "={{ $('Set Configuration Parameters').item.json.scheduledDeletionTime }}"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": []
        },
        "resource": "item",
        "operation": "create",
        "requestOptions": {}
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "a86d5913-080c-4f11-ad4e-f04b6cb8434b",
      "name": "Skip This - Already Queued",
      "type": "n8n-nodes-base.noOp",
      "position": [
        12608,
        8480
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "3772875e-f758-42f8-bfca-5853931ea322",
      "name": "When Daily Triggered at 9 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        10912,
        8960
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * *"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "48d3fe43-04c8-4e70-b7ca-86cfe0988f9c",
      "name": "Set Execution Parameters",
      "type": "n8n-nodes-base.set",
      "position": [
        11136,
        8960
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "e-c01",
              "name": "sharepointSiteId",
              "type": "string",
              "value": "YOUR_SHAREPOINT_SITE_ID"
            },
            {
              "id": "e-c02",
              "name": "pendingDeletionsListId",
              "type": "string",
              "value": "YOUR_PENDING_DELETIONS_LIST_ID"
            },
            {
              "id": "e-c03",
              "name": "auditLogListId",
              "type": "string",
              "value": "YOUR_GUEST_AUDIT_LIST_ID"
            },
            {
              "id": "e-c04",
              "name": "retentionExceptionsListId",
              "type": "string",
              "value": "YOUR_RETENTION_EXCEPTIONS_LIST_ID"
            },
            {
              "id": "e-c05",
              "name": "teamsTeamId",
              "type": "string",
              "value": "YOUR_TEAMS_TEAM_ID"
            },
            {
              "id": "e-c06",
              "name": "teamsChannelId",
              "type": "string",
              "value": "YOUR_IT_SECURITY_CHANNEL_ID"
            },
            {
              "id": "e-c07",
              "name": "nowISO",
              "type": "string",
              "value": "={{ $now.toISO() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "5c89a95b-3586-46da-a870-e740fab79f33",
      "name": "Fetch Overdue Pending Deletions",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "position": [
        11424,
        8960
      ],
      "parameters": {
        "list": {
          "mode": "id",
          "value": "={{ $json.pendingDeletionsListId }}"
        },
        "site": {
          "mode": "id",
          "value": "={{ $json.sharepointSiteId }}"
        },
        "filter": "={{ \"fields/Status eq 'Pending' and fields/ScheduledDeletionTime le '\" + $json.nowISO + \"'\" }}",
        "options": {},
        "resource": "item",
        "simplify": false,
        "returnAll": true,
        "requestOptions": {}
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "0571e2d8-35fb-4fe9-973b-2144b3b4a99f",
      "name": "Check Due for Deletion",
      "type": "n8n-nodes-base.if",
      "position": [
        11648,
        8960
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "has-work",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ Array.isArray($json) ? $json.length : ($json.value || []).length }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "6d0bf509-da14-43fa-9929-0ec6cee84678",
      "name": "No Deletions Due Today",
      "type": "n8n-nodes-base.noOp",
      "position": [
        11872,
        9056
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "61d17bdd-3537-472f-949a-2560e5b24486",
      "name": "Verify Retention Exception",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "position": [
        12208,
        8976
      ],
      "parameters": {
        "list": {
          "mode": "id",
          "value": "={{ $('Set Configuration Parameters').item.json.retentionExceptionsListId }}"
        },
        "site": {
          "mode": "id",
          "value": "={{ $('Set Configuration Parameters').item.json.sharepointSiteId }}"
        },
        "limit": 1,
        "filter": "={{ \"fields/GuestEmail eq '\" + $json.guestEmailODataSafe + \"'\" }}",
        "options": {},
        "resource": "item",
        "simplify": false,
        "requestOptions": {}
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "51a77122-c730-4b2c-ab20-fdcf115f0f84",
      "name": "Is Retention Exception Present?",
      "type": "n8n-nodes-base.if",
      "position": [
        12432,
        8976
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "ret-check",
              "operator": {
                "type": "number",
                "operation": "equal"
              },
              "leftValue": "={{ Array.isArray($json) ? $json.length : ($json.value || []).length }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "da1e1bed-2fc7-4e04-b14b-8ee351091b05",
      "name": "Remove Stale Guest Account",
      "type": "n8n-nodes-base.microsoftEntra",
      "position": [
        12880,
        8896
      ],
      "parameters": {
        "user": {
          "mode": "id",
          "value": "={{ $('Distribute Pending Items').item.json.guestId }}"
        },
        "operation": "delete",
        "requestOptions": {}
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "f194c413-b31f-458f-9ecc-153d9d304d73",
      "name": "Verify Deletion Success",
      "type": "n8n-nodes-base.if",
      "position": [
        13104,
        8896
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "del-ok",
              "operator": {
                "type": "boolean",
                "operation": "equal"
              },
              "leftValue": "={{ $json.deleted }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "799abced-4332-4b0c-982b-5ef14a49e279",
      "name": "Record Deletion in Audit Log",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "position": [
        13728,
        8848
      ],
      "parameters": {
        "list": {
          "mode": "id",
          "value": "={{ $('Set Execution Parameters').item.json.auditLogListId }}"
        },
        "site": {
          "mode": "id",
          "value": "={{ $('Set Execution Parameters').item.json.sharepointSiteId }}"
        },
        "columns": {
          "value": {
            "DeletedAt": "={{ $now.toISO() }}",
            "GuestEmail": "={{ $('Distribute Pending Items').item.json.guestEmail }}",
            "LastSignIn": "={{ $('Distribute Pending Items').item.json.lastSignIn }}",
            "DisplayName": "={{ $('Distribute Pending Items').item.json.displayName }}",
            "SponsorNotified": "={{ $('Distribute Pending Items').item.json.sponsorEmail }}"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": []
        },
        "resource": "item",
        "operation": "create",
        "requestOptions": {}
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "4a9f15df-2c83-491a-a8d0-09e53e46a526",
      "name": "Update Status to Deleted",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "position": [
        14000,
        8848
      ],
      "parameters": {
        "list": {
          "mode": "id",
          "value": "={{ $('Set Execution Parameters').item.json.pendingDeletionsListId }}"
        },
        "site": {
          "mode": "id",
          "value": "={{ $('Set Execution Parameters').item.json.sharepointSiteId }}"
        },
        "columns": {
          "value": {
            "id": "={{ $('Distribute Pending Items').item.json.listItemId }}",
            "Status": "Deleted",
            "DeletedAt": "={{ $now.toISO() }}"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ]
        },
        "resource": "item",
        "operation": "update",
        "requestOptions": {}
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "20cb88ae-f9a2-4f8f-b0dd-5d6f9aad9148",
      "name": "Notify Delete Error via Teams",
      "type": "n8n-nodes-base.microsoftTeams",
      "position": [
        13744,
        9088
      ],
      "parameters": {
        "teamId": "={{ $('Set Execution Parameters').item.json.teamsTeamId }}",
        "message": "=\u274c **Guest Deletion Error \u2014 Manual Action Required**\n\n**Guest:** {{ $('Distribute Pending Items').item.json.guestEmail }}\n**Display Name:** {{ $('Distribute Pending Items').item.json.displayName }}\n**Error:** {{ $json.error ? $json.error.message : 'Delete API call failed' }}\n**Time:** {{ $now.toISO() }}\n\nPlease delete this account manually in the Azure AD portal.",
        "options": {},
        "resource": "channelMessage",
        "channelId": "={{ $('Set Execution Parameters').item.json.teamsChannelId }}"
      },
      "typeVersion": 2,
      "continueOnFail": true
    },
    {
      "id": "24e2c3b8-b3fe-4180-9d1f-da28c66ce8e8",
      "name": "Update Status to Retained",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "position": [
        12880,
        9088
      ],
      "parameters": {
        "list": {
          "mode": "id",
          "value": "={{ $('Set Execution Parameters').item.json.pendingDeletionsListId }}"
        },
        "site": {
          "mode": "id",
          "value": "={{ $('Set Execution Parameters').item.json.sharepointSiteId }}"
        },
        "columns": {
          "value": {
            "id": "={{ $('Distribute Pending Items').item.json.listItemId }}",
            "Status": "Retained",
            "RetainedAt": "={{ $now.toISO() }}"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ]
        },
        "resource": "item",
        "operation": "update",
        "requestOptions": {}
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "03859493-5958-4f16-8eb9-80f495cbf32e",
      "name": "Notify Retention via Teams",
      "type": "n8n-nodes-base.microsoftTeams",
      "position": [
        13104,
        9088
      ],
      "parameters": {
        "teamId": "={{ $('Set Execution Parameters').item.json.teamsTeamId }}",
        "message": "=\u2139\ufe0f **Guest Retention Exception Applied**\n\n**Guest:** {{ $('Distribute Pending Items').item.json.displayName }}\n**Email:** {{ $('Distribute Pending Items').item.json.guestEmail }}\n**Sponsor:** {{ $('Distribute Pending Items').item.json.sponsorName }}\n**Time:** {{ $now.toISO() }}\n\nRetention exception found. Deletion skipped and record marked Retained.",
        "options": {},
        "resource": "channelMessage",
        "channelId": "={{ $('Set Execution Parameters').item.json.teamsChannelId }}"
      },
      "typeVersion": 2,
      "continueOnFail": true
    },
    {
      "id": "67e5b9e0-cace-434b-a158-dba890376eb0",
      "name": "Update Status to Error",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "position": [
        14016,
        9088
      ],
      "parameters": {
        "list": {
          "mode": "id",
          "value": "={{ $('Set Execution Parameters').item.json.pendingDeletionsListId }}"
        },
        "site": {
          "mode": "id",
          "value": "={{ $('Set Execution Parameters').item.json.sharepointSiteId }}"
        },
        "columns": {
          "value": {
            "id": "={{ $('Distribute Pending Items').item.json.listItemId }}",
            "Status": "Error",
            "ErrorAt": "={{ $now.toISO() }}",
            "ErrorDetails": "Manual Action Required \u2014 automated deletion failed. Check Azure AD portal."
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ]
        },
        "resource": "item",
        "operation": "update",
        "requestOptions": {}
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "b08f1343-924d-4984-b526-273aa1ad9200",
      "name": "Distribute Pending Items",
      "type": "n8n-nodes-base.code",
      "position": [
        11872,
        8864
      ],
      "parameters": {
        "jsCode": "// Native microsoftSharePoint getAll returns items as array directly\n// Each item has: id (list item ID) and the field values at top level\n// (the node flattens fields for us \u2014 no .fields wrapper needed)\nconst raw = $input.item.json;\nconst items = Array.isArray(raw) ? raw : (raw.value || []);\n\nreturn items.map(item => ({\n\u00a0 json: {\n\u00a0 \u00a0 listItemId: item.id,\n\u00a0 \u00a0 guestId: item.GuestId || item.fields?.GuestId,\n\u00a0 \u00a0 guestEmail: item.GuestEmail || item.fields?.GuestEmail || '',\n\u00a0 \u00a0 guestEmailODataSafe: (item.GuestEmail || item.fields?.GuestEmail || '').replace(/'/g, \"''\"),\n\u00a0 \u00a0 displayName: item.DisplayName || item.fields?.DisplayName,\n\u00a0 \u00a0 lastSignIn: item.LastSignIn || item.fields?.LastSignIn,\n\u00a0 \u00a0 sponsorEmail: item.SponsorEmail || item.fields?.SponsorEmail,\n\u00a0 \u00a0 sponsorName: item.SponsorName || item.fields?.SponsorName,\n\u00a0 \u00a0 scheduledDeletionTime: item.ScheduledDeletionTime || item.fields?.ScheduledDeletionTime\n\u00a0 }\n}));"
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "f931fea5-91c5-4899-b12e-3a3fae2d097d",
  "connections": {
    "Config": {
      "main": [
        [
          {
            "node": "Get Guest Users",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Already Queued?": {
      "main": [
        [
          {
            "node": "Not Yet Queued?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Guest Users": {
      "main": [
        [
          {
            "node": "Filter Stale Per Page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Not Yet Queued?": {
      "main": [
        [
          {
            "node": "Queue Pending Deletion",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Skip - Already Queued",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delete Succeeded?": {
      "main": [
        [
          {
            "node": "Log Deletion to Audit List",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Delete Error Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Daily Cron Trigger": {
      "main": [
        [
          {
            "node": "Config (Executioner)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delete Error Alert": {
      "main": [
        [
          {
            "node": "Mark Pending as Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Pending Items": {
      "main": [
        [
          {
            "node": "Check Retention Exception",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Weekly Cron Trigger": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Not Yet Queued": {
      "main": [
        [
          {
            "node": "Queue for Pending Deletion",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Skip This - Already Queued",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config (Executioner)": {
      "main": [
        [
          {
            "node": "Get Overdue Pending Deletions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Retention Exception?": {
      "main": [
        [
          {
            "node": "Delete Stale Guest Account",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Mark Pending as Retained",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Valid Sponsor Email?": {
      "main": [
        [
          {
            "node": "Notify Sponsor via Teams Channel",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Alert IT Security - No Sponsor",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Any Due For Deletion?": {
      "main": [
        [
          {
            "node": "Split Pending Items",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Nothing Due Today",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Stale Accounts": {
      "main": [
        [
          {
            "node": "Check if Already Queued",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Stale Per Page": {
      "main": [
        [
          {
            "node": "Already Queued?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Due for Deletion": {
      "main": [
        [
          {
            "node": "Distribute Pending Items",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Deletions Due Today",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Queue Pending Deletion": {
      "main": [
        [
          {
            "node": "Valid Sponsor Email?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if Already Queued": {
      "main": [
        [
          {
            "node": "Check Not Yet Queued",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify Deletion Success": {
      "main": [
        [
          {
            "node": "Record Deletion in Audit Log",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Notify Delete Error via Teams",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Distribute Pending Items": {
      "main": [
        [
          {
            "node": "Verify Retention Exception",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Pending as Retained": {
      "main": [
        [
          {
            "node": "Log Retention to Teams",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Execution Parameters": {
      "main": [
        [
          {
            "node": "Fetch Overdue Pending Deletions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Retention Exception": {
      "main": [
        [
          {
            "node": "Retention Exception?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Status to Retained": {
      "main": [
        [
          {
            "node": "Notify Retention via Teams",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delete Stale Guest Account": {
      "main": [
        [
          {
            "node": "Delete Succeeded?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Deletion to Audit List": {
      "main": [
        [
          {
            "node": "Mark Pending as Deleted",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Queue for Pending Deletion": {
      "main": [
        [
          {
            "node": "Check for Valid Sponsor Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remove Stale Guest Account": {
      "main": [
        [
          {
            "node": "Verify Deletion Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify Retention Exception": {
      "main": [
        [
          {
            "node": "Is Retention Exception Present?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Guest Users from Graph": {
      "main": [
        [
          {
            "node": "Filter Stale Accounts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Record Deletion in Audit Log": {
      "main": [
        [
          {
            "node": "Update Status to Deleted",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Configuration Parameters": {
      "main": [
        [
          {
            "node": "Fetch Guest Users from Graph",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Daily Triggered at 9 AM": {
      "main": [
        [
          {
            "node": "Set Execution Parameters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check for Valid Sponsor Email": {
      "main": [
        [
          {
            "node": "Notify Sponsor via Teams",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Alert IT of Missing Sponsor",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Overdue Pending Deletions": {
      "main": [
        [
          {
            "node": "Any Due For Deletion?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notify Delete Error via Teams": {
      "main": [
        [
          {
            "node": "Update Status to Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Weekly Triggered at 8 AM": {
      "main": [
        [
          {
            "node": "Set Configuration Parameters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Overdue Pending Deletions": {
      "main": [
        [
          {
            "node": "Check Due for Deletion",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Retention Exception Present?": {
      "main": [
        [
          {
            "node": "Remove Stale Guest Account",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Update Status to Retained",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}