This workflow corresponds to n8n.io template #17881 — we link there as the canonical source.
This workflow follows the Gmail → 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 →
{
"name": "AI Site Monitor - Free Starter",
"nodes": [
{
"id": "sticky-upsell",
"name": "Get the Full Version",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
-460
],
"parameters": {
"color": 4,
"width": 460,
"height": 420,
"content": "### This is the FREE Starter version\n\nShows the core trick: an AI reads any webpage and decides what's relevant - no CSS scraping, works on any site/language.\n\nIn this free version you edit the URL and criteria directly inside the nodes below (no config screen), and you run it manually (no schedule).\n\n**Want the full version?** It adds:\n- Daily automatic schedule\n- Duplicate detection (never get alerted on the same item twice)\n- One simple CONFIG screen (edit URL/criteria without touching code)\n- Full setup README + support\n\nGet it here: https://wlti.gumroad.com/l/ai-site-monitor ($39)"
},
"typeVersion": 1
},
{
"id": "sticky-fetch",
"name": "How it works: Fetch",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
250
],
"parameters": {
"color": 6,
"width": 420,
"height": 160,
"content": "### 1. Fetch\nGrabs the raw HTML of the page you want to monitor."
},
"typeVersion": 1
},
{
"id": "sticky-extract",
"name": "How it works: Extract",
"type": "n8n-nodes-base.stickyNote",
"position": [
440,
250
],
"parameters": {
"color": 6,
"width": 640,
"height": 160,
"content": "### 2. Extract\nSends the page content to Claude, which reads it and pulls out a structured list of items (title, description, link, date) - no custom scraper per site needed."
},
"typeVersion": 1
},
{
"id": "sticky-score",
"name": "How it works: Score & Filter",
"type": "n8n-nodes-base.stickyNote",
"position": [
1100,
250
],
"parameters": {
"color": 6,
"width": 860,
"height": 160,
"content": "### 3. Score & Filter\nEach item goes back to Claude to be judged against your criteria, written in plain English. Only items that score high enough move forward."
},
"typeVersion": 1
},
{
"id": "sticky-notify",
"name": "How it works: Notify",
"type": "n8n-nodes-base.stickyNote",
"position": [
1980,
280
],
"parameters": {
"color": 6,
"width": 640,
"height": 160,
"content": "### 4. Notify\nBuilds an HTML summary of the approved items and emails it to you."
},
"typeVersion": 1
},
{
"id": "trigger-manual",
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"position": [
0,
0
],
"parameters": {},
"typeVersion": 1
},
{
"id": "http-fonte",
"name": "HTTP Source (edit the URL above)",
"type": "n8n-nodes-base.httpRequest",
"position": [
220,
0
],
"parameters": {
"url": "https://REPLACE-WITH-THE-URL-OF-THE-SITE-YOU-WANT-TO-CHECK.com",
"method": "GET",
"options": {
"response": {
"response": {
"responseFormat": "text"
}
}
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "User-Agent",
"value": "Mozilla/5.0 (compatible; AIMonitorBot/1.0)"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "code-montar-extracao",
"name": "Build Extraction Request",
"type": "n8n-nodes-base.code",
"position": [
440,
0
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "// EDIT THIS if your site's items don't fit the default description below\nconst EXTRACTION_CRITERIA = \"Extract each relevant listing item found on the page (e.g. each ad, product, job posting, auction lot, property - depending on the monitored site).\";\n\nconst raw = $input.first().json.data || '';\nlet html = String(raw);\nhtml = html\n .replace(/<script[\\s\\S]*?<\\/script>/gi, ' ')\n .replace(/<style[\\s\\S]*?<\\/style>/gi, ' ')\n .replace(/<!--[\\s\\S]*?-->/g, ' ');\n\n// character limit to control token cost - increase if the page is large and items are getting missed\nconst MAX_CHARS = 100000;\nif (html.length > MAX_CHARS) html = html.slice(0, MAX_CHARS);\n\nconst systemText = \"You are an assistant that extracts structured listings from web pages based on raw HTML. \" + EXTRACTION_CRITERIA + \" For each item, extract: title, description (short summary), link (full URL - use the href found in the HTML; if relative, keep it as-is), date (if available, otherwise leave empty). Return ONLY a JSON array, no extra text, no markdown, no code blocks, using exactly these field names: title, description, link, date. If you find no items, return an empty array [].\";\n\nconst userText = \"PAGE HTML:\\n\" + html;\n\nconst requestBody = {\n model: 'claude-haiku-4-5',\n max_tokens: 8000,\n system: [ { type: 'text', text: systemText } ],\n messages: [ { role: 'user', content: userText } ]\n};\n\nreturn [{ json: { requestBody } }];\n",
"language": "javaScript"
},
"typeVersion": 2
},
{
"id": "http-anthropic-extracao",
"name": "Call Anthropic API (Extraction)",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"position": [
660,
0
],
"parameters": {
"url": "https://api.anthropic.com/v1/messages",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify($json.requestBody) }}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "anthropic-version",
"value": "2023-06-01"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "code-extrair-itens",
"name": "Extract Items",
"type": "n8n-nodes-base.code",
"position": [
880,
0
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "let items = [];\ntry {\n const resp = $input.first().json;\n const blocks = resp.content || [];\n const textBlock = blocks.find(b => b && b.type === 'text');\n let text = textBlock ? textBlock.text : '';\n text = text.trim().replace(/^```(?:json)?\\s*/i, '').replace(/```\\s*$/i, '').trim();\n const parsed = JSON.parse(text);\n items = Array.isArray(parsed) ? parsed : [];\n} catch (e) {\n items = [];\n}\nreturn items.map(v => ({ json: v }));\n",
"language": "javaScript"
},
"typeVersion": 2
},
{
"id": "code-montar-score",
"name": "Build AI Request (Score)",
"type": "n8n-nodes-base.code",
"position": [
1100,
0
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "// EDIT THIS: describe in plain English what you consider relevant\nconst CRITERIA_TEXT = \"Describe here what you consider relevant. Example: 'I'm looking for a 2 or 3 bedroom apartment in the Porto area, appraisal value under 150,000 euros, preferably unoccupied and with no condo debts mentioned in the auction notice.'\";\n\nconst SYSTEM_TEXT = \"You are an assistant that evaluates whether an item found on a web page is relevant according to the user's criteria.\\n\\nUSER CRITERIA:\\n\" + CRITERIA_TEXT;\n\nreturn $input.all().map(item => {\n const v = item.json;\n\n const userText = \"ITEM:\\n\" +\n \"Title: \" + (v.title || '') + \"\\n\" +\n \"Description: \" + (v.description || '') + \"\\n\" +\n \"Link: \" + (v.link || '') + \"\\n\\n\" +\n \"Evaluate this item's compatibility with the user's criteria. Return ONLY a JSON in the following format, no extra text, no markdown, no code blocks:\\n\" +\n \"{\\n\" +\n \" \\\"score\\\": (rating from 0 to 10, number),\\n\" +\n \" \\\"short_justification\\\": \\\"1-2 sentences explaining the score\\\"\\n\" +\n \"}\";\n\n const requestBody = {\n model: 'claude-haiku-4-5',\n max_tokens: 300,\n system: [ { type: 'text', text: SYSTEM_TEXT, cache_control: { type: 'ephemeral' } } ],\n messages: [ { role: 'user', content: userText } ]\n };\n\n return { json: { item: v, requestBody } };\n});\n",
"language": "javaScript"
},
"typeVersion": 2
},
{
"id": "http-anthropic-score",
"name": "Call Anthropic API (Score)",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"position": [
1320,
0
],
"parameters": {
"url": "https://api.anthropic.com/v1/messages",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify($json.requestBody) }}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "anthropic-version",
"value": "2023-06-01"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "code-extrair-score",
"name": "Extract Score",
"type": "n8n-nodes-base.code",
"position": [
1540,
0
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "const originals = $('Build AI Request (Score)').all();\nconst responses = $input.all();\nconst result = [];\n\nfor (let i = 0; i < responses.length; i++) {\n const original = (originals[i] && originals[i].json && originals[i].json.item) || {};\n const resp = responses[i].json;\n\n let scoreData = { score: null, short_justification: 'Error processing AI response.' };\n\n try {\n const blocks = resp.content || [];\n const textBlock = blocks.find(b => b && b.type === 'text');\n let text = textBlock ? textBlock.text : '';\n text = text.trim().replace(/^```(?:json)?\\s*/i, '').replace(/```\\s*$/i, '').trim();\n const parsed = JSON.parse(text);\n scoreData = {\n score: typeof parsed.score === 'number' ? parsed.score : null,\n short_justification: parsed.short_justification || ''\n };\n } catch (e) {}\n\n result.push({ json: Object.assign({}, original, scoreData) });\n}\n\nreturn result;\n",
"language": "javaScript"
},
"typeVersion": 2
},
{
"id": "if-filtrar-aprovados",
"name": "Filter Approved (score >= 7)",
"type": "n8n-nodes-base.if",
"position": [
1760,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "cond-score-cutoff",
"operator": {
"type": "number",
"operation": "gte"
},
"leftValue": "={{ $json.score }}",
"rightValue": 7
}
]
}
},
"typeVersion": 2
},
{
"id": "code-montar-resumo",
"name": "Build Summary",
"type": "n8n-nodes-base.code",
"position": [
1980,
120
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "function esc(s) {\n return String(s === undefined || s === null ? '' : s)\n .replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n}\n\nconst approved = $input.all().map(i => i.json);\n\nconst blocks = approved.map(v => {\n return (\n '<div style=\"border:1px solid #ddd;border-radius:8px;padding:16px;margin-bottom:20px;\">' +\n '<h3 style=\"margin:0 0 4px;\">' + esc(v.title) + '</h3>' +\n '<p style=\"margin:0 0 8px;color:#555;\">Score: <b>' + esc(v.score) + '</b>/10</p>' +\n '<p><b>Why it is relevant:</b> ' + esc(v.short_justification) + '</p>' +\n '<p>' + esc(v.description) + '</p>' +\n '<p><a href=\"' + esc(v.link) + '\">View original item</a></p>' +\n '</div>'\n );\n}).join('');\n\nconst html = '<h2>AI Site Monitor - Results (' + approved.length + (approved.length === 1 ? ' item' : ' items') + ')</h2>' + blocks;\n\nreturn [{ json: { total_approved: approved.length, summary_html: html } }];\n",
"language": "javaScript"
},
"typeVersion": 2
},
{
"id": "if-tem-itens",
"name": "Has Results",
"type": "n8n-nodes-base.if",
"position": [
2200,
120
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "cond-tem-itens",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.total_approved }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2
},
{
"id": "gmail-enviar-resumo",
"name": "Send Results Email (edit the address above)",
"type": "n8n-nodes-base.gmail",
"position": [
2420,
60
],
"parameters": {
"sendTo": "user@example.com",
"message": "={{ $json.summary_html }}",
"options": {},
"subject": "=AI Site Monitor - Results ({{ $json.total_approved }} items)",
"resource": "message",
"emailType": "html",
"operation": "send"
},
"typeVersion": 2.1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"connections": {
"Has Results": {
"main": [
[
{
"node": "Send Results Email (edit the address above)",
"type": "main",
"index": 0
}
],
[]
]
},
"Build Summary": {
"main": [
[
{
"node": "Has Results",
"type": "main",
"index": 0
}
]
]
},
"Extract Items": {
"main": [
[
{
"node": "Build AI Request (Score)",
"type": "main",
"index": 0
}
]
]
},
"Extract Score": {
"main": [
[
{
"node": "Filter Approved (score >= 7)",
"type": "main",
"index": 0
}
]
]
},
"Manual Trigger": {
"main": [
[
{
"node": "HTTP Source (edit the URL above)",
"type": "main",
"index": 0
}
]
]
},
"Build AI Request (Score)": {
"main": [
[
{
"node": "Call Anthropic API (Score)",
"type": "main",
"index": 0
}
]
]
},
"Build Extraction Request": {
"main": [
[
{
"node": "Call Anthropic API (Extraction)",
"type": "main",
"index": 0
}
]
]
},
"Call Anthropic API (Score)": {
"main": [
[
{
"node": "Extract Score",
"type": "main",
"index": 0
}
]
]
},
"Filter Approved (score >= 7)": {
"main": [
[
{
"node": "Build Summary",
"type": "main",
"index": 0
}
],
[]
]
},
"Call Anthropic API (Extraction)": {
"main": [
[
{
"node": "Extract Items",
"type": "main",
"index": 0
}
]
]
},
"HTTP Source (edit the URL above)": {
"main": [
[
{
"node": "Build Extraction Request",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow manually fetches a webpage, uses Anthropic Claude to extract listing-style items from the raw HTML and score them against your criteria, then emails an HTML summary of high-scoring matches via Gmail. Starts when you run the workflow manually. Requests the target…
Source: https://n8n.io/workflows/17881/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Gmail Workflow. Uses gmail, googleCalendar, executeWorkflowTrigger, whatsApp. Event-driven trigger; 61 nodes.
This workflow ingests proof-of-delivery and completion documents from Gmail or a webhook, extracts key fields with an OpenRouter vision model, reconciles them against Google Sheets dispatch data, arch
Splitout Code. Uses manualTrigger, httpRequest, stickyNote, splitOut. Event-driven trigger; 46 nodes.
Automate CSV imports into HubSpot without the mess. Powered by n8n. Supercharged by Pollup AI.
Echo Brand Voice Analysis (Processor) - TASK-074 Dec 10 Fix. Uses formTrigger, httpRequest, executeWorkflowTrigger, moveBinaryData. Event-driven trigger; 40 nodes.