{
  "name": "Monitor newly registered domains and typosquat variants for your brand with DomainKits",
  "nodes": [
    {
      "id": "c0000000-0000-0000-0000-000000000001",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -680,
        -260
      ],
      "parameters": {
        "width": 520,
        "height": 1140,
        "content": "## Monitor newly registered domains and typosquat variants with DomainKits\n\n### How it works\n\nEach day this workflow collects domain registration data for one domain you own, in two independent parts. Part one lists the domains registered in the last 10 days whose name contains your brand keyword, and looks up WHOIS and DNS for each: registrar, nameservers, A records and MX records. Part two generates the mechanical typosquat variants of your domain and reports which are registered, listed for sale or expiring. The workflow reports what the data says and leaves the interpretation to you.\n\n### Setup steps\n\n- Set your brand domain in the Set Brand node. Everything downstream derives from it, so this is the only value you need to edit.\n- Add your DomainKits API credentials to the four DomainKits nodes. One key covers every endpoint.\n- Configure the Send Report node with your SMTP credentials and recipients. The schedule is set to 14:00 UTC, by which time the previous day's registration data is complete.\n\n### Customization\n\nSwap the Send Report node for Slack, Jira or a ticketing system to route the report into an existing workflow. Add a Filter node after Prepare Candidates to narrow the list before the WHOIS and DNS lookups run.\n\n### Quota note\n\nEach run costs one search per section plus two lookups per candidate domain. A brand matching 30 new registrations a day uses roughly 60 lookups per run.\n\n### Data note\n\nResults contain domain names, registration dates, registrar names, nameservers and DNS records only. No registrant personal data is included (no names, emails, addresses or phone numbers), so the output is GDPR compliant and safe to forward or store."
      },
      "typeVersion": 1
    },
    {
      "id": "c0000000-0000-0000-0000-000000000002",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -40,
        -260
      ],
      "parameters": {
        "color": 7,
        "width": 1000,
        "height": 600,
        "content": "## Collect\n\nRuns daily and sets the brand domain once. Two independent lookups follow: yesterday's new registrations containing the brand keyword, and the typosquat variants of the brand domain."
      },
      "typeVersion": 1
    },
    {
      "id": "c0000000-0000-0000-0000-000000000003",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1020,
        -260
      ],
      "parameters": {
        "color": 7,
        "width": 760,
        "height": 600,
        "content": "## Look up each domain\n\nQueries WHOIS and DNS for every domain in the list and records the registrar, nameservers, A records and MX records as returned."
      },
      "typeVersion": 1
    },
    {
      "id": "c0000000-0000-0000-0000-000000000004",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1840,
        -260
      ],
      "parameters": {
        "color": 7,
        "width": 760,
        "height": 600,
        "content": "## Report and deliver\n\nLists the registrations newest first, summarises variant coverage separately, converts the records to a CSV attachment and emails both."
      },
      "typeVersion": 1
    },
    {
      "id": "c1000000-0000-0000-0000-000000000001",
      "name": "Every Day at 2pm",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        0,
        80
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "triggerAtHour": 14
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "c1000000-0000-0000-0000-000000000002",
      "name": "Set Brand",
      "type": "n8n-nodes-base.set",
      "position": [
        240,
        80
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "d1+12345678900-+1234567890",
              "name": "brand_domain",
              "type": "string",
              "value": "domainkits.com"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "c1000000-0000-0000-0000-000000000003",
      "name": "Find New Registrations",
      "type": "n8n-nodes-domainkits.domainKits",
      "position": [
        480,
        0
      ],
      "parameters": {
        "limit": 100,
        "filters": {
          "days_range": "0-10"
        },
        "keyword": "={{ $json.brand_domain.split('.')[0] }}",
        "resource": "nrd",
        "operation": "search",
        "searchMode": "keyword"
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "c1000000-0000-0000-0000-000000000004",
      "name": "Generate Typo Variants",
      "type": "n8n-nodes-domainkits.domainKits",
      "position": [
        480,
        180
      ],
      "parameters": {
        "domain": "={{ $json.brand_domain }}",
        "resource": "lookup",
        "operation": "typosquat"
      },
      "typeVersion": 1
    },
    {
      "id": "c1000000-0000-0000-0000-000000000010",
      "name": "Wait For Both",
      "type": "n8n-nodes-base.merge",
      "position": [
        720,
        80
      ],
      "parameters": {
        "mode": "chooseBranch",
        "options": {},
        "useDataOfInput": 1
      },
      "typeVersion": 3
    },
    {
      "id": "c1000000-0000-0000-0000-000000000005",
      "name": "Prepare Candidates",
      "type": "n8n-nodes-base.code",
      "position": [
        1060,
        80
      ],
      "parameters": {
        "jsCode": "const candidates = $('Find New Registrations').all()\n  .map(i => i.json)\n  .filter(d => d.domain);\n\n// One item carrying the whole batch: the bulk lookups run once for the list\n// instead of once per domain. The API caps a batch at 30 domains.\nreturn [{\n  json: {\n    domains: candidates.map(d => d.domain).slice(0, 30),\n    candidates,\n  },\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c1000000-0000-0000-0000-000000000006",
      "name": "Bulk WHOIS",
      "type": "n8n-nodes-domainkits.domainKits",
      "onError": "continueRegularOutput",
      "maxTries": 3,
      "position": [
        1300,
        80
      ],
      "parameters": {
        "domains": "={{ $json.domains }}",
        "resource": "lookup",
        "operation": "whoisBulk"
      },
      "retryOnFail": true,
      "typeVersion": 1,
      "alwaysOutputData": true,
      "waitBetweenTries": 2000
    },
    {
      "id": "c1000000-0000-0000-0000-000000000007",
      "name": "Bulk DNS",
      "type": "n8n-nodes-domainkits.domainKits",
      "onError": "continueRegularOutput",
      "maxTries": 3,
      "position": [
        1540,
        80
      ],
      "parameters": {
        "domains": "={{ $('Prepare Candidates').first().json.domains.slice(0, 20) }}",
        "resource": "lookup",
        "operation": "dnsBulk"
      },
      "retryOnFail": true,
      "typeVersion": 1,
      "alwaysOutputData": true,
      "waitBetweenTries": 2000
    },
    {
      "id": "c1000000-0000-0000-0000-000000000008",
      "name": "Build Report",
      "type": "n8n-nodes-base.code",
      "position": [
        1880,
        80
      ],
      "parameters": {
        "jsCode": "const base = $('Prepare Candidates').first().json.candidates;\n\nconst whoisByDomain = new Map();\nfor (const w of $('Bulk WHOIS').all().map(i => i.json)) {\n  if (w.domain) whoisByDomain.set(w.domain, w);\n}\nconst dnsByDomain = new Map();\nfor (const d of $('Bulk DNS').all().map(i => i.json)) {\n  if (d.domain) dnsByDomain.set(d.domain, d);\n}\n\nconst brand = $('Set Brand').first().json;\nconst keyword = brand.brand_domain.split('.')[0];\n\nconst records = base.map((b) => {\n  const w = whoisByDomain.get(b.domain) || {};\n  const dns = dnsByDomain.get(b.domain) || {};\n  return {\n    domain: b.domain,\n    registered_date: b.registered_date || '',\n    expiry_date: b.expiry_date || '',\n    registrar: w.registrar_name || '',\n    nameservers: (w.nameservers || dns.nameservers || []).filter(Boolean),\n    domain_status: dns.domain_status || '',\n  };\n});\n\nrecords.sort((a, b) => (b.registered_date || '').localeCompare(a.registered_date || '') || a.domain.localeCompare(b.domain));\n\nconst variantItems = $('Generate Typo Variants').all().map(i => i.json).filter(v => v.domain);\nconst registeredVariants = variantItems.filter(v => v.registered);\nconst generatedCount = variantItems[0]?._total ?? registeredVariants.length;\n\nconst byType = {};\nfor (const v of registeredVariants) {\n  byType[v.type] = (byType[v.type] || 0) + 1;\n}\nconst forSale = registeredVariants.filter(v => v.status === 'for_sale').map(v => v.domain);\nconst expiring = registeredVariants.filter(v => v.status === 'expiring').map(v => v.domain);\n\nconst lines = records.map(r => {\n  const parts = [\n    r.domain,\n    `  registered ${r.registered_date || 'not reported'}` + (r.expiry_date ? `, expires ${r.expiry_date}` : ''),\n    `  registrar: ${r.registrar || 'not reported'}`,\n  ];\n  parts.push(`  nameservers: ${r.nameservers.length ? r.nameservers.join(', ') : 'none'}`);\n  if (r.domain_status) parts.push(`  status: ${r.domain_status}`);\n  return parts.join('\\n');\n});\n\nconst body = [\n  `Domain data report for ${brand.brand_domain}`,\n  'Rolling window: domains registered in the last 10 days',\n  '',\n  `=== SECTION 1: Registrations containing \"${keyword}\" ===`,\n  '',\n  `${records.length} domains, newest first. Empty fields mean the lookup returned nothing.`,\n  '',\n  records.length ? lines.join('\\n\\n') : 'No registrations matched the keyword in the last 10 days.',\n  '',\n  '',\n  `=== SECTION 2: Typosquat variants of ${brand.brand_domain} ===`,\n  '',\n  'Mechanical variants generated from your domain. Independent of section 1: these come from your domain, not from recent registrations.',\n  '',\n  `${registeredVariants.length} of ${generatedCount} generated variants are registered.`,\n  '',\n  'By mutation type:',\n  Object.entries(byType).sort((a, b) => b[1] - a[1]).map(([t, n]) => `  ${t}: ${n}`).join('\\n'),\n  '',\n  `Listed for sale: ${forSale.length}${forSale.length ? ' (' + forSale.slice(0, 10).join(', ') + (forSale.length > 10 ? ', ...' : '') + ')' : ''}`,\n  `Expiring: ${expiring.length}${expiring.length ? ' (' + expiring.slice(0, 10).join(', ') + (expiring.length > 10 ? ', ...' : '') + ')' : ''}`,\n  '',\n  'Full records are attached as CSV.',\n].join('\\n');\n\nreturn [{\n  json: {\n    subject: `${brand.brand_domain}: ${records.length} registrations in the last 10 days`,\n    message: body,\n    records,\n    variant_summary: {\n      generated: generatedCount,\n      registered: registeredVariants.length,\n      by_type: byType,\n      for_sale: forSale,\n      expiring: expiring,\n    },\n  },\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c1000000-0000-0000-0000-000000000009",
      "name": "Send Report",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        2360,
        80
      ],
      "parameters": {
        "text": "={{ $('Build Report').first().json.message }}",
        "options": {
          "attachments": "data"
        },
        "subject": "={{ $('Build Report').first().json.subject }}",
        "toEmail": "you@example.com",
        "fromEmail": "user@example.com",
        "emailFormat": "text"
      },
      "typeVersion": 2.1
    },
    {
      "id": "c1000000-0000-0000-0000-000000000012",
      "name": "Split Findings",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        2120,
        180
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "records"
      },
      "typeVersion": 1
    },
    {
      "id": "c1000000-0000-0000-0000-000000000011",
      "name": "Findings To CSV",
      "type": "n8n-nodes-base.convertToFile",
      "position": [
        2120,
        0
      ],
      "parameters": {
        "options": {},
        "operation": "csv"
      },
      "typeVersion": 1.1
    }
  ],
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "Bulk DNS": {
      "main": [
        [
          {
            "node": "Build Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Brand": {
      "main": [
        [
          {
            "node": "Find New Registrations",
            "type": "main",
            "index": 0
          },
          {
            "node": "Generate Typo Variants",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Bulk WHOIS": {
      "main": [
        [
          {
            "node": "Bulk DNS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Report": {
      "main": [
        [
          {
            "node": "Split Findings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait For Both": {
      "main": [
        [
          {
            "node": "Prepare Candidates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Findings": {
      "main": [
        [
          {
            "node": "Findings To CSV",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Findings To CSV": {
      "main": [
        [
          {
            "node": "Send Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every Day at 2pm": {
      "main": [
        [
          {
            "node": "Set Brand",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Candidates": {
      "main": [
        [
          {
            "node": "Bulk WHOIS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find New Registrations": {
      "main": [
        [
          {
            "node": "Wait For Both",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Typo Variants": {
      "main": [
        [
          {
            "node": "Wait For Both",
            "type": "main",
            "index": 1
          }
        ]
      ]
    }
  }
}