{
  "name": "Enrich domains in Google Sheets with email provider and tech stack using Apify",
  "nodes": [
    {
      "parameters": {},
      "id": "274e5cd8-768d-5162-adcc-0b7c9bd66e0c",
      "name": "Run manually",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        -100
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "daysInterval": 1,
              "triggerAtHour": 10
            }
          ]
        }
      },
      "id": "54a47f1a-bb71-5ed2-9389-c4195b3018dc",
      "name": "Every day at 10:00",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        100
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "REPLACE_WITH_YOUR_SPREADSHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Domains",
          "mode": "name"
        },
        "options": {}
      },
      "id": "57aa2d6b-8b9a-56dd-a433-645f2da144aa",
      "name": "Read the Domains sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        240,
        0
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_WITH_YOUR_GOOGLE_SHEETS_CREDENTIAL",
          "name": "Google Sheets account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Send only rows that have not been enriched yet (empty checkedAt), so re-runs cost nothing.\nconst domains = [...new Set(\n  $input.all()\n    .map((i) => i.json)\n    .filter((r) => r.domain && !r.checkedAt)\n    .map((r) => String(r.domain).trim())\n    .filter(Boolean),\n)].slice(0, 5000);\nif (domains.length === 0) return [];\nreturn [{ json: { domains } }];"
      },
      "id": "7067f0a8-39c8-5e0d-8163-31070f302e11",
      "name": "Pick new domains",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        0
      ]
    },
    {
      "parameters": {
        "resource": "Actors",
        "operation": "Run actor and get dataset",
        "actorId": {
          "__rl": true,
          "value": "XRAfTUezTqloGBQwp",
          "mode": "id"
        },
        "customBody": "={{ JSON.stringify({ domains: $json.domains, checkWebsite: true, checkSsl: true, checkRegistration: true, maxResults: 0 }) }}",
        "maxTotalChargeUsd": 2
      },
      "id": "40cfb6ae-30fc-5727-8c58-1e041d979209",
      "name": "Look up the domains",
      "type": "@apify/n8n-nodes-apify.apify",
      "typeVersion": 2,
      "position": [
        720,
        0
      ],
      "credentials": {
        "apifyApi": {
          "id": "REPLACE_WITH_YOUR_APIFY_CREDENTIAL",
          "name": "Apify account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Flatten arrays to text and keep the columns worth having in a sheet.\n// `domain` is set to the exact value from your sheet so the update hits the right row.\nconst list = (a) => (Array.isArray(a) ? a.join(', ') : a ?? '');\nconst asked = $('Pick new domains').first().json.domains;\nconst byInput = new Map($input.all().map((i) => i.json).filter((d) => d.input).map((d) => [d.input, d]));\nreturn asked.map((domain) => {\n  const d = byInput.get(domain);\n  if (!d) return { json: { domain, resolves: 'not checked (see the Apify run log)', checkedAt: new Date().toISOString() } };\n  return { json: {\n    domain,\n    resolves: d.resolves,\n    emailProvider: d.emailProvider ?? '',\n    emailSecurityGateway: d.emailSecurityGateway ?? '',\n    hasSpf: d.hasSpf ?? '',\n    dmarcPolicy: d.dmarcPolicy ?? '',\n    dnsProvider: d.dnsProvider ?? '',\n    hosting: d.hosting ?? '',\n    cdn: d.cdn ?? '',\n    cms: d.cms ?? '',\n    ecommerce: list(d.ecommerce),\n    analytics: list(d.analytics),\n    marketing: list(d.marketing),\n    payments: list(d.payments),\n    technologies: list(d.technologies),\n    siteTitle: d.title ?? '',\n    registrar: d.registrar ?? '',\n    registeredAt: d.registeredAt ?? '',\n    sslDaysToExpiry: d.sslDaysToExpiry ?? '',\n    checkedAt: d.checkedAt ?? new Date().toISOString(),\n  } };\n});"
      },
      "id": "6b06f581-6048-53b9-9253-ab74e3cbb63e",
      "name": "Flatten for the sheet",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        960,
        0
      ]
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "value": "REPLACE_WITH_YOUR_SPREADSHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Domains",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [
            "domain"
          ],
          "schema": []
        },
        "options": {}
      },
      "id": "8a7caee7-c53f-5370-8892-872c9e13b009",
      "name": "Write results to the sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1200,
        0
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_WITH_YOUR_GOOGLE_SHEETS_CREDENTIAL",
          "name": "Google Sheets account"
        }
      }
    },
    {
      "parameters": {
        "content": "## Read the sheet\nOnly rows without `checkedAt` are sent.",
        "height": 460,
        "width": 680,
        "color": 7
      },
      "id": "40a85288-d663-5512-a95b-5392520a6770",
      "name": "Section 1",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -50,
        -230
      ]
    },
    {
      "parameters": {
        "content": "## Look up and write back\nOne row per domain, matched on `domain`.",
        "height": 260,
        "width": 680,
        "color": 7
      },
      "id": "7cd3d8d2-1c97-5cec-a74f-4320de60873f",
      "name": "Section 2",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        670,
        -130
      ]
    },
    {
      "parameters": {
        "content": "## Enrich domains in Google Sheets with email provider and tech stack\n\nFor sales and marketing teams qualifying leads, agencies scoping new clients, and anyone with a column of company websites.\n\n### How it works\n1. Start it by hand or let the daily schedule run. It reads the **Domains** tab and keeps rows whose `checkedAt` is empty.\n2. The **domain-intel** Actor checks each domain's public DNS records and homepage: email provider, SPF and DMARC, DNS and hosting provider, CDN, CMS, shop platform, analytics and about 100 other technologies, SSL expiry and registrar.\n3. A Code node turns the lists into text and writes one row back per domain, matched on the `domain` column.\n\n### Setup\n1. Add your own Apify API token as an *Apify API* credential.\n2. Make a Google Sheet with a tab named **Domains** and the header row `domain`, `checkedAt`. Domains, URLs and email addresses all work.\n3. Paste the sheet ID into both Sheets nodes and pick your Google credential.\n\n### Requirements\nApify account (free plan: up to 100 domains per run) and Google Sheets. The Actor charges $3 per 1,000 domains; the node caps each run at $2. Only public records are read; no personal contact data is collected.\n\n**Community node:** uses the verified Apify node (`@apify/n8n-nodes-apify`). On n8n Cloud it installs from the canvas; on self-hosted n8n install it under Settings > Community nodes.\n\n### Customization tips\nSet `checkWebsite` to false for a faster DNS-only run, or remove columns you don't need in **Flatten for the sheet**.\n\nMade by locaihost, a small studio that builds its tools with AI agents.",
        "height": 817,
        "width": 560
      },
      "id": "f012ee4e-836d-5581-afac-469d0f0deed7",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -670,
        -230
      ]
    }
  ],
  "connections": {
    "Run manually": {
      "main": [
        [
          {
            "node": "Read the Domains sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every day at 10:00": {
      "main": [
        [
          {
            "node": "Read the Domains sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read the Domains sheet": {
      "main": [
        [
          {
            "node": "Pick new domains",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pick new domains": {
      "main": [
        [
          {
            "node": "Look up the domains",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Look up the domains": {
      "main": [
        [
          {
            "node": "Flatten for the sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flatten for the sheet": {
      "main": [
        [
          {
            "node": "Write results to the sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}
