AutomationFlowsWeb Scraping › Email Reports on Expiring Microsoft Entra ID App Secrets and Certificates…

Email Reports on Expiring Microsoft Entra ID App Secrets and Certificates…

Original n8n title: Email Reports on Expiring Microsoft Entra ID App Secrets and Certificates with Microsoft Graph

ByAlexander Schnabl @alexschnabl on n8n.io

Stay ahead of credential expirations by automatically detecting Entra ID application client secrets and certificates that are about to expire, and sending a neatly formatted email report.

Event trigger★★★★☆ complexity22 nodesHTTP RequestEmail Send
Web Scraping Trigger: Event Nodes: 22 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #12399 — we link there as the canonical source.

This workflow follows the Emailsend → HTTP Request recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "aK1bY1cjRKpQNAqu",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Monitor expiring EntraID application secrets and notify responsible",
  "tags": [
    {
      "id": "0FRymMOqi6UCCYTI",
      "name": "free",
      "createdAt": "2025-12-25T11:06:36.527Z",
      "updatedAt": "2025-12-25T11:06:36.527Z"
    },
    {
      "id": "ks1tninGT7ZLrbrc",
      "name": "template",
      "createdAt": "2025-12-31T15:16:19.399Z",
      "updatedAt": "2025-12-31T15:16:19.399Z"
    }
  ],
  "nodes": [
    {
      "id": "c032ba4b-6ad9-4aaf-9336-7023d25710be",
      "name": "When clicking \u2018Execute workflow\u2019",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -208,
        16
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "f6886b67-bdcd-42fa-8b28-38d232fa91ae",
      "name": "Split Out Applications",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        464,
        16
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "value"
      },
      "typeVersion": 1
    },
    {
      "id": "0648ee8a-90d8-42f0-a79e-29ec3d37f2c9",
      "name": "Set Variables",
      "type": "n8n-nodes-base.set",
      "position": [
        16,
        16
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a5f4cc7c-7377-4370-a610-736ed8267eed",
              "name": "notificationEmail",
              "type": "string",
              "value": "user@example.com"
            },
            {
              "id": "cd0be702-2a6a-463a-99c6-1f093ef79a07",
              "name": "daysBeforeExpiry",
              "type": "number",
              "value": 14
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "e9617bcf-8764-4f8f-9f7d-37f3be8a38d3",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        1360,
        16
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "1239f798-30b5-4da3-a2eb-cee7f25a676e",
      "name": "Filter Client Secrets",
      "type": "n8n-nodes-base.filter",
      "position": [
        912,
        -80
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "5b96c25e-f8f8-4ebf-8aa5-2b430e36b16f",
              "operator": {
                "type": "dateTime",
                "operation": "beforeOrEquals"
              },
              "leftValue": "={{ $json.endDateTime }}",
              "rightValue": "={{ new Date(Date.now() + $('Set Variables').first().json.daysBeforeExpiry * 24 * 60 * 60 * 1000) }}"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "d9f6dfd9-07b3-4ff5-bef3-1501a27f26eb",
      "name": "Split Out Client Secrets",
      "type": "n8n-nodes-base.splitOut",
      "notes": "Password Credentials are Client secrets in the EntraID UI",
      "position": [
        688,
        -80
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "passwordCredentials"
      },
      "notesInFlow": false,
      "typeVersion": 1
    },
    {
      "id": "02b33d87-3e06-4c10-afec-584b826645c1",
      "name": "Split Out Certificates",
      "type": "n8n-nodes-base.splitOut",
      "notes": "Key Credentials are Certificates in the EntraID UI",
      "position": [
        688,
        112
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "keyCredentials"
      },
      "notesInFlow": false,
      "typeVersion": 1
    },
    {
      "id": "e5e91f10-cbeb-44b9-a89a-a4778afdcdf6",
      "name": "Filter Client Certificates",
      "type": "n8n-nodes-base.filter",
      "position": [
        912,
        112
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "5b96c25e-f8f8-4ebf-8aa5-2b430e36b16f",
              "operator": {
                "type": "dateTime",
                "operation": "beforeOrEquals"
              },
              "leftValue": "={{ $json.endDateTime }}",
              "rightValue": "={{ new Date(Date.now() + $('Set Variables').first().json.daysBeforeExpiry * 24 * 60 * 60 * 1000) }}"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "219314a0-9ce8-4352-b6be-3ffb92f52362",
      "name": "Build Client Secrets Report",
      "type": "n8n-nodes-base.set",
      "position": [
        1136,
        -80
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "b046951a-7b15-4ad3-ad6c-0bec7b2622b3",
              "name": "applicationName",
              "type": "string",
              "value": "={{ $('Split Out Applications').item.json.displayName }}"
            },
            {
              "id": "3fbb2382-35f5-4e91-82b0-89c03434195d",
              "name": "appId",
              "type": "string",
              "value": "={{ $('Split Out Applications').item.json.appId }}"
            },
            {
              "id": "98798f0e-3102-47cd-9bf6-6e6ae7192363",
              "name": "type",
              "type": "string",
              "value": "Client Secret"
            },
            {
              "id": "9b8ac2ee-9dbe-437c-92d5-3933010d5a8e",
              "name": "clientSecretName",
              "type": "string",
              "value": "={{ $json.displayName }}"
            },
            {
              "id": "5260e16d-0c50-4353-a2f5-c39200a79350",
              "name": "clientSecretId",
              "type": "string",
              "value": "={{ $json.keyId }}"
            },
            {
              "id": "192eaab0-67a9-4a22-aa2d-0c16ff6c0ecb",
              "name": "expiresInDays",
              "type": "number",
              "value": "={{ Math.max(0, Math.floor((Date.parse($json.endDateTime) - Date.now()) / (1000 * 60 * 60 * 24))) }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "b603c3e5-0027-48b3-8ca7-fcd82d133657",
      "name": "Build Certificates Report",
      "type": "n8n-nodes-base.set",
      "position": [
        1136,
        112
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "b046951a-7b15-4ad3-ad6c-0bec7b2622b3",
              "name": "applicationName",
              "type": "string",
              "value": "={{ $('Split Out Applications').item.json.displayName }}"
            },
            {
              "id": "3fbb2382-35f5-4e91-82b0-89c03434195d",
              "name": "appId",
              "type": "string",
              "value": "={{ $('Split Out Applications').item.json.appId }}"
            },
            {
              "id": "98798f0e-3102-47cd-9bf6-6e6ae7192363",
              "name": "type",
              "type": "string",
              "value": "Certificate"
            },
            {
              "id": "9b8ac2ee-9dbe-437c-92d5-3933010d5a8e",
              "name": "certificateName",
              "type": "string",
              "value": "={{ $json.displayName }}"
            },
            {
              "id": "5260e16d-0c50-4353-a2f5-c39200a79350",
              "name": "certificateId",
              "type": "string",
              "value": "={{ $json.keyId }}"
            },
            {
              "id": "192eaab0-67a9-4a22-aa2d-0c16ff6c0ecb",
              "name": "expiresInDays",
              "type": "number",
              "value": "={{ Math.max(0, Math.floor((Date.parse($json.endDateTime) - Date.now()) / (1000 * 60 * 60 * 24))) }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "d6b1957d-2216-414a-94cf-e1da38d3f517",
      "name": "Get EntraID Applications and Secrets",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        240,
        16
      ],
      "parameters": {
        "url": "https://graph.microsoft.com/v1.0/applications?$select=id,appId,displayName,passwordCredentials,keyCredentials",
        "options": {},
        "authentication": "genericCredentialType",
        "genericAuthType": "oAuth2Api"
      },
      "credentials": {
        "oAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "6e3ff7b9-174a-44fd-8f94-c5a262b0894b",
      "name": "Aggregate",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        1584,
        16
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "expiringSecrets"
      },
      "typeVersion": 1
    },
    {
      "id": "8d7fe566-f4ce-4c15-a45b-4c7171398bfe",
      "name": "If Expiring Secrets not empty",
      "type": "n8n-nodes-base.if",
      "position": [
        1792,
        16
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "2d188564-2edc-4525-a373-31087dcba601",
              "operator": {
                "type": "array",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.expiringSecrets }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "073c5d14-3bbe-427e-a6eb-7343071ef45a",
      "name": "No Operation, do nothing",
      "type": "n8n-nodes-base.noOp",
      "position": [
        2016,
        112
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "11d6a960-44de-44bf-8002-2f001e3d6b19",
      "name": "HTML Table with Expiring Secrets",
      "type": "n8n-nodes-base.html",
      "position": [
        2016,
        -80
      ],
      "parameters": {
        "html": "<h2 style=\"font-family: Arial, Helvetica, sans-serif; margin: 0 0 12px 0;\">Applications with expiring EntraID secrets</h2>\n\n<table style=\"border-collapse: collapse; width: 100%; background: #ffffff; font-family: Arial, Helvetica, sans-serif;\">\n  <thead>\n    <tr>\n      <th style=\"border: 1px solid #ddd; padding: 10px 12px; text-align: left; background: #f0f2f5; font-weight: 600;\">Application Name</th>\n      <th style=\"border: 1px solid #ddd; padding: 10px 12px; text-align: left; background: #f0f2f5; font-weight: 600;\">App ID</th>\n      <th style=\"border: 1px solid #ddd; padding: 10px 12px; text-align: left; background: #f0f2f5; font-weight: 600;\">Type</th>\n      <th style=\"border: 1px solid #ddd; padding: 10px 12px; text-align: left; background: #f0f2f5; font-weight: 600;\">Secret / Certificate Name</th>\n      <th style=\"border: 1px solid #ddd; padding: 10px 12px; text-align: left; background: #f0f2f5; font-weight: 600;\">Secret / Certificate ID</th>\n      <th style=\"border: 1px solid #ddd; padding: 10px 12px; text-align: left; background: #f0f2f5; font-weight: 600;\">Expires In (Days)</th>\n    </tr>\n  </thead>\n\n  <tbody>\n    {{\n      ($json.expiringSecrets || [])\n        .slice() // avoid mutating original data\n        .sort((a, b) =>\n          (a.applicationName || \"\").localeCompare(b.applicationName || \"\", undefined, { sensitivity: \"base\" })\n        )\n        .map((item, idx) => {\n          const name = item.clientSecretName || item.certificateName || \"\";\n          const id = item.clientSecretId || item.certificateId || \"\";\n          const days = Number(item.expiresInDays ?? \"\");\n\n          const rowBg = (idx % 2 === 1) ? \"background:#fafafa;\" : \"\";\n          const expiresStyle =\n            (days <= 30) ? \"color:#b91c1c;font-weight:600;\" :\n            (days <= 90) ? \"color:#b45309;font-weight:600;\" :\n            \"\";\n\n          return `\n            <tr style=\"${rowBg}\">\n              <td style=\"border: 1px solid #ddd; padding: 10px 12px;\">${item.applicationName ?? \"\"}</td>\n              <td style=\"border: 1px solid #ddd; padding: 10px 12px;\">${item.appId ?? \"\"}</td>\n              <td style=\"border: 1px solid #ddd; padding: 10px 12px;\">${item.type ?? \"\"}</td>\n              <td style=\"border: 1px solid #ddd; padding: 10px 12px;\">${name}</td>\n              <td style=\"border: 1px solid #ddd; padding: 10px 12px;\">${id}</td>\n              <td style=\"border: 1px solid #ddd; padding: 10px 12px; ${expiresStyle}\">${item.expiresInDays ?? \"\"}</td>\n            </tr>\n          `;\n        })\n        .join(\"\")\n    }}\n  </tbody>\n</table>\n"
      },
      "typeVersion": 1.2
    },
    {
      "id": "87ad3402-890e-4356-bff0-5a121cf6dcc2",
      "name": "Send email",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        2224,
        -80
      ],
      "parameters": {
        "html": "={{ $json.html }}",
        "options": {},
        "subject": "Applications with expiring EntraID secrets",
        "toEmail": "={{ $('Set Variables').item.json.notificationEmail }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "86d429d5-168c-4f08-bec5-09a24fc5bde9",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -880,
        -128
      ],
      "parameters": {
        "width": 608,
        "height": 368,
        "content": "## How it works\n1. Fetches all Entra ID applications and their credential metadata via Microsoft Graph  \n2. Separates client secrets and certificates into individual entries  \n3. Filters entries that expire within the configured time window  \n4. Builds a normalized list of expiring items with days remaining  \n5. Emails an HTML table report (only if results exist)\n\n## Setup steps\n- Microsoft Entra ID Application with permission:\n  - `Application.Read.All`\n  - Microsoft Graph OAuth2 credentials configured in n8n\n  - Assign the credential to the Get EntraID Applications HTTP Request node\n- Adjust the values of the Set Variables node\n- Configure the Send EMail node to send the email with the report"
      },
      "typeVersion": 1
    },
    {
      "id": "9f166c9c-8eaa-45e5-b647-418b6ea0c946",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        624,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 672,
        "height": 432,
        "content": "## Filter expiring secrets"
      },
      "typeVersion": 1
    },
    {
      "id": "c38ec327-7ead-4c78-93ba-8a02cfeca86b",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1568,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 832,
        "height": 432,
        "content": "## Report\nBuild the report as an HTML table and send it via mail"
      },
      "typeVersion": 1
    },
    {
      "id": "34dba612-3b2d-4342-9897-760dfb651c3f",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -48,
        -48
      ],
      "parameters": {
        "color": 7,
        "width": 224,
        "height": 224,
        "content": "## Variables"
      },
      "typeVersion": 1
    },
    {
      "id": "d682d2a0-d5b6-498a-996f-c2a6fc3ab0e5",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        176,
        -64
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 272,
        "content": "## Get EntraID Applications"
      },
      "typeVersion": 1
    },
    {
      "id": "35e3d142-ad5b-4554-a76a-3aa6f6f21aa4",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -880,
        256
      ],
      "parameters": {
        "color": 6,
        "width": 608,
        "height": 144,
        "content": "### Notes\n- Requires EntraID Application\n- Use Client Credentials when adding the OAuth2 credentials in n8n\n- Use a schedule trigger to automatically run this\n- Need help? \u2709\ufe0f **office@sus-tech.at**"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "55b402a4-c0b9-46fd-9887-5b07a7491b6d",
  "connections": {
    "Merge": {
      "main": [
        [
          {
            "node": "Aggregate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate": {
      "main": [
        [
          {
            "node": "If Expiring Secrets not empty",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Variables": {
      "main": [
        [
          {
            "node": "Get EntraID Applications and Secrets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Client Secrets": {
      "main": [
        [
          {
            "node": "Build Client Secrets Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out Applications": {
      "main": [
        [
          {
            "node": "Split Out Client Secrets",
            "type": "main",
            "index": 0
          },
          {
            "node": "Split Out Certificates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out Certificates": {
      "main": [
        [
          {
            "node": "Filter Client Certificates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out Client Secrets": {
      "main": [
        [
          {
            "node": "Filter Client Secrets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Certificates Report": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Filter Client Certificates": {
      "main": [
        [
          {
            "node": "Build Certificates Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Client Secrets Report": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Expiring Secrets not empty": {
      "main": [
        [
          {
            "node": "HTML Table with Expiring Secrets",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Operation, do nothing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTML Table with Expiring Secrets": {
      "main": [
        [
          {
            "node": "Send email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get EntraID Applications and Secrets": {
      "main": [
        [
          {
            "node": "Split Out Applications",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Set Variables",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Stay ahead of credential expirations by automatically detecting Entra ID application client secrets and certificates that are about to expire, and sending a neatly formatted email report.

Source: https://n8n.io/workflows/12399/ — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

This workflow is for anyone who is using N8N. It's especially helpful if you are a DevOps and your N8N instance is self hosted. If you carea lot about security and number of failed executions and at t

n8n, HTTP Request
Web Scraping

This n8n workflow monitors significant register, financial, and news-related events for German companies. It takes a list of companies (for example from a CRM or lead list), resolves them via the Impl

HTTP Request
Web Scraping

[](https://xqus.relezy.com/haveibeenpwnedcom-breaches) Security professionals Developers Individuals interested in data breach awareness Automated monitoring for new breaches Proactive identity protec

HTTP Request, Read Write File
Web Scraping

Automatically monitor billable Kimai projects every weekday morning and receive a formatted HTML email when a project deadline is approaching or its hour budget is running low. If nothing requires att

Email Send, HTTP Request
Web Scraping

This n8n template shows how to upload a file in your Google Drive desired folder, compress it with the iLovePDF tool and move the compressed file to another folder.

Google Drive, Google Drive Trigger, HTTP Request