{
  "name": "Deduplicate weekly New York LLC filings into Google Sheets with a digest",
  "nodes": [
    {
      "parameters": {
        "content": "### How it works\nThis workflow helps bankers, insurance agents, bookkeepers, and local B2B sales teams covering New York build a weekly list of brand-new LLCs. Every Monday it runs the Loomwise New York New Business Filings Actor on Apify, applies your county, entity-type, and keyword filters, then compares each filing's official DOS ID against rows already in your Google Sheet. Only new filings are appended, so reruns do not create duplicates. Counties that are often registered-agent addresses (Albany by default) can be skipped. When the run finishes, it can email or post a summary with the new count and top names. If the sheet read, Apify run, or append fails after retries, the workflow stops writing and sends a failure alert instead.\n\n### Setup\n1. Install the verified community node `@apify/n8n-nodes-apify` (Settings \u2192 Community Nodes). See https://docs.apify.com/integrations/n8n. Use v0.6 or later.\n2. Connect an Apify API credential. Never paste the API token into a node.\n3. Create a Google Sheet with header row: uniqueId, dosId, businessName, entityType, filingDate, county, city, zipCode, businessAddress, registeredAgentCompany, retrievedAt.\n4. Connect Google Sheets OAuth. In Configure filters, paste your spreadsheet ID and adjust counties, skipCountiesCsv, keywords, and maxResults (keep 100 for a first run).\n5. Connect Gmail or Slack for the digest and failure alerts. Set notificationChannel to gmail or slack and replace the placeholders. Set sendSummaryNotification to false to skip the digest.\n6. Run once manually, then activate. The Actor is pay-per-result (about $2 per 1,000). Actor: https://apify.com/loomwise/new-york-new-business-filings \u2014 more data tools: https://loomwise.app/data/\n\n### Customization\nChange counties or keywords, add CORP to entityTypesCsv, or clear skipCountiesCsv. Point the digest at Slack, or add a CRM node after the append step.\n",
        "height": 740,
        "width": 520,
        "color": 1
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -560,
        -100
      ],
      "id": "7d02775c-5753-4d81-8a1e-a9fc2620b20d",
      "name": "Overview"
    },
    {
      "parameters": {
        "content": "## 1. Schedule and configuration\nSet filters, the Google Sheet ID, and notification choices here. Later nodes reuse these values.",
        "height": 200,
        "width": 480,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        20,
        -100
      ],
      "id": "58655468-128c-4874-81b0-1412e98bad28",
      "name": "## 1. Schedule and configuration"
    },
    {
      "parameters": {
        "content": "## 2. Load existing rows and run Apify\nRead unique IDs already in the sheet, then run the Actor with your filters. The Apify node uses your API credential, not a pasted token.",
        "height": 200,
        "width": 720,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        540,
        -100
      ],
      "id": "092cd58e-4440-4524-b439-f50dd1ad6614",
      "name": "## 2. Load existing rows and run Apify"
    },
    {
      "parameters": {
        "content": "## 3. Filter and deduplicate\nDrop rows that fail your extra rules or already exist in the sheet, then build a short summary of what is new.",
        "height": 200,
        "width": 460,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1300,
        -100
      ],
      "id": "42322579-b174-45c5-8b0d-c3c744e93c62",
      "name": "## 3. Filter and deduplicate"
    },
    {
      "parameters": {
        "content": "## 4. Save new rows and notify\nAppend only new rows. If notifications are on, send the count and top items by Gmail or Slack.",
        "height": 200,
        "width": 720,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1800,
        -100
      ],
      "id": "67a9e96b-39fa-4a78-929d-ea0fd77d51b8",
      "name": "## 4. Save new rows and notify"
    },
    {
      "parameters": {
        "content": "## 5. Handle failures\nIf the sheet read, Apify run, or append fails after retries, skip writing from that step and send a failure alert on your chosen channel.",
        "height": 160,
        "width": 720,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        540,
        520
      ],
      "id": "4223fbba-7447-4db3-afb8-3ea592f8fb21",
      "name": "## 5. Handle failures"
    },
    {
      "parameters": {
        "content": "Replace every REPLACE_WITH_\u2026 value in Configure filters, then connect Apify, Google Sheets, and Gmail or Slack credentials before you run or activate this workflow.",
        "height": 200,
        "width": 480,
        "color": 3
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        20,
        520
      ],
      "id": "1fb19a09-963f-452f-84e1-03ce3d5407c0",
      "name": "\u26a0 Replace placeholders before you run"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 7,
              "triggerAtMinute": 0
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        40,
        220
      ],
      "id": "740f9b3c-4edd-4756-9469-521ff508915a",
      "name": "Every Monday 7am"
    },
    {
      "parameters": {
        "mode": "manual",
        "duplicateItem": false,
        "assignments": {
          "assignments": [
            {
              "id": "8bb01f2a-e591-4c2d-bbf6-66c8e590cf45",
              "name": "workflowTitle",
              "value": "Weekly New York LLC filings",
              "type": "string"
            },
            {
              "id": "f4fff7b0-f7be-4bb4-a99a-f0b66377da48",
              "name": "spreadsheetId",
              "value": "REPLACE_WITH_YOUR_GOOGLE_SHEET_ID",
              "type": "string"
            },
            {
              "id": "4fccc65d-e09f-41a3-b0f2-d068d1bf3248",
              "name": "sheetName",
              "value": "Sheet1",
              "type": "string"
            },
            {
              "id": "6eec9a87-8593-4a4b-9be1-1534d7e210b5",
              "name": "uniqueIdField",
              "value": "dosId",
              "type": "string"
            },
            {
              "id": "3afc6164-f852-4751-b814-8d926ed4e5f0",
              "name": "filedWithinDays",
              "value": 7,
              "type": "number"
            },
            {
              "id": "a58fcc5f-929c-484f-a88c-8acf1c78d60a",
              "name": "countiesCsv",
              "value": "Kings,Queens",
              "type": "string"
            },
            {
              "id": "b14cf261-e830-4753-9d46-e9912c68a651",
              "name": "skipCountiesCsv",
              "value": "Albany",
              "type": "string"
            },
            {
              "id": "cf066b4d-0e39-4294-b2d2-d7e5963ab102",
              "name": "entityTypesCsv",
              "value": "LLC",
              "type": "string"
            },
            {
              "id": "10baa75d-9357-4379-89f6-2899f5e46e64",
              "name": "keywordsCsv",
              "value": "",
              "type": "string"
            },
            {
              "id": "4bb17c32-0419-43bc-b616-50d77735b913",
              "name": "maxResults",
              "value": 100,
              "type": "number"
            },
            {
              "id": "e43056b6-29f9-4b5d-a835-f87a376db6dc",
              "name": "sendSummaryNotification",
              "value": true,
              "type": "boolean"
            },
            {
              "id": "d609a1f8-b434-42d5-82a5-2d4d1d8e16e2",
              "name": "notificationChannel",
              "value": "gmail",
              "type": "string"
            },
            {
              "id": "de29bde9-af5f-40d4-a597-3d5609369658",
              "name": "notifyEmail",
              "value": "REPLACE_WITH_YOUR_EMAIL@example.com",
              "type": "string"
            },
            {
              "id": "5756a93b-95d4-4c94-9f1d-eacccd831e3f",
              "name": "slackChannelId",
              "value": "REPLACE_WITH_YOUR_SLACK_CHANNEL_ID",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": false,
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        260,
        220
      ],
      "id": "1aadb8fb-f093-4277-af21-8190ca4de26e",
      "name": "Configure filters"
    },
    {
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Configure filters').first().json.spreadsheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Configure filters').first().json.sheetName }}",
          "mode": "name"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        540,
        220
      ],
      "id": "34f501c2-d8ba-4419-89a3-189e535b26f6",
      "name": "Read existing sheet rows",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_WITH_YOUR_GOOGLE_SHEETS_CREDENTIAL_ID",
          "name": "Google Sheets account"
        }
      },
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000,
      "alwaysOutputData": true,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "jsCode": "const cfg = $('Configure filters').first().json;\nconst field = String(cfg.uniqueIdField || '').trim();\nconst existingIds = [];\nconst seen = new Set();\n\nfunction computeId(row) {\n  if (!row || typeof row !== 'object') return '';\n  const stored = String(row.uniqueId || '').trim();\n  if (stored && stored.toLowerCase() !== 'uniqueid') return stored;\n  if (field === 'cityId+permitNumber') {\n    return [row.cityId || row.city, row.permitNumber].map((v) => String(v || '').trim()).filter(Boolean).join('|');\n  }\n  if (field === 'taxpayerNumber+outletNumber') {\n    return [row.taxpayerNumber, row.outletNumber].map((v) => String(v || '').trim()).filter(Boolean).join('|');\n  }\n  return String(row[field] || '').trim();\n}\n\nfor (const item of $input.all()) {\n  const id = computeId(item.json || {});\n  if (id && !seen.has(id)) {\n    seen.add(id);\n    existingIds.push(id);\n  }\n}\n\nreturn [{\n  json: {\n    ...cfg,\n    existingIds,\n    existingCount: existingIds.length,\n  },\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        780,
        220
      ],
      "id": "2e86a31f-24bd-4ce5-9419-02ade8f8e128",
      "name": "Collect existing IDs"
    },
    {
      "parameters": {
        "operation": "Run actor and get dataset",
        "actorId": "loomwise~new-york-new-business-filings",
        "customBody": "={{ JSON.stringify({ filedWithinDays: $json.filedWithinDays, counties: String($json.countiesCsv||\"\").split(\",\").map(s=>s.trim()).filter(Boolean), entityTypes: String($json.entityTypesCsv||\"\").split(\",\").map(s=>s.trim()).filter(Boolean), keywords: String($json.keywordsCsv||\"\").split(\",\").map(s=>s.trim()).filter(Boolean), maxResults: $json.maxResults, includeAddress: true }) }}",
        "timeout": null,
        "memory": 1024,
        "resource": "Actors",
        "actorInputMode": "json",
        "authentication": "apifyApi"
      },
      "type": "@apify/n8n-nodes-apify.apify",
      "typeVersion": 1,
      "position": [
        1040,
        220
      ],
      "id": "b5575822-0a60-42d7-99c3-3f69bcfc7b76",
      "name": "Fetch NY new LLC filings",
      "credentials": {
        "apifyApi": {
          "id": "REPLACE_WITH_YOUR_APIFY_CREDENTIAL_ID",
          "name": "Apify account"
        }
      },
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 10000,
      "alwaysOutputData": true,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "jsCode": "const cfg = $('Collect existing IDs').first().json;\nconst existing = new Set((cfg.existingIds || []).map((id) => String(id)));\nconst field = String(cfg.uniqueIdField || '').trim();\nconst channel = String(cfg.notificationChannel || 'none').trim().toLowerCase();\nconst sendSummary = cfg.sendSummaryNotification === true || String(cfg.sendSummaryNotification).toLowerCase() === 'true';\n\nfunction computeId(row) {\n  if (!row || typeof row !== 'object') return '';\n  if (field === 'cityId+permitNumber') {\n    return [row.cityId || row.city, row.permitNumber].map((v) => String(v || '').trim()).filter(Boolean).join('|');\n  }\n  if (field === 'taxpayerNumber+outletNumber') {\n    return [row.taxpayerNumber, row.outletNumber].map((v) => String(v || '').trim()).filter(Boolean).join('|');\n  }\n  return String(row[field] || '').trim();\n}\n\nfunction hasActorFields(row) {\n  return Boolean(computeId(row));\n}\n\nfunction csvList(value) {\n  return String(value || '').split(',').map((s) => s.trim()).filter(Boolean);\n}\nfunction passesExtraFilters(row, cfg) {\n  const skip = csvList(cfg.skipCountiesCsv).map((s) => s.toLowerCase());\n  if (skip.length && skip.includes(String(row.county || '').trim().toLowerCase())) return false;\n  const keywords = csvList(cfg.keywordsCsv).map((s) => s.toLowerCase());\n  if (keywords.length) {\n    const hay = `${row.businessName || ''} ${row.businessAddress || ''}`.toLowerCase();\n    if (!keywords.some((k) => hay.includes(k))) return false;\n  }\n  return true;\n}\nfunction mapRow(row, uniqueId) {\n  return {\n    uniqueId,\n    dosId: row.dosId || '',\n    businessName: row.businessName || '',\n    entityType: row.entityType || '',\n    filingDate: row.filingDate || '',\n    county: row.county || '',\n    city: row.city || '',\n    zipCode: row.zipCode || '',\n    businessAddress: row.businessAddress || '',\n    registeredAgentCompany: row.registeredAgentCompany || '',\n    retrievedAt: row.retrievedAt || new Date().toISOString(),\n  };\n}\n\nconst fetched = [];\nfor (const item of $input.all()) {\n  const row = item.json || {};\n  if (hasActorFields(row)) fetched.push(row);\n}\n\nlet filteredOut = 0;\nlet skippedDuplicates = 0;\nconst newRows = [];\nconst seenThisRun = new Set();\n\nfor (const row of fetched) {\n  if (!passesExtraFilters(row, cfg)) {\n    filteredOut += 1;\n    continue;\n  }\n  const uniqueId = computeId(row);\n  if (!uniqueId) {\n    filteredOut += 1;\n    continue;\n  }\n  if (existing.has(uniqueId) || seenThisRun.has(uniqueId)) {\n    skippedDuplicates += 1;\n    continue;\n  }\n  seenThisRun.add(uniqueId);\n  const mapped = mapRow(row, uniqueId);\n  newRows.push(mapped);\n}\n\nconst topItems = newRows.slice(0, 8);\nconst lines = topItems.map((row, idx) => `${idx + 1}. ${row.businessName} \u2014 ${row.county || 'n/a'} / ${row.city || 'n/a'} filed ${row.filingDate || 'n/a'}`);\nconst intro = `New New York LLC filings appended this week: ${newRows.length}.`;\nconst body = newRows.length\n  ? [intro, '', ...lines, '', `Skipped ${skippedDuplicates} already in the sheet. Filtered out ${filteredOut} extra rows. Existing IDs loaded: ${cfg.existingCount || 0}.`].join('\\n')\n  : \"No new New York LLC filings matched your filters (or they were already in the sheet).\";\nconst subject = `NY LLC filings: ${newRows.length} new lead(s)`;\nconst slackText = body;\n\nreturn [{\n  json: {\n    fetchedCount: fetched.length,\n    newCount: newRows.length,\n    skippedDuplicates,\n    filteredOut,\n    existingCount: cfg.existingCount || 0,\n    newRows,\n    topItems,\n    sendSummaryNotification: sendSummary,\n    notificationChannel: channel,\n    notifyEmail: cfg.notifyEmail,\n    slackChannelId: cfg.slackChannelId,\n    subject,\n    body,\n    slackText,\n  },\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1320,
        220
      ],
      "id": "cc39fd05-5a49-4d11-a5e4-c0ca9aca80e1",
      "name": "Keep only new matching leads"
    },
    {
      "parameters": {
        "fieldToSplitOut": "newRows",
        "options": {}
      },
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        1560,
        140
      ],
      "id": "8807b575-31ae-42b1-94d3-4e58751db2c9",
      "name": "Split out new rows"
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Configure filters').first().json.spreadsheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Configure filters').first().json.sheetName }}",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "uniqueId": "={{ $json.uniqueId }}",
            "dosId": "={{ $json.dosId }}",
            "businessName": "={{ $json.businessName }}",
            "entityType": "={{ $json.entityType }}",
            "filingDate": "={{ $json.filingDate }}",
            "county": "={{ $json.county }}",
            "city": "={{ $json.city }}",
            "zipCode": "={{ $json.zipCode }}",
            "businessAddress": "={{ $json.businessAddress }}",
            "registeredAgentCompany": "={{ $json.registeredAgentCompany }}",
            "retrievedAt": "={{ $json.retrievedAt }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "uniqueId",
              "displayName": "uniqueId",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "dosId",
              "displayName": "dosId",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "businessName",
              "displayName": "businessName",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "entityType",
              "displayName": "entityType",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "filingDate",
              "displayName": "filingDate",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "county",
              "displayName": "county",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "city",
              "displayName": "city",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "zipCode",
              "displayName": "zipCode",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "businessAddress",
              "displayName": "businessAddress",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "registeredAgentCompany",
              "displayName": "registeredAgentCompany",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "retrievedAt",
              "displayName": "retrievedAt",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1840,
        140
      ],
      "id": "4f25b985-88c6-4044-8f6a-4730869b0c0b",
      "name": "Append new rows to Sheet",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_WITH_YOUR_GOOGLE_SHEETS_CREDENTIAL_ID",
          "name": "Google Sheets account"
        }
      },
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "ca5c9e2c-542b-41d4-8d77-8d9abbfdac42",
              "leftValue": "={{ $json.sendSummaryNotification }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1560,
        340
      ],
      "id": "7b2978d1-1295-4c58-961e-3b259cbf173a",
      "name": "Should send a summary?"
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "leftValue": "",
                  "typeValidation": "loose",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "56e6f996-9415-47a2-9baa-f48f751c0990",
                    "leftValue": "={{ $json.notificationChannel }}",
                    "rightValue": "gmail",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "gmail"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "leftValue": "",
                  "typeValidation": "loose",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "7ecfa7fa-7ee7-4cd7-89b8-fce3ee73e5ec",
                    "leftValue": "={{ $json.notificationChannel }}",
                    "rightValue": "slack",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "slack"
            }
          ]
        },
        "options": {
          "fallbackOutput": "none"
        }
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        1840,
        340
      ],
      "id": "73db23f4-bd34-4377-86d3-f40fd4a7d1a7",
      "name": "Route the notification"
    },
    {
      "parameters": {
        "sendTo": "={{ $json.notifyEmail }}",
        "subject": "={{ $json.subject }}",
        "emailType": "text",
        "message": "={{ $json.body }}",
        "options": {}
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        2120,
        260
      ],
      "id": "c6d59343-64c0-4006-b72b-f59b87ff2a5d",
      "name": "Email the run summary",
      "credentials": {
        "gmailOAuth2": {
          "id": "REPLACE_WITH_YOUR_GMAIL_CREDENTIAL_ID",
          "name": "Gmail account"
        }
      },
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "={{ $json.slackChannelId }}",
          "mode": "id"
        },
        "text": "={{ $json.slackText }}",
        "otherOptions": {}
      },
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [
        2120,
        420
      ],
      "id": "e037495f-efcc-4dc7-9fcb-70696622668e",
      "name": "Post the run summary to Slack",
      "credentials": {
        "slackApi": {
          "id": "REPLACE_WITH_YOUR_SLACK_CREDENTIAL_ID",
          "name": "Slack account"
        }
      },
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "jsCode": "const cfg = $('Configure filters').first().json;\nconst item = $input.first() || {};\nconst payload = item.json || {};\nconst err = item.error || payload.error || {};\nconst message = err.message || payload.message || payload.errorMessage || 'Unknown error (open the failed execution in n8n for details).';\nconst channel = String(cfg.notificationChannel || 'none').trim().toLowerCase();\nconst title = cfg.workflowTitle || 'Loomwise lead workflow';\nconst subject = `Workflow failed: ${title}`;\nconst body = [\n  `The n8n workflow \"${title}\" hit an error and stopped.`,\n  'New rows were not appended from this failed step. Open the failed execution in n8n, fix the issue, then run it again.',\n  '',\n  `Error: ${message}`,\n].join('\\n');\n\nreturn [{\n  json: {\n    isFailure: true,\n    notificationChannel: channel,\n    notifyEmail: cfg.notifyEmail,\n    slackChannelId: cfg.slackChannelId,\n    subject,\n    body,\n    slackText: body,\n  },\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1040,
        720
      ],
      "id": "07f687cd-ffc1-445f-a725-a8a14d564196",
      "name": "Format a run error"
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "leftValue": "",
                  "typeValidation": "loose",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "28dcd2bf-0281-4999-90ae-ad50a71ad2c4",
                    "leftValue": "={{ $json.notificationChannel }}",
                    "rightValue": "gmail",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "gmail"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": false,
                  "leftValue": "",
                  "typeValidation": "loose",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "0ea70991-d864-4e52-a2a2-9c55eb37792f",
                    "leftValue": "={{ $json.notificationChannel }}",
                    "rightValue": "slack",
                    "operator": {
                      "type": "string",
                      "operation": "equals",
                      "name": "filter.operator.equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "slack"
            }
          ]
        },
        "options": {
          "fallbackOutput": "none"
        }
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        1320,
        720
      ],
      "id": "2916e7e6-2cdc-45ec-a8c7-f663a93d3adb",
      "name": "Route the failure alert"
    },
    {
      "parameters": {
        "sendTo": "={{ $json.notifyEmail }}",
        "subject": "={{ $json.subject }}",
        "emailType": "text",
        "message": "={{ $json.body }}",
        "options": {}
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1600,
        640
      ],
      "id": "00182286-9f0c-4be2-a05f-d9a224157b59",
      "name": "Email a failure alert",
      "credentials": {
        "gmailOAuth2": {
          "id": "REPLACE_WITH_YOUR_GMAIL_CREDENTIAL_ID",
          "name": "Gmail account"
        }
      },
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "={{ $json.slackChannelId }}",
          "mode": "id"
        },
        "text": "={{ $json.slackText }}",
        "otherOptions": {}
      },
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [
        1600,
        820
      ],
      "id": "feda6b27-fdc9-4d27-b0a1-45c4161f71dc",
      "name": "Post a failure alert to Slack",
      "credentials": {
        "slackApi": {
          "id": "REPLACE_WITH_YOUR_SLACK_CREDENTIAL_ID",
          "name": "Slack account"
        }
      },
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    }
  ],
  "pinData": {},
  "connections": {
    "Every Monday 7am": {
      "main": [
        [
          {
            "node": "Configure filters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configure filters": {
      "main": [
        [
          {
            "node": "Read existing sheet rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read existing sheet rows": {
      "main": [
        [
          {
            "node": "Collect existing IDs",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format a run error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Collect existing IDs": {
      "main": [
        [
          {
            "node": "Fetch NY new LLC filings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch NY new LLC filings": {
      "main": [
        [
          {
            "node": "Keep only new matching leads",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format a run error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Keep only new matching leads": {
      "main": [
        [
          {
            "node": "Split out new rows",
            "type": "main",
            "index": 0
          },
          {
            "node": "Should send a summary?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split out new rows": {
      "main": [
        [
          {
            "node": "Append new rows to Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append new rows to Sheet": {
      "main": [
        [],
        [
          {
            "node": "Format a run error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Should send a summary?": {
      "main": [
        [
          {
            "node": "Route the notification",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Route the notification": {
      "main": [
        [
          {
            "node": "Email the run summary",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Post the run summary to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format a run error": {
      "main": [
        [
          {
            "node": "Route the failure alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route the failure alert": {
      "main": [
        [
          {
            "node": "Email a failure alert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Post a failure alert to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "callerPolicy": "workflowsFromSameOwner"
  },
  "versionId": "f30c2c69-36f2-4e74-a217-84fb4f3dfcb9",
  "meta": {
    "templateCredsSetupCompleted": false,
    "instanceId": "loomwise-template-export"
  },
  "tags": []
}
