AutomationFlowsWeb Scraping › Monitor Website Changes with Polydoc Screenshots and Email Alerts

Monitor Website Changes with Polydoc Screenshots and Email Alerts

Bypolydoc.tech @polydoc on n8n.io

This workflow checks a website URL every 6 hours, detects content changes by hashing the normalized HTML, and when a change is found it uses PolyDoc to capture a full-page screenshot and emails the PNG as an attachment. Runs every 6 hours on a schedule. Sets the target URL to…

Cron / scheduled trigger★★★★☆ complexity17 nodesHTTP RequestN8N Nodes PolydocEmail Send
Web Scraping Trigger: Cron / scheduled Nodes: 17 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #16391 — 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
{
  "name": "PolyDoc - Website change monitor (screenshot on change)",
  "nodes": [
    {
      "id": "d1111111-1111-4111-8111-111111111111",
      "name": "Every 6 hours",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        240,
        320
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 6
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "d2222222-2222-4222-8222-222222222222",
      "name": "Settings",
      "type": "n8n-nodes-base.set",
      "position": [
        440,
        320
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "set-url",
              "name": "url",
              "type": "string",
              "value": "https://example.com"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "d3333333-3333-4333-8333-333333333333",
      "name": "Fetch page",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        640,
        320
      ],
      "parameters": {
        "url": "={{ $json.url }}",
        "method": "GET",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "d4444444-4444-4444-8444-444444444444",
      "name": "Detect change",
      "type": "n8n-nodes-base.code",
      "position": [
        840,
        320
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "  const res = $input.first().json;\n  const html = res.data ?? (typeof res === 'string' ? res : JSON.stringify(res));\n  const normalized = String(html)\n    .replace(/<script[\\s\\S]*?<\\/script>/gi, '')\n    .replace(/<style[\\s\\S]*?<\\/style>/gi, '')\n    .replace(/<!--[\\s\\S]*?-->/g, '')\n    .replace(/\\s+/g, ' ')\n    .trim();\n\n  // djb2-xor hash: no imports, works in the sandboxed Code node.\n  let hash = 5381;\n  for (let i = 0; i < normalized.length; i++) {\n    hash = ((hash * 33) ^ normalized.charCodeAt(i)) >>> 0;\n  }\n  const current = String(hash);\n\n  const store = $getWorkflowStaticData('global');\n  const previous = store.lastHash;\n  store.lastHash = current;\n\n  return [\n    {\n      json: {\n        url: $('Settings').first().json.url,\n        changed: previous !== current,\n        firstRun: previous === undefined,\n        previousHash: previous ?? null,\n        currentHash: current,\n      },\n    },\n  ];"
      },
      "typeVersion": 2
    },
    {
      "id": "d5555555-5555-4555-8555-555555555555",
      "name": "Changed?",
      "type": "n8n-nodes-base.if",
      "position": [
        1040,
        320
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-changed",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.changed }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "d6666666-6666-4666-8666-666666666666",
      "name": "Screenshot page",
      "type": "n8n-nodes-polydoc.polyDoc",
      "position": [
        1280,
        320
      ],
      "parameters": {
        "url": "={{ $json.url }}",
        "operation": "screenshot",
        "sourceType": "url",
        "deliveryMode": "download",
        "additionalFields": {
          "filename": "snapshot.png"
        },
        "screenshotOptions": {
          "fullPage": true,
          "imageType": "png",
          "viewportWidth": 1280,
          "viewportHeight": 800
        },
        "binaryPropertyName": "data"
      },
      "typeVersion": 1
    },
    {
      "id": "d7777777-7777-4777-8777-777777777777",
      "name": "Email screenshot",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        1480,
        320
      ],
      "parameters": {
        "text": "=The watched page changed since the last check.\n\nURL: {{ $('Detect change').first().json.url }}\n\nThe current full-page screenshot is attached.",
        "options": {
          "attachments": "data"
        },
        "subject": "=Change detected: {{ $('Detect change').first().json.url }}",
        "toEmail": "you@example.com",
        "fromEmail": "user@example.com",
        "emailFormat": "text"
      },
      "typeVersion": 2.1
    },
    {
      "id": "d8888888-8888-4888-8888-888888888888",
      "name": "No change",
      "type": "n8n-nodes-base.noOp",
      "position": [
        1280,
        700
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "d9999999-9999-4999-8999-999999999999",
      "name": "Read me first",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        200,
        -260
      ],
      "parameters": {
        "width": 1380,
        "height": 360,
        "content": "## Website change monitor with screenshots\n\n**What it does:** Polls a web page on a schedule, detects whether its content changed since the last run, and captures a full-page PolyDoc screenshot **only when it changed**, so you never spend a screenshot on an unchanged page. The snapshot is emailed to you.\n\n**Who it's for:** Anyone tracking competitor pages, pricing, docs, or status pages who wants a visual record of every change.\n\n### Setup\n1. Set the **url** to watch in the *Settings* node.\n2. Add your **PolyDoc API** credential on *Screenshot page* (free key at dashboard.polydoc.tech).\n3. Add an **SMTP** credential on *Email screenshot*, or swap it for Slack / S3 / Google Drive.\n\nStart free: https://polydoc.tech"
      },
      "typeVersion": 1
    },
    {
      "id": "da111111-1111-4111-8111-111111111111",
      "name": "Section: Watch",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        200,
        180
      ],
      "parameters": {
        "color": 4,
        "width": 400,
        "height": 260,
        "content": "## 1. Watch on a schedule\n\nRuns every 6 hours. The URL to watch lives in **Settings**, so you edit it in one place."
      },
      "typeVersion": 1
    },
    {
      "id": "db222222-2222-4222-8222-222222222222",
      "name": "Section: Detect",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        620,
        180
      ],
      "parameters": {
        "color": 4,
        "width": 620,
        "height": 260,
        "content": "## 2. Detect a real change\n\nFetch the page, strip scripts, styles and whitespace, hash it, and compare to the last run (stored in workflow static data). Only a genuine content change continues."
      },
      "typeVersion": 1
    },
    {
      "id": "dc333333-3333-4333-8333-333333333333",
      "name": "Section: Capture",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1260,
        180
      ],
      "parameters": {
        "color": 4,
        "width": 420,
        "height": 260,
        "content": "## 3. Capture & send\n\nPolyDoc renders a full-page PNG of the changed page, which is emailed as an attachment."
      },
      "typeVersion": 1
    },
    {
      "id": "de555555-5555-4555-8555-555555555555",
      "name": "Setup: URL",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        400,
        470
      ],
      "parameters": {
        "color": 7,
        "width": 180,
        "height": 110,
        "content": "**Set the URL** you want to watch here."
      },
      "typeVersion": 1
    },
    {
      "id": "df666666-6666-4666-8666-666666666666",
      "name": "Note: first run",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        660,
        470
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 130,
        "content": "First run and every manual test count as a change, so the screenshot branch runs right away. State then persists between scheduled runs."
      },
      "typeVersion": 1
    },
    {
      "id": "e0777777-7777-4777-8777-777777777777",
      "name": "Setup: PolyDoc key",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1240,
        470
      ],
      "parameters": {
        "color": 7,
        "width": 190,
        "height": 110,
        "content": "**Add your PolyDoc API credential.** Free key at dashboard.polydoc.tech"
      },
      "typeVersion": 1
    },
    {
      "id": "e1888888-8888-4888-8888-888888888888",
      "name": "Setup: email",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1450,
        470
      ],
      "parameters": {
        "color": 7,
        "width": 210,
        "height": 110,
        "content": "**Add an SMTP credential**, or swap this node for Slack / S3 / Google Drive."
      },
      "typeVersion": 1
    },
    {
      "id": "e2999999-9999-4999-8999-999999999999",
      "name": "Note: no change",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1420,
        700
      ],
      "parameters": {
        "color": 7,
        "width": 250,
        "height": 110,
        "content": "**Nothing changed.** No screenshot is taken, the run ends quietly."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "Changed?": {
      "main": [
        [
          {
            "node": "Screenshot page",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No change",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Settings": {
      "main": [
        [
          {
            "node": "Fetch page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch page": {
      "main": [
        [
          {
            "node": "Detect change",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Detect change": {
      "main": [
        [
          {
            "node": "Changed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every 6 hours": {
      "main": [
        [
          {
            "node": "Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Screenshot page": {
      "main": [
        [
          {
            "node": "Email screenshot",
            "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 checks a website URL every 6 hours, detects content changes by hashing the normalized HTML, and when a change is found it uses PolyDoc to capture a full-page screenshot and emails the PNG as an attachment. Runs every 6 hours on a schedule. Sets the target URL to…

Source: https://n8n.io/workflows/16391/ — 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

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

Rank Drop Alert Monitor. Uses googleSheets, httpRequest, slack, emailSend. Scheduled trigger; 11 nodes.

Google Sheets, HTTP Request, Slack +1
Web Scraping

Proxmox Infrastructure Monitoring. Uses httpRequest, emailSend. Scheduled trigger; 7 nodes.

HTTP Request, Email Send
Web Scraping

AlphaAI: daily watchlist news digest -> email. Uses httpRequest, emailSend. Scheduled trigger; 6 nodes.

HTTP Request, Email Send
Web Scraping

Uptime Alerting & Notification. Uses httpRequest, emailSend. Scheduled trigger; 4 nodes.

HTTP Request, Email Send