This workflow corresponds to n8n.io template #11402 — we link there as the canonical source.
This workflow follows the Agent → Gmail 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 →
{
"id": "c5nWQIik9zbV5gHF",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "SEO Page Auditing with Decodo Scraper, GPT and Gmail Report",
"tags": [],
"nodes": [
{
"id": "a03e179c-c098-4509-87bb-666963a91c63",
"name": "When clicking \u2018Execute workflow\u2019",
"type": "n8n-nodes-base.manualTrigger",
"position": [
640,
496
],
"parameters": {},
"typeVersion": 1
},
{
"id": "4a313de4-ee02-46c8-b403-05cd52262d28",
"name": "\ud83d\udcdd OVERVIEW",
"type": "n8n-nodes-base.stickyNote",
"position": [
-48,
-64
],
"parameters": {
"color": "#E6F4EA",
"width": 520,
"height": 864,
"content": "## AI SEO Watchdog \u2014 Overview & Configuration\n\nThis workflow automatically audits web pages for SEO and generates an **executive-friendly SEO summary** for each page.\n\nIt reads URLs from Google Sheets, scrapes the page content using **Decodo**, reduces the raw HTML to key SEO elements, and analyzes them with an AI Agent. \nThe result is a structured SEO report with issues, quick wins, and recommendations, which is then:\n- Stored in Google Sheets\n- Sent by email as a formatted executive summary (Gmail)\n\n### How to configure it\n\n1. **Google Sheets**\n - Create one sheet with input URLs (one URL per row)\n - Create another sheet to store SEO results\n\n2. **Decodo**\n - Add your Decodo API credentials\n - The URL field is automatically taken from the input sheet \n[Decodo \u2013 Web Scraper for n8n](https://visit.decodo.com/raqXGD)\n\n3. **AI Agent**\n - Connect your LLM credentials (e.g. OpenAI or Gemini)\n - The prompt is already optimized for executive SEO summaries\n\n4. **Gmail**\n - Connect your Gmail account\n - Set the recipient email address\n - The email is sent in HTML format with clear formatting\n\nThe workflow can be executed manually or scheduled for continuous SEO monitoring."
},
"typeVersion": 1
},
{
"id": "02243c4d-7527-4de7-b40b-eb9f884ccb33",
"name": "\ud83d\udcdd DECODE NOTE",
"type": "n8n-nodes-base.stickyNote",
"position": [
656,
336
],
"parameters": {
"color": 7,
"width": 420,
"height": 124,
"content": "### Content Extraction \nURLs are read from Google Sheets.\nEach page is scraped using Decodo to reliably fetch the HTML content."
},
"typeVersion": 1
},
{
"id": "9a92dfdf-2d42-458b-bbd2-d4e0981057ff",
"name": "\ud83d\udcdd AI AGENT NOTE",
"type": "n8n-nodes-base.stickyNote",
"position": [
1264,
208
],
"parameters": {
"color": 7,
"width": 564,
"height": 96,
"content": "### AI Analysis\nJavaScript reduces the HTML to key SEO elements.\nThe AI Agent analyzes the data and generates an executive SEO summary."
},
"typeVersion": 1
},
{
"id": "25abeabc-5c44-48be-9fa3-612ed7c758df",
"name": "\ud83d\udcdd PARSE & REPAIR",
"type": "n8n-nodes-base.stickyNote",
"position": [
1936,
208
],
"parameters": {
"color": 7,
"width": 520,
"height": 116,
"content": "### Data Output \n\nResults are saved to Google Sheets.\nA formatted SEO summary is sent by email using Gmail.\n\n"
},
"typeVersion": 1
},
{
"id": "8b53cb51-5e11-4583-8564-f96771149446",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
1840,
592
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini",
"cachedResultName": "gpt-4.1-mini"
},
"options": {},
"builtInTools": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.3
},
{
"id": "ebf24d14-429d-4dd3-b92f-587929c411d2",
"name": "Decodo",
"type": "@decodo/n8n-nodes-decodo.decodo",
"position": [
1312,
368
],
"parameters": {
"url": "={{ $('Fetch URLs from Google Sheet').item.json.urls }}"
},
"credentials": {
"decodoApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "1df70380-51cb-4043-997f-9a9117e6491d",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
608,
-64
],
"parameters": {
"color": 7,
"width": 336,
"height": 352,
"content": "## Input"
},
"typeVersion": 1
},
{
"id": "9663551a-828e-400a-a1f1-fe0cdf1e8679",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
2560,
-240
],
"parameters": {
"color": 7,
"width": 832,
"height": 512,
"content": "## Email\n"
},
"typeVersion": 1
},
{
"id": "6a5b45be-938f-4f91-9ac3-687293dabf38",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
2624,
320
],
"parameters": {
"color": 7,
"width": 800,
"height": 240,
"content": "## Google sheet\n\n"
},
"typeVersion": 1
},
{
"id": "01bb0573-68c1-4fb9-bada-5cd757be4097",
"name": "Process URLs one by one",
"type": "n8n-nodes-base.splitInBatches",
"position": [
1088,
496
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "bdcf3fce-7872-4776-b41b-7bc7ff37e7db",
"name": "Extract SEO Elements from HTML",
"type": "n8n-nodes-base.code",
"position": [
1536,
368
],
"parameters": {
"jsCode": "/**\n * PRE-EXTRACT SEO (HTML -> compact payload)\n * Reads: $json.results[0].content\n * Returns: seo_compact_text + seo_fields\n */\n\nfunction decodeHtml(s) {\n if (!s || typeof s !== \"string\") return s;\n return s\n .replace(/&/g, \"&\")\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/>/g, \">\")\n .replace(/&#(\\d+);/g, (_, code) => String.fromCharCode(Number(code)));\n}\n\nfunction cleanSpaces(s) {\n return (s || \"\")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\nfunction stripTags(html) {\n return cleanSpaces(\n (html || \"\")\n .replace(/<script[\\s\\S]*?<\\/script>/gi, \" \")\n .replace(/<style[\\s\\S]*?<\\/style>/gi, \" \")\n .replace(/<!--[\\s\\S]*?-->/g, \" \")\n .replace(/<[^>]*>/g, \" \")\n );\n}\n\nfunction getFirstMatch(html, regex) {\n const m = html.match(regex);\n return m ? decodeHtml(m[1]).trim() : null;\n}\n\nfunction getAllMatches(html, regex, limit = 20) {\n const out = [];\n let m;\n while ((m = regex.exec(html)) !== null) {\n out.push(decodeHtml(m[1]).trim());\n if (out.length >= limit) break;\n }\n return out;\n}\n\nfunction truncate(s, maxChars) {\n if (!s) return \"\";\n return s.length > maxChars ? s.slice(0, maxChars) + \"\u2026\" : s;\n}\n\nconst html = $json?.results?.[0]?.content;\nif (!html || typeof html !== \"string\") {\n return [{\n json: {\n seo_compact_text: \"NO_HTML_INPUT\",\n seo_fields: { title: null, meta_description: null, h1: null, h2: [], canonical: null, og_title: null, og_description: null, text_excerpt: \"\" }\n }\n }];\n}\n\n// Basic head elements\nconst title = getFirstMatch(html, /<title[^>]*>([\\s\\S]*?)<\\/title>/i);\nconst meta_description =\n getFirstMatch(html, /<meta[^>]*name=[\"']description[\"'][^>]*content=[\"']([^\"']*)[\"']/i) ||\n getFirstMatch(html, /<meta[^>]*content=[\"']([^\"']*)[\"'][^>]*name=[\"']description[\"']/i);\n\nconst canonical =\n getFirstMatch(html, /<link[^>]*rel=[\"']canonical[\"'][^>]*href=[\"']([^\"']+)[\"']/i) ||\n getFirstMatch(html, /<link[^>]*href=[\"']([^\"']+)[\"'][^>]*rel=[\"']canonical[\"']/i);\n\n// OG tags\nconst og_title =\n getFirstMatch(html, /<meta[^>]*property=[\"']og:title[\"'][^>]*content=[\"']([^\"']*)[\"']/i) ||\n getFirstMatch(html, /<meta[^>]*content=[\"']([^\"']*)[\"'][^>]*property=[\"']og:title[\"']/i);\n\nconst og_description =\n getFirstMatch(html, /<meta[^>]*property=[\"']og:description[\"'][^>]*content=[\"']([^\"']*)[\"']/i) ||\n getFirstMatch(html, /<meta[^>]*content=[\"']([^\"']*)[\"'][^>]*property=[\"']og:description[\"']/i);\n\n// H1/H2 (simple regex extraction)\nconst h1 = getFirstMatch(html, /<h1[^>]*>([\\s\\S]*?)<\\/h1>/i);\nconst h2 = getAllMatches(html, /<h2[^>]*>([\\s\\S]*?)<\\/h2>/gi, 15).map(x => cleanSpaces(stripTags(x))).filter(Boolean);\n\n// Visible text excerpt (limited)\nconst visibleText = stripTags(html);\nconst text_excerpt = truncate(visibleText, 6000);\n\n// Build compact text for LLM\nconst lines = [];\nlines.push(\"SEO_PAGE_EXTRACT\");\nlines.push(\"---\");\nlines.push(`TITLE: ${title || \"null\"}`);\nlines.push(`META_DESCRIPTION: ${meta_description || \"null\"}`);\nlines.push(`CANONICAL: ${canonical || \"null\"}`);\nlines.push(`H1: ${h1 ? cleanSpaces(stripTags(h1)) : \"null\"}`);\nlines.push(`H2: ${h2.length ? h2.join(\" | \") : \"null\"}`);\nlines.push(`OG_TITLE: ${og_title || \"null\"}`);\nlines.push(`OG_DESCRIPTION: ${og_description || \"null\"}`);\nlines.push(\"---\");\nlines.push(\"VISIBLE_TEXT_EXCERPT:\");\nlines.push(text_excerpt || \"\");\n\nreturn [{\n json: {\n seo_compact_text: lines.join(\"\\n\"),\n seo_fields: {\n title,\n meta_description,\n canonical,\n h1: h1 ? cleanSpaces(stripTags(h1)) : null,\n h2,\n og_title,\n og_description,\n text_excerpt\n }\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "3d561a6f-3b1b-481b-a50c-416a25092b6d",
"name": "Generate AI SEO Executive Summary",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
1760,
368
],
"parameters": {
"text": "={{ $json.seo_compact_text }}",
"options": {
"systemMessage": "You are an SEO auditor producing an executive summary for non-technical stakeholders.\n\nInput is a compact SEO extract:\nTITLE, META_DESCRIPTION, CANONICAL, H1/H2, and a short visible-text excerpt.\n\nReturn ONLY valid JSON:\n{\n \"overall_status\": \"good\"|\"needs_attention\"|\"critical\",\n \"top_issues\": [string], // max 5 bullets, short\n \"quick_wins\": [string], // max 5 bullets, actionable\n \"title_recommendation\": string|null,\n \"meta_description_recommendation\": string|null,\n \"notes\": string|null // 1\u20132 lines\n}\n\nRules:\n- Keep it short and executive-friendly.\n- No markdown, no emojis.\n- If info is missing, explain briefly in notes.\n"
},
"promptType": "define"
},
"typeVersion": 3
},
{
"id": "7ee090c1-de05-4a8e-a501-69854521d0ce",
"name": "Parse & Validate AI JSON Output",
"type": "n8n-nodes-base.code",
"position": [
2112,
368
],
"parameters": {
"jsCode": "// Input esperado: items[0].json.output (string con ```json ... ```)\n// Output: 1 item con el JSON ya parseado en items[0].json\n\nfunction stripCodeFences(s) {\n if (!s || typeof s !== 'string') return s;\n // Quita ```json ... ``` o ``` ... ```\n return s.replace(/^```(?:json)?\\s*/i, '').replace(/\\s*```$/i, '').trim();\n}\n\nfunction extractFirstJSONObject(s) {\n // Extrae el primer bloque {...} v\u00e1lido\n const start = s.indexOf('{');\n const end = s.lastIndexOf('}');\n if (start === -1 || end === -1 || end <= start) throw new Error('No JSON object braces found');\n return s.slice(start, end + 1);\n}\n\nconst out = [];\nfor (const item of items) {\n let raw = item.json.output ?? item.json ?? '';\n if (typeof raw !== 'string') raw = JSON.stringify(raw);\n\n // Si viene envuelto en array tipo [ { output: \"...\" } ], deshazlo\n // (por si te llega el array entero aqu\u00ed)\n if (Array.isArray(item.json) && item.json.length && item.json[0].output) {\n raw = item.json[0].output;\n }\n\n raw = stripCodeFences(raw);\n const jsonStr = extractFirstJSONObject(raw);\n\n let parsed;\n try {\n parsed = JSON.parse(jsonStr);\n } catch (e) {\n // Intento de \u201crepair\u201d sencillo: elimina BOM y comas colgantes comunes\n const repaired = jsonStr\n .replace(/^\\uFEFF/, '')\n .replace(/,\\s*}/g, '}')\n .replace(/,\\s*]/g, ']');\n parsed = JSON.parse(repaired);\n }\n\n out.push({ json: parsed });\n}\nreturn out;\n"
},
"typeVersion": 2
},
{
"id": "1e82b9a5-ddc6-4b4e-84df-0dc201ebdd00",
"name": "Save SEO Report to Google Sheets1",
"type": "n8n-nodes-base.googleSheets",
"position": [
2560,
496
],
"parameters": {
"columns": {
"value": {
"notes": "={{ $('Parse & Validate AI JSON Output').item.json.notes }}",
"status": "={{ $('Parse & Validate AI JSON Output').item.json.overall_status }}",
"brand_url": "={{ $('Fetch URLs from Google Sheet').item.json.urls }}",
"quick_wins": "={{ $('Parse & Validate AI JSON Output').item.json.quick_wins }}",
"top_issues": "={{ $('Parse & Validate AI JSON Output').item.json.top_issues }}",
"meta_description": "={{ $('Parse & Validate AI JSON Output').item.json.meta_description_recommendation }}",
"title_recommendation": "={{ $('Parse & Validate AI JSON Output').item.json.title_recommendation }}"
},
"schema": [
{
"id": "brand_url",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "brand_url",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "status",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "top_issues",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "top_issues",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "quick_wins",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "quick_wins",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "title_recommendation",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "title_recommendation",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "meta_description",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "meta_description",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "notes",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "notes",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1LPYN31Dtt5ZFF39yB4mJDvRG40Ga2sow_MlLcNx_DOg/edit#gid=0",
"cachedResultName": "Hoja 1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1LPYN31Dtt5ZFF39yB4mJDvRG40Ga2sow_MlLcNx_DOg",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1LPYN31Dtt5ZFF39yB4mJDvRG40Ga2sow_MlLcNx_DOg/edit?usp=drivesdk",
"cachedResultName": "SEO Analyzer"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "a53ecfff-8e78-4ee0-8b76-6fe32ec92e71",
"name": "Send SEO Report by Email",
"type": "n8n-nodes-base.gmail",
"position": [
2336,
368
],
"parameters": {
"sendTo": "user@example.com",
"message": "=<p>Hi,</p>\n\n<p>\nHere is the latest <strong>SEO Watchdog executive summary</strong> for:\n<br>\n<strong>this page</strong>\n</p>\n\n<hr>\n\n<p>\n<strong>Status:</strong>\n<span style=\"color:#d97706;\"><strong>Needs attention</strong></span>\n</p>\n\n<h3>\ud83d\udd0e Top issues</h3>\n<ul>\n <li><strong>Title</strong> is too long and may be truncated in search results</li>\n <li><strong>Meta description</strong> is lengthy and lacks clear calls-to-action</li>\n <li><strong>H1</strong> is generic and does not strongly reflect primary keywords</li>\n <li><strong>Canonical URL</strong> points only to homepage, no specific page URL provided</li>\n <li><strong>H2 headings</strong> are numerous and unfocused, reducing clarity</li>\n</ul>\n\n<h3>\u26a1 Quick wins</h3>\n<ul>\n <li>Shorten the <strong>title</strong> to under 60 characters focusing on the main keyword</li>\n <li>Rewrite the <strong>meta description</strong> to under 160 characters with clear benefits and CTAs</li>\n <li>Refine the <strong>H1</strong> to better reflect primary keywords and user intent</li>\n <li>Ensure the <strong>canonical URL</strong> matches the specific page URL</li>\n <li>Consolidate <strong>H2 tags</strong> to emphasize main offerings and keywords</li>\n</ul>\n\n<h3>\ud83c\udff7\ufe0f Recommended title</h3>\n<p>\n<strong>Best API for Historical Stock Market Prices & Financial Data | Free Trial</strong>\n</p>\n\n<h3>\ud83d\udcdd Recommended meta description</h3>\n<p>\nAccess historical and real-time stock prices for 150,000+ tickers with flexible plans.\n<strong>Start your free trial today.</strong>\n</p>\n\n<h3>\ud83d\udccc Notes</h3>\n<p>\nSEO elements are present but need refinement for optimal search visibility and user engagement.\nVisible text content is comprehensive but dense.\n</p>\n\n<hr>\n\n<p style=\"font-size:12px;color:#666;\">\n\u2014 Sent automatically by <strong>n8n SEO Watchdog</strong><br>\n<a href=\"https://n8n.io\" target=\"_blank\">https://n8n.io</a>\n</p>",
"options": {},
"subject": "=SEO Watchdog Report \u2014 {{$now}}"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.2
},
{
"id": "47b3a83e-ec11-47b7-9eaa-6ed5b2057ccb",
"name": "Fetch URLs from Google Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
864,
496
],
"parameters": {
"options": {
"dataLocationOnSheet": {
"values": {
"rangeDefinition": "specifyRange"
}
}
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1mIVyGQXXVR92IhlvaQfdlGqJEU0lsJHHmlZONyMzU9c/edit#gid=0",
"cachedResultName": "Hoja 1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1mIVyGQXXVR92IhlvaQfdlGqJEU0lsJHHmlZONyMzU9c",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1mIVyGQXXVR92IhlvaQfdlGqJEU0lsJHHmlZONyMzU9c/edit?usp=drivesdk",
"cachedResultName": "urls_to_scrape"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "d848ed4c-1a33-4f90-bc5a-890b9638fce1",
"connections": {
"Decodo": {
"main": [
[
{
"node": "Extract SEO Elements from HTML",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "Generate AI SEO Executive Summary",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Process URLs one by one": {
"main": [
[],
[
{
"node": "Decodo",
"type": "main",
"index": 0
}
]
]
},
"Send SEO Report by Email": {
"main": [
[
{
"node": "Save SEO Report to Google Sheets1",
"type": "main",
"index": 0
}
]
]
},
"Fetch URLs from Google Sheet": {
"main": [
[
{
"node": "Process URLs one by one",
"type": "main",
"index": 0
}
]
]
},
"Extract SEO Elements from HTML": {
"main": [
[
{
"node": "Generate AI SEO Executive Summary",
"type": "main",
"index": 0
}
]
]
},
"Parse & Validate AI JSON Output": {
"main": [
[
{
"node": "Send SEO Report by Email",
"type": "main",
"index": 0
}
]
]
},
"Generate AI SEO Executive Summary": {
"main": [
[
{
"node": "Parse & Validate AI JSON Output",
"type": "main",
"index": 0
}
]
]
},
"Save SEO Report to Google Sheets1": {
"main": [
[
{
"node": "Process URLs one by one",
"type": "main",
"index": 0
}
]
]
},
"When clicking \u2018Execute workflow\u2019": {
"main": [
[
{
"node": "Fetch URLs from Google Sheet",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
decodoApigmailOAuth2googleSheetsOAuth2ApiopenAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow automatically audits web pages for SEO issues and generates an executive-friendly SEO report using AI.
Source: https://n8n.io/workflows/11402/ — 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.
This workflow contains community nodes that are only compatible with the self-hosted version of n8n.
This workflow contains community nodes that are only compatible with the self-hosted version of n8n.
This template extracts high-intent SEO keywords from any web page and turns them into a ranked keyword list you can use for content planning, landing pages, and SEO strategy.
This workflow is designed for Scrum Masters and Agile Coaches who prepare and coordinate Sprint Planning sessions, using Google Calendar, Google Sheets, and OpenAI.
This workflow is designed for Scrum Masters, Agile Coaches, and Product Owners who want to automate backlog refinement preparation using Google Sheets, Gmail, and OpenAI. It’s ideal for teams seeking