AutomationFlowsWeb Scraping › Generate Bulk Certificates and Contracts with Carbone, Excel and Onedrive

Generate Bulk Certificates and Contracts with Carbone, Excel and Onedrive

ByMychel Garzon @mychel-garzon on n8n.io

This workflow runs every Monday and reads pending rows from a Microsoft Excel workbook, generates certificate and contract PDFs in bulk with Carbone using templates stored in SharePoint, uploads the files to OneDrive, posts a summary card to Microsoft Teams, and marks the Excel…

Cron / scheduled trigger★★★★☆ complexity28 nodesHTTP RequestMicrosoft ExcelMicrosoft SharePointN8N Nodes CarboneCompressionMicrosoft One DriveMicrosoft Teams
Web Scraping Trigger: Cron / scheduled Nodes: 28 Complexity: ★★★★☆ Added:

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

The workflow JSON

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

Download .json
{
  "id": "U7FRP9wgf1KgBBQY",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Bulk Certificate and Contract Generator with Carbone, Excel and OneDrive",
  "tags": [],
  "nodes": [
    {
      "id": "b02a48be-78eb-4744-a765-80a6a14156bc",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        8656,
        -1440
      ],
      "parameters": {
        "width": 614,
        "height": 1008,
        "content": "## Bulk Certificate and Contract Generator: Carbone PDF Batch with OneDrive Archive and Excel Status Update\n\n### Weekly Scheduled Document Generation from Excel via Carbone and Microsoft 365\n\n### How it works\nThis workflow runs every Monday at 08:00, reads pending rows from a Microsoft\nExcel workbook, generates batches of PDF certificates and contracts using\nCarbone's renderDocument operation, archives each file to OneDrive, notifies\na Teams channel with a summary Adaptive Card, and batch-updates the Excel\nstatus rows to Completed via the Graph API.\n\n### Setup steps\n- [ ] Open the Config node and replace all placeholder IDs: workbookId,\n      worksheetId, certificateTemplateFileId, contractTemplateFileId,\n      certificatesFolderId, contractsFolderId, teamsTeamId, teamsChannelId.\n- [ ] Connect your Microsoft Excel credential to the Read Pending Rows from\n      Excel node.\n- [ ] Connect your Microsoft Graph OAuth2 credential to the Fetch Header Row\n      and Batch Update Excel via Graph API nodes.\n- [ ] Connect your Microsoft SharePoint credential to both Fetch Template\n      nodes and configure the file IDs from the Config node.\n- [ ] Connect your Carbone credential to both Generate nodes. Ensure your\n      Carbone templates use the field names output by the Build Carbone Batch\n      Arrays node: certificates array uses name, email, company, role,\n      courseTitle, completionDate, score, certificateId, issuedBy, issuerName.\n      Contracts array uses name, email, company, role, startDate, contractRef,\n      issuedBy, issuerName.\n- [ ] Connect your Microsoft OneDrive credential to both Upload nodes and\n      configure the target folder IDs from the Config node.\n- [ ] Connect your Microsoft Teams credential to the Notify Teams Channel\n      node and configure the target team and channel IDs from the Config node.\n- [ ] Update issuedBy and issuerName in the Build Carbone Batch Arrays node\n      to match your organization name and authorized signatory.\n\n### Required credentials\n- **Microsoft Excel** (row read)\n- **Microsoft Graph OAuth2** (header row fetch + Excel batch status update)\n- **Microsoft SharePoint** (template fetch)\n- **Carbone** (PDF batch generation via renderDocument)\n- **Microsoft OneDrive** (file archive)\n- **Microsoft Teams** (completion notification)\n\n### Required Excel columns\n`Name` \u00b7 `Email` \u00b7 `Company` \u00b7 `Role` \u00b7 `DocumentType` \u00b7 `CourseTitle` \u00b7\n`CompletionDate` \u00b7 `Score` \u00b7 `Status` \u00b7 `ProcessedAt`\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "fd443d56-99b4-4f3d-9e59-80281b126fd8",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        9344,
        -1008
      ],
      "parameters": {
        "color": 7,
        "width": 1360,
        "height": 440,
        "content": "### Trigger \u00b7 Config \u00b7 Excel Read\nFires every Monday 08:00. All IDs loaded from Config. Header row fetched via Graph API to resolve column letters at runtime. Reads only Pending rows."
      },
      "typeVersion": 1
    },
    {
      "id": "090f86db-afc1-4853-b5e5-15b2b3d2c91b",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        10800,
        -1200
      ],
      "parameters": {
        "color": 7,
        "width": 1320,
        "height": 540,
        "content": "### Certificate Generation & Upload\nFetches template from SharePoint, generates batch PDF ZIP via Carbone, extracts files, uploads each to OneDrive, aggregates before continuing."
      },
      "typeVersion": 1
    },
    {
      "id": "8f7f4106-6431-40bd-a547-8682ab80b087",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        12208,
        -1200
      ],
      "parameters": {
        "color": 7,
        "width": 1360,
        "height": 540,
        "content": "### Contract Generation & Upload\nFetches template from SharePoint, generates batch PDF ZIP via Carbone, extracts files, uploads each to OneDrive, aggregates before notification."
      },
      "typeVersion": 1
    },
    {
      "id": "fe41ade8-08ed-46bc-98ec-e164f8b40def",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        13648,
        -1040
      ],
      "parameters": {
        "color": 7,
        "width": 1300,
        "height": 440,
        "content": "### Notify & Status Update\nBuilds Teams Adaptive Card with batch summary and posts to configured channel. Batch-updates Excel rows to Completed via Graph API using dynamically resolved column letters, chunked at 20 requests per call."
      },
      "typeVersion": 1
    },
    {
      "id": "6691bf1a-ae61-4657-b61a-85367c51a210",
      "name": "Schedule Trigger (Monday 8am)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        9440,
        -800
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "d98f2fed-a95f-416f-96a0-f42b4a7ff2cd",
      "name": "Config",
      "type": "n8n-nodes-base.set",
      "notes": "Replace all YOUR_* values before activating. workbookId and worksheetId: open the workbook in Excel Online \u2014 workbookId is after /items/, worksheetId is after /worksheets/. SharePoint file IDs: from the file URL, value between sourcedoc=%7B and %7D. OneDrive folder IDs: right-click folder \u2192 Details.",
      "position": [
        9664,
        -800
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cfg-001",
              "name": "workbookId",
              "type": "string",
              "value": "YOUR_EXCEL_WORKBOOK_ID"
            },
            {
              "id": "cfg-002",
              "name": "worksheetId",
              "type": "string",
              "value": "YOUR_WORKSHEET_ID"
            },
            {
              "id": "cfg-003",
              "name": "certificateTemplateFileId",
              "type": "string",
              "value": "YOUR_CERTIFICATE_TEMPLATE_FILE_ID"
            },
            {
              "id": "cfg-004",
              "name": "contractTemplateFileId",
              "type": "string",
              "value": "YOUR_CONTRACT_TEMPLATE_FILE_ID"
            },
            {
              "id": "cfg-005",
              "name": "certificatesFolderId",
              "type": "string",
              "value": "YOUR_ONEDRIVE_CERTIFICATES_FOLDER_ID"
            },
            {
              "id": "cfg-006",
              "name": "contractsFolderId",
              "type": "string",
              "value": "YOUR_ONEDRIVE_CONTRACTS_FOLDER_ID"
            },
            {
              "id": "cfg-007",
              "name": "teamsTeamId",
              "type": "string",
              "value": "YOUR_TEAMS_TEAM_ID"
            },
            {
              "id": "cfg-008",
              "name": "teamsChannelId",
              "type": "string",
              "value": "YOUR_TEAMS_CHANNEL_ID"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "94bbe570-c425-4b0c-995d-3dac8b952c57",
      "name": "Fetch Header Row",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        9888,
        -800
      ],
      "parameters": {
        "url": "={{ `https://graph.microsoft.com/v1.0/me/drive/items/${$('Config').first().json.workbookId}/workbook/worksheets/${$('Config').first().json.worksheetId}/rows/itemAt(index=0)` }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftExcelOAuth2Api"
      },
      "typeVersion": 4.2
    },
    {
      "id": "07caa82d-1d67-4c84-b62d-e8e7cbd21f86",
      "name": "Read Pending Rows from Excel",
      "type": "n8n-nodes-base.microsoftExcel",
      "position": [
        10112,
        -800
      ],
      "parameters": {
        "operation": "readRows"
      },
      "typeVersion": 2
    },
    {
      "id": "2561472a-5eec-4d56-8280-b975d5fc7271",
      "name": "Check Rows Exist",
      "type": "n8n-nodes-base.if",
      "position": [
        10336,
        -800
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $input.all().length }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "36459af1-b3d6-4081-afe1-a3121b10642b",
      "name": "Log No Rows",
      "type": "n8n-nodes-base.noOp",
      "position": [
        10560,
        -688
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "f861e1e6-732c-415f-be33-6e344844556a",
      "name": "Build Carbone Batch Arrays",
      "type": "n8n-nodes-base.code",
      "position": [
        10560,
        -896
      ],
      "parameters": {
        "jsCode": "const allItems = $input.all();\n\nif (!allItems || allItems.length === 0) {\n  return [{ json: { certificates: [], contracts: [], totalCertificates: 0, totalContracts: 0, totalDocuments: 0, batchId: `BATCH-${Date.now()}`, processedAt: new Date().toISOString(), originalRows: [] } }];\n}\n\nconst rows = allItems.map((item, index) => ({\n  ...item.json,\n  _originalRowIndex: index + 2\n}));\n\nconst certificates = rows\n  .filter(r => (r.DocumentType || '').toLowerCase().includes('certificate'))\n  .map(r => ({\n    name: r.Name || '',\n    email: r.Email || '',\n    company: r.Company || '',\n    role: r.Role || '',\n    courseTitle: r.CourseTitle || '',\n    completionDate: r.CompletionDate || new Date().toISOString().split('T')[0],\n    score: r.Score || '',\n    certificateId: `CERT-${Date.now()}-${Math.random().toString(36).substring(2,6).toUpperCase()}`,\n    issuedBy: 'Your Organization Name',\n    issuerName: 'Your Name'\n  }));\n\nconst contracts = rows\n  .filter(r => (r.DocumentType || '').toLowerCase().includes('contract'))\n  .map(r => ({\n    name: r.Name || '',\n    email: r.Email || '',\n    company: r.Company || '',\n    role: r.Role || '',\n    startDate: r.CompletionDate || new Date().toISOString().split('T')[0],\n    contractRef: `CTR-${Date.now()}-${Math.random().toString(36).substring(2,6).toUpperCase()}`,\n    issuedBy: 'Your Organization Name',\n    issuerName: 'Your Name'\n  }));\n\nreturn [{\n  json: {\n    certificates,\n    contracts,\n    totalCertificates: certificates.length,\n    totalContracts: contracts.length,\n    totalDocuments: certificates.length + contracts.length,\n    batchId: `BATCH-${Date.now()}`,\n    processedAt: new Date().toISOString(),\n    originalRows: rows\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "5dfd171f-8314-46f3-88ee-0f4d902781c0",
      "name": "Has Certificates?",
      "type": "n8n-nodes-base.if",
      "position": [
        10848,
        -896
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.totalCertificates }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "344c966d-03b3-492c-a066-b84dd2041110",
      "name": "Fetch Certificate Template",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "position": [
        11072,
        -976
      ],
      "parameters": {
        "operation": "getFile",
        "requestOptions": {}
      },
      "typeVersion": 1
    },
    {
      "id": "edf6bff5-39e6-40b9-85c1-6d656815370a",
      "name": "Generate Certificates Batch PDF",
      "type": "n8n-nodes-carbone.carbone",
      "position": [
        11296,
        -976
      ],
      "parameters": {
        "data": "={{ JSON.stringify({ d: { certificates: $('Build Carbone Batch Arrays').first().json.certificates } }) }}",
        "resource": "renderDocument",
        "convertTo": "pdf",
        "templateSource": "file",
        "generateAdditionalOptions": {
          "batchOutput": "zip",
          "batchSplitBy": "d.certificates"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "e75b852f-378f-484e-a9d6-7eaf9f741283",
      "name": "Extract Certificate Files from ZIP",
      "type": "n8n-nodes-base.compression",
      "position": [
        11520,
        -976
      ],
      "parameters": {
        "binaryPropertyName": "certificates_zip"
      },
      "typeVersion": 1
    },
    {
      "id": "e54ddc2c-12b5-4717-918f-ee0cedb30f55",
      "name": "Upload Certificate to OneDrive",
      "type": "n8n-nodes-base.microsoftOneDrive",
      "position": [
        11744,
        -976
      ],
      "settings": {
        "continueOnFail": true
      },
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "c3465e6a-306f-446e-b721-d7459d8c4033",
      "name": "Aggregate Certificate Uploads",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        11968,
        -976
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData"
      },
      "typeVersion": 1
    },
    {
      "id": "9753af79-c2f9-42bf-8984-51c02e379ec8",
      "name": "Has Contracts?",
      "type": "n8n-nodes-base.if",
      "position": [
        12240,
        -880
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $('Build Carbone Batch Arrays').first().json.totalContracts }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "b683b812-9090-4882-bfb5-a5a71525c810",
      "name": "Fetch Contract Template",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "position": [
        12368,
        -1040
      ],
      "parameters": {
        "operation": "getFile",
        "requestOptions": {}
      },
      "typeVersion": 1
    },
    {
      "id": "3ae1b04a-b813-49c0-a156-c87f18e50e8c",
      "name": "Generate Contracts Batch PDF",
      "type": "n8n-nodes-carbone.carbone",
      "position": [
        12592,
        -1040
      ],
      "parameters": {
        "data": "={{ JSON.stringify({ d: { contracts: $('Build Carbone Batch Arrays').first().json.contracts } }) }}",
        "resource": "renderDocument",
        "convertTo": "pdf",
        "templateSource": "file",
        "generateAdditionalOptions": {
          "batchOutput": "zip",
          "batchSplitBy": "d.contracts"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "44ff36f1-5982-4e30-bc8a-ac3cbdbba4b3",
      "name": "Extract Contract Files from ZIP",
      "type": "n8n-nodes-base.compression",
      "position": [
        12816,
        -1040
      ],
      "parameters": {
        "binaryPropertyName": "contracts_zip"
      },
      "typeVersion": 1
    },
    {
      "id": "9da9804a-934e-4d75-90a9-04754457b85d",
      "name": "Upload Contract to OneDrive",
      "type": "n8n-nodes-base.microsoftOneDrive",
      "position": [
        13040,
        -1040
      ],
      "settings": {
        "continueOnFail": true
      },
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "94e72e08-b9d2-4e1f-9679-09662904633b",
      "name": "Aggregate Contract Uploads",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        13264,
        -1040
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData"
      },
      "typeVersion": 1
    },
    {
      "id": "d0f08d2a-2d38-4ac2-981c-1c6adbe0e201",
      "name": "Build Teams Adaptive Card",
      "type": "n8n-nodes-base.code",
      "position": [
        13952,
        -864
      ],
      "parameters": {
        "jsCode": "const batchData = $('Build Carbone Batch Arrays').first().json;\n\nreturn [{\n  json: {\n    attachments: [\n      {\n        contentType: 'application/vnd.microsoft.card.adaptive',\n        content: {\n          '$schema': 'http://adaptivecards.io/schemas/adaptive-card.json',\n          type: 'AdaptiveCard',\n          version: '1.4',\n          body: [\n            {\n              type: 'TextBlock',\n              text: 'Bulk Document Generation Complete',\n              weight: 'Bolder',\n              size: 'Large',\n              color: 'Good'\n            },\n            {\n              type: 'FactSet',\n              facts: [\n                { title: 'Batch ID',               value: batchData.batchId },\n                { title: 'Certificates Generated', value: String(batchData.totalCertificates) },\n                { title: 'Contracts Generated',    value: String(batchData.totalContracts) },\n                { title: 'Total Documents',        value: String(batchData.totalDocuments) },\n                { title: 'Processed At',           value: batchData.processedAt }\n              ]\n            },\n            {\n              type: 'TextBlock',\n              text: 'All files uploaded to OneDrive. Excel rows updated to Completed.',\n              wrap: true,\n              color: 'Default'\n            }\n          ]\n        }\n      }\n    ]\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c972a888-24b8-40aa-9534-6417c79e2f8c",
      "name": "Notify Teams Channel",
      "type": "n8n-nodes-base.microsoftTeams",
      "position": [
        14176,
        -864
      ],
      "parameters": {
        "operation": "sendMessage"
      },
      "typeVersion": 2
    },
    {
      "id": "e17c269f-61b5-4e10-ab10-57173b17d540",
      "name": "Build Graph Batch Payload",
      "type": "n8n-nodes-base.code",
      "position": [
        14400,
        -864
      ],
      "parameters": {
        "jsCode": "const headerValues = $('Fetch Header Row').first().json.values[0];\n\nconst colIndex = {};\nheaderValues.forEach((name, i) => {\n  if (typeof name === 'string' && name.trim()) {\n    colIndex[name.trim()] = i;\n  }\n});\n\nconst statusIdx      = colIndex['Status'];\nconst processedAtIdx = colIndex['ProcessedAt'];\n\nif (statusIdx === undefined || processedAtIdx === undefined) {\n  throw new Error(\n    `Required columns missing. Found: ${JSON.stringify(colIndex)}. Need: Status, ProcessedAt.`\n  );\n}\n\nfunction colLetter(idx) {\n  let letter = '';\n  let n = idx;\n  while (n >= 0) {\n    letter = String.fromCharCode(65 + (n % 26)) + letter;\n    n = Math.floor(n / 26) - 1;\n  }\n  return letter;\n}\n\nconst statusCol    = colLetter(statusIdx);\nconst processedCol = colLetter(processedAtIdx);\n\nconst rows        = $('Build Carbone Batch Arrays').first().json.originalRows;\nconst workbookId  = $('Config').first().json.workbookId;\nconst worksheetId = $('Config').first().json.worksheetId;\nconst now         = new Date().toISOString();\n\nconst requests = [];\nrows.forEach((row, i) => {\n  const excelRow = row._originalRowIndex;\n  requests.push({\n    id: `status_${i}`,\n    method: 'PATCH',\n    url: `/me/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${statusCol}${excelRow}')`,\n    headers: { 'Content-Type': 'application/json' },\n    body: { values: [[ 'Completed' ]] }\n  });\n  requests.push({\n    id: `time_${i}`,\n    method: 'PATCH',\n    url: `/me/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${processedCol}${excelRow}')`,\n    headers: { 'Content-Type': 'application/json' },\n    body: { values: [[ now ]] }\n  });\n});\n\n// 2 requests per row \u2014 chunk at 10 rows to stay under Graph $batch limit of 20\nconst chunks = [];\nfor (let i = 0; i < requests.length; i += 20) {\n  chunks.push({ json: { requests: requests.slice(i, i + 20) } });\n}\n\nreturn chunks;"
      },
      "typeVersion": 2
    },
    {
      "id": "7f2fab32-6d9a-407d-b5ff-162afef8fc53",
      "name": "Batch Update Excel via Graph API",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        14624,
        -864
      ],
      "parameters": {
        "url": "https://graph.microsoft.com/v1.0/$batch",
        "method": "POST",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "jsonBody": "={{ $json }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "microsoftExcelOAuth2Api"
      },
      "typeVersion": 4.2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "c25540c0-a406-48f6-87d1-40053c48ce8e",
  "nodeGroups": [],
  "connections": {
    "Config": {
      "main": [
        [
          {
            "node": "Fetch Header Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Contracts?": {
      "main": [
        [
          {
            "node": "Fetch Contract Template",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Teams Adaptive Card",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Rows Exist": {
      "main": [
        [
          {
            "node": "Build Carbone Batch Arrays",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log No Rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Header Row": {
      "main": [
        [
          {
            "node": "Read Pending Rows from Excel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Certificates?": {
      "main": [
        [
          {
            "node": "Fetch Certificate Template",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Has Contracts?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notify Teams Channel": {
      "main": [
        [
          {
            "node": "Build Graph Batch Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Contract Template": {
      "main": [
        [
          {
            "node": "Generate Contracts Batch PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Graph Batch Payload": {
      "main": [
        [
          {
            "node": "Batch Update Excel via Graph API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Teams Adaptive Card": {
      "main": [
        [
          {
            "node": "Notify Teams Channel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Contract Uploads": {
      "main": [
        [
          {
            "node": "Build Teams Adaptive Card",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Carbone Batch Arrays": {
      "main": [
        [
          {
            "node": "Has Certificates?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Certificate Template": {
      "main": [
        [
          {
            "node": "Generate Certificates Batch PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload Contract to OneDrive": {
      "main": [
        [
          {
            "node": "Aggregate Contract Uploads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Contracts Batch PDF": {
      "main": [
        [
          {
            "node": "Extract Contract Files from ZIP",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Pending Rows from Excel": {
      "main": [
        [
          {
            "node": "Check Rows Exist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Certificate Uploads": {
      "main": [
        [
          {
            "node": "Has Contracts?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger (Monday 8am)": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload Certificate to OneDrive": {
      "main": [
        [
          {
            "node": "Aggregate Certificate Uploads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Contract Files from ZIP": {
      "main": [
        [
          {
            "node": "Upload Contract to OneDrive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Certificates Batch PDF": {
      "main": [
        [
          {
            "node": "Extract Certificate Files from ZIP",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Certificate Files from ZIP": {
      "main": [
        [
          {
            "node": "Upload Certificate to OneDrive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

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

About this workflow

This workflow runs every Monday and reads pending rows from a Microsoft Excel workbook, generates certificate and contract PDFs in bulk with Carbone using templates stored in SharePoint, uploads the files to OneDrive, posts a summary card to Microsoft Teams, and marks the Excel…

Source: https://n8n.io/workflows/16379/ — 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 template runs two scheduled workflows to govern Microsoft Entra ID (Azure AD) guest accounts by detecting stale users via Microsoft Graph, staging deletions in SharePoint with a 72-hour window, n

Microsoft SharePoint, Microsoft Teams, Microsoft Entra +1
Web Scraping

PF – WF-03 Social Media Auto-Post. Uses microsoftSharePoint, httpRequest, microsoftTeams. Scheduled trigger; 16 nodes.

Microsoft SharePoint, HTTP Request, Microsoft Teams
Web Scraping

PF – WF-03 Social Media Post v5. Uses microsoftSharePoint, httpRequest, microsoftTeams. Scheduled trigger; 16 nodes.

Microsoft SharePoint, HTTP Request, Microsoft Teams
Web Scraping

PF WF-02 Caption Generator v18.3. Uses microsoftSharePoint, httpRequest. Scheduled trigger; 38 nodes.

Microsoft SharePoint, HTTP Request
Web Scraping

This workflow runs weekly to find inactive Entra ID (Azure AD) B2B guest accounts using Microsoft Graph sign-in activity, notifies each guest’s sponsor via Microsoft Teams, waits 72 hours, deletes the

HTTP Request, Microsoft Teams