This workflow corresponds to n8n.io template #11491 — 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 Monitoring Workflow with AI and Email Reporting",
"tags": [],
"nodes": [
{
"id": "a03e179c-c098-4509-87bb-666963a91c63",
"name": "When clicking \u2018Execute workflow\u2019",
"type": "n8n-nodes-base.manualTrigger",
"position": [
640,
492
],
"parameters": {},
"typeVersion": 1
},
{
"id": "7ee090c1-de05-4a8e-a501-69854521d0ce",
"name": "Code in JavaScript",
"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": "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": [
672,
384
],
"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": "47b3a83e-ec11-47b7-9eaa-6ed5b2057ccb",
"name": "Get row(s) in sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
864,
492
],
"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
},
{
"id": "01bb0573-68c1-4fb9-bada-5cd757be4097",
"name": "Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
1088,
492
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "8b53cb51-5e11-4583-8564-f96771149446",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
1832,
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": "={{ $('Get row(s) in sheet').item.json.urls }}"
},
"credentials": {
"decodoApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "3d561a6f-3b1b-481b-a50c-416a25092b6d",
"name": "AI Agent",
"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": "1e82b9a5-ddc6-4b4e-84df-0dc201ebdd00",
"name": "Append row in sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
2560,
492
],
"parameters": {
"columns": {
"value": {
"notes": "={{ $('Code in JavaScript').item.json.notes }}",
"status": "={{ $('Code in JavaScript').item.json.overall_status }}",
"brand_url": "={{ $('Get row(s) in sheet').item.json.urls }}",
"quick_wins": "={{ $('Code in JavaScript').item.json.quick_wins }}",
"top_issues": "={{ $('Code in JavaScript').item.json.top_issues }}",
"meta_description": "={{ $('Code in JavaScript').item.json.meta_description_recommendation }}",
"title_recommendation": "={{ $('Code in JavaScript').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 a message",
"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": "1df70380-51cb-4043-997f-9a9117e6491d",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
624,
-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": "bdcf3fce-7872-4776-b41b-7bc7ff37e7db",
"name": "Code in JavaScript1",
"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": "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
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "60cac515-06f2-4072-a503-d0aac04b23a8",
"connections": {
"Decodo": {
"main": [
[
{
"node": "Code in JavaScript1",
"type": "main",
"index": 0
}
]
]
},
"AI Agent": {
"main": [
[
{
"node": "Code in JavaScript",
"type": "main",
"index": 0
}
]
]
},
"Send a message": {
"main": [
[
{
"node": "Append row in sheet",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[],
[
{
"node": "Decodo",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Code in JavaScript": {
"main": [
[
{
"node": "Send a message",
"type": "main",
"index": 0
}
]
]
},
"Append row in sheet": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript1": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"Get row(s) in sheet": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"When clicking \u2018Execute workflow\u2019": {
"main": [
[
{
"node": "Get row(s) in 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 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.
Source: https://n8n.io/workflows/11491/ — 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 workflow automatically audits web pages for SEO issues and generates an executive-friendly SEO report using AI.
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