AutomationFlowsAI & RAG › Monitor AI Brand Visibility and Geo Gaps with Openai, Gmail and Slack

Monitor AI Brand Visibility and Geo Gaps with Openai, Gmail and Slack

ByRahul Joshi @rahul08 on n8n.io

This workflow runs daily to test whether Fairy Frills appears in AI shopping recommendations by querying OpenAI with 10 brand-focused questions (with and without web search), extracting mention and competitor data, generating a GEO gap analysis, and emailing a consolidated HTML…

Cron / scheduled trigger★★★★☆ complexityAI-powered21 nodesOpenAIGmailSlackError Trigger
AI & RAG Trigger: Cron / scheduled Nodes: 21 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #16741 — we link there as the canonical source.

This workflow follows the Error Trigger → 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 →

Download .json
{
  "id": "peWpPIdIBCgkAqDc",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "AI Brand Visibility & GEO Monitoring System",
  "tags": [],
  "nodes": [
    {
      "id": "cbbb7b19-ba66-495b-bdbe-be4ca4a793be",
      "name": "Section \u2013 GEO Gap Analyzer & Report",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2512,
        6224
      ],
      "parameters": {
        "color": 7,
        "width": 1444,
        "height": 412,
        "content": "## \ud83c\udfaf GEO Gap Analysis & Report\nA second AI prompt reverse-engineers why competitors ranked \u2014 extracting ranking signals, content types referenced, and exact phrases used. Results are deduplicated across all 10 queries, then compiled into a prioritised HTML email with action items, content gaps, and phrases to add to Fairy Frills content."
      },
      "typeVersion": 1
    },
    {
      "id": "c62646ec-6994-44d1-a177-571464e01088",
      "name": "\u23f0 Daily Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        752,
        6352
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 24
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "3a2fbf94-b900-49e0-9d42-aeef9322f215",
      "name": "\ud83d\udccb Build Brand Queries \u2013 Fairy Frills",
      "type": "n8n-nodes-base.code",
      "position": [
        1008,
        6352
      ],
      "parameters": {
        "jsCode": "// ============================================================\n// FAIRY FRILLS \u2013 AI Brand Mention Query Builder\n// ============================================================\n\nconst brand = 'Fairy Frills';\nconst brandUrl = 'fairyfrills.in';\nconst competitors = [\n  'Hopscotch',\n  'FirstCry',\n  'Mothercare India',\n  'Mimi & Lula',\n  'Little Muffet',\n  'Bubblegum Club',\n  'Olele'\n];\n\nconst queries = [\n  'What are the best brands for handcrafted baby girl birthday dresses in India?',\n  'Where can I buy mother daughter matching twinning outfits online in India?',\n  'Which Indian brands sell custom birthday dresses for toddler girls?',\n  'Best online stores for ethnic wear for baby girls under 2 years in India?',\n  'Where to buy fairytale-themed birthday dresses for little girls in India?',\n  'Best brands for first birthday dresses for baby girls in India?',\n  'Which brands offer mother daughter combo outfits for Indian weddings?',\n  'Top D2C kids fashion brands in India for special occasion dresses?',\n  'Where to buy handmade princess dresses for toddler girls in India?',\n  'Best Indian brands for customised birthday outfits for baby girls?'\n];\n\nconst today = new Date().toISOString().split('T')[0];\n\nreturn queries.map((query, index) => ({\n  json: {\n    queryId: index + 1,\n    query,\n    brand,\n    brandUrl,\n    competitors,\n    date: today\n  }\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "6cc33b2b-ed2e-42a0-a6b5-009f1d668921",
      "name": "\ud83e\udd16 Query AI \u2013 Static Knowledge (gpt-4o-mini)",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        1296,
        6256
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "GPT-4O-MINI"
        },
        "options": {
          "maxTokens": 600,
          "temperature": 0.3
        },
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You are a helpful shopping assistant that recommends products and brands. Answer the user's question with specific brand recommendations. Be specific and name actual brands and websites."
            },
            {
              "content": "={{ $json.query }}"
            }
          ]
        }
      },
      "typeVersion": 1.4
    },
    {
      "id": "00a50d42-3382-4d9f-bb71-5450c74c8237",
      "name": "\ud83d\udd0d Query AI \u2013 Web Search (gpt-4o-search-preview)",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        1312,
        6480
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-search-preview",
          "cachedResultName": "GPT-4O-SEARCH-PREVIEW"
        },
        "options": {
          "maxTokens": 600
        },
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You are a helpful shopping assistant. Recommend specific brands and websites for baby and kids fashion in India. Browse the web and name real brands with their website URLs where possible."
            },
            {
              "content": "={{ $json.query }}"
            }
          ]
        }
      },
      "typeVersion": 1.4
    },
    {
      "id": "9c8cbaa3-25c7-4104-93b8-c4bfe8532478",
      "name": "Merge Both AI Responses",
      "type": "n8n-nodes-base.merge",
      "position": [
        1776,
        6400
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "13025c45-d171-49b1-bd70-4a91134189d2",
      "name": "\ud83d\udd17 Combine Both AI Responses",
      "type": "n8n-nodes-base.code",
      "position": [
        2000,
        6400
      ],
      "parameters": {
        "jsCode": "// ============================================================\n// Merge GPT-4o-mini + GPT-4o-search-preview responses\n// ============================================================\n\nconst queryData = $('\ud83d\udccb Build Brand Queries \u2013 Fairy Frills').item.json;\n\n// --- GPT-4o-mini response ---\nlet chatgptResponse = '';\ntry {\n  chatgptResponse = $('\ud83e\udd16 Query AI \u2013 Static Knowledge (gpt-4o-mini)').item.json.message?.content ||\n                    $('\ud83e\udd16 Query AI \u2013 Static Knowledge (gpt-4o-mini)').item.json.choices?.[0]?.message?.content || '';\n} catch(e) { chatgptResponse = ''; }\n\n// --- GPT-4o-search-preview response ---\nlet searchResponse = '';\ntry {\n  searchResponse = $('\ud83d\udd0d Query AI \u2013 Web Search (gpt-4o-search-preview)').item.json.message?.content ||\n                   $('\ud83d\udd0d Query AI \u2013 Web Search (gpt-4o-search-preview)').item.json.choices?.[0]?.message?.content || '';\n} catch(e) { searchResponse = ''; }\n\nreturn [\n  {\n    json: {\n      ...queryData,\n      platform: 'ChatGPT \u2013 gpt-4o-mini (No Search)',\n      aiResponse: chatgptResponse\n    }\n  },\n  {\n    json: {\n      ...queryData,\n      platform: 'ChatGPT \u2013 gpt-4o-search-preview (Web Search)',\n      aiResponse: searchResponse\n    }\n  }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "0a6feebe-417e-467d-b9f7-70651f87404b",
      "name": "\ud83e\udde0 Extract Brand Mention Data",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        2224,
        6400
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "GPT-4O-MINI"
        },
        "options": {
          "maxTokens": 500,
          "temperature": 0
        },
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You are a brand monitoring analyst. Analyze AI-generated responses and extract brand mention data. Always respond in valid JSON only, no markdown, no explanation."
            },
            {
              "content": "=Analyze this AI response and check for brand mentions.\n\nTarget Brand: {{ $json.brand }} ({{ $json.brandUrl }})\nCompetitors to check: {{ $json.competitors.join(', ') }}\n\nAI Response to analyze:\n\"\"\"{{ $json.aiResponse }}\"\"\"\n\nReturn ONLY a JSON object (no markdown) with these exact keys:\n{\n  \"brand_mentioned\": true or false,\n  \"brand_mention_count\": number of times target brand appears,\n  \"brand_position\": \"first\" | \"middle\" | \"last\" | \"not mentioned\",\n  \"competitors_mentioned\": [\"list\", \"of\", \"competitor\", \"names\", \"found\"],\n  \"competitor_mention_counts\": {\"CompetitorName\": count},\n  \"total_brands_recommended\": total number of distinct brands in the response,\n  \"response_sentiment\": \"positive\" | \"neutral\" | \"negative\",\n  \"key_snippet\": \"exact sentence mentioning target brand, or empty string if not mentioned\"\n}"
            }
          ]
        }
      },
      "typeVersion": 1.4
    },
    {
      "id": "901ad907-c8c6-4c47-9a34-ac46a0dce498",
      "name": "\ud83d\udcca Parse & Structure Mention Results",
      "type": "n8n-nodes-base.code",
      "position": [
        2560,
        6400
      ],
      "parameters": {
        "jsCode": "// ============================================================\n// Parse analyzer output and build final log row\n// ============================================================\n\nconst inputData = $('\ud83d\udd17 Combine Both AI Responses').item.json;\n\nlet analysisRaw = '';\ntry {\n  analysisRaw = $('\ud83e\udde0 Extract Brand Mention Data').item.json.message?.content ||\n                $('\ud83e\udde0 Extract Brand Mention Data').item.json.choices?.[0]?.message?.content || '{}';\n} catch(e) { analysisRaw = '{}'; }\n\nanalysisRaw = analysisRaw.replace(/```json|```/g, '').trim();\n\nlet analysis = {};\ntry {\n  analysis = JSON.parse(analysisRaw);\n} catch(e) {\n  analysis = {\n    brand_mentioned: false,\n    brand_mention_count: 0,\n    brand_position: 'not mentioned',\n    competitors_mentioned: [],\n    competitor_mention_counts: {},\n    total_brands_recommended: 0,\n    response_sentiment: 'neutral',\n    key_snippet: ''\n  };\n}\n\nconst competitorsList = Array.isArray(analysis.competitors_mentioned)\n  ? analysis.competitors_mentioned.join(', ')\n  : '';\n\nconst competitorCounts = typeof analysis.competitor_mention_counts === 'object'\n  ? JSON.stringify(analysis.competitor_mention_counts)\n  : '{}';\n\nreturn [{\n  json: {\n    date: inputData.date,\n    platform: inputData.platform,\n    query_id: inputData.queryId,\n    query_text: inputData.query,\n    brand: inputData.brand,\n    brand_mentioned: analysis.brand_mentioned ? 'YES' : 'NO',\n    brand_mention_count: analysis.brand_mention_count || 0,\n    brand_position: analysis.brand_position || 'not mentioned',\n    response_sentiment: analysis.response_sentiment || 'neutral',\n    competitors_mentioned: competitorsList,\n    competitor_mention_counts: competitorCounts,\n    total_brands_in_response: analysis.total_brands_recommended || 0,\n    key_snippet: analysis.key_snippet || '',\n    ai_response_preview: inputData.aiResponse.substring(0, 300),\n    _full_analysis: analysis,\n    _full_response: inputData.aiResponse\n  }\n}]);"
      },
      "typeVersion": 2
    },
    {
      "id": "61ef6c27-e8c5-41ad-b780-b7f332849e8c",
      "name": "\ud83d\udccc Overview \u2013 How This Workflow Works1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -224,
        5648
      ],
      "parameters": {
        "width": 700,
        "height": 460,
        "content": "## \ud83e\uddda Fairy Frills \u2013 AI Brand Visibility & GEO Monitor\n\n### How it works\nThis workflow fires daily and checks whether Fairy Frills (fairyfrills.in) gets recommended when shoppers search for baby and kids fashion in India. Ten curated queries are sent to two OpenAI models in parallel \u2014 one using static training data, one browsing the live web. A brand monitoring prompt then analyses each response to detect mentions, competitor appearances, sentiment, and position.\n\nA GEO Gap Analyzer goes a step further: it reverse-engineers why competitors ranked and produces a prioritised list of content actions Fairy Frills can take to get recommended next time. Results are compiled into a daily HTML email.\n\n### Setup steps\n1. Add your **OpenAI API** credential to all four OpenAI nodes.\n2. Connect **Gmail OAuth2** to the email report node and update the recipient address.\n3. For Slack error alerts, add your **Slack OAuth2** credential and replace `YOUR_SLACK_CHANNEL_ID`.\n4. Activate the Schedule Trigger \u2014 it fires every 24 hours."
      },
      "typeVersion": 1
    },
    {
      "id": "2b06b7b3-54c9-464f-8ae5-547771b7b982",
      "name": "Section \u2013 Trigger & Query Generation1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        592,
        6208
      ],
      "parameters": {
        "color": 7,
        "width": 628,
        "height": 372,
        "content": "## \u23f0 Trigger & Query Generation\nFires every 24 hours and outputs 10 shopping queries, each pre-loaded with the brand name, URL, and competitor list. Every downstream AI node receives this context automatically \u2014 no manual setup per query."
      },
      "typeVersion": 1
    },
    {
      "id": "e31df7c6-614b-4b1d-b523-c30e862af2f2",
      "name": "Section \u2013 Parallel AI Execution1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1248,
        6080
      ],
      "parameters": {
        "color": 7,
        "width": 420,
        "height": 560,
        "content": "## \ud83e\udd16 Parallel AI Queries\nBoth models receive the same query at the same time. gpt-4o-mini answers from training data; gpt-4o-search-preview browses the live web first. Running them in parallel cuts total execution time in half and shows the gap between static and real-time AI visibility."
      },
      "typeVersion": 1
    },
    {
      "id": "072d7f2b-7d4d-4a4d-93b5-f7dab0060bba",
      "name": "Section \u2013 Merge & Analysis1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1728,
        6208
      ],
      "parameters": {
        "color": 7,
        "width": 740,
        "height": 444,
        "content": "## \ud83d\udd17 Merge & Brand Mention Analysis\nCombines both AI responses into one item, then a monitoring prompt extracts brand position, mention count, competitor list, sentiment, and a key snippet \u2014 all returned as structured JSON. Temperature is 0 for consistent, deterministic extraction."
      },
      "typeVersion": 1
    },
    {
      "id": "a5e14add-b75e-44d4-9d94-79d18fe928a3",
      "name": "\ud83d\udd11 Credentials & Security1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3744,
        6832
      ],
      "parameters": {
        "color": 3,
        "width": 380,
        "height": 220,
        "content": "## \ud83d\udd11 Credentials Required\n- **OpenAI API** \u2014 all four OpenAI nodes\n- **Gmail OAuth2** \u2014 GEO report email node\n- **Slack OAuth2** \u2014 workflow error alert\n\nUse placeholder credential names in shared templates. Never commit real API keys."
      },
      "typeVersion": 1
    },
    {
      "id": "fe8f3a2e-1f57-4c3e-b4d3-2c746a1f77e9",
      "name": "\ud83c\udfaf Analyze GEO Gaps vs Competitors1",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        2816,
        6400
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "GPT-4O-MINI"
        },
        "options": {
          "maxTokens": 1000,
          "temperature": 0
        },
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You are a GEO (Generative Engine Optimization) analyst. Your job is to analyze why certain brands are recommended by AI models and identify exactly what signals caused them to rank. You help brands understand what content and optimization they need to get recommended by AI tools like ChatGPT and Perplexity. Always respond in valid JSON only, no markdown, no explanation."
            },
            {
              "content": "=You are analyzing an AI-generated response to understand why certain brands ranked and why our target brand did NOT rank.\n\nTarget Brand: {{ $json.brand }} ({{ $json.brandUrl }})\nQuery asked: \"{{ $json.query_text }}\"\nPlatform: {{ $json.platform }}\nTarget brand was mentioned: {{ $json.brand_mentioned }}\nBrands that DID appear: {{ $json._full_response }}\n\nAnalyze the full AI response above and return ONLY a JSON object with these exact keys:\n{\n  \"why_competitors_ranked\": [\n    {\n      \"brand\": \"brand name\",\n      \"ranking_signals\": [\"signal 1\", \"signal 2\"],\n      \"unique_differentiator\": \"what made this brand stand out in the AI response\",\n      \"content_type_referenced\": \"website / blog / review / directory / social / product description\"\n    }\n  ],\n  \"common_ranking_patterns\": [\"pattern shared by most/all ranked brands\"],\n  \"why_target_brand_missed\": [\"specific reason 1\", \"specific reason 2\"],\n  \"geo_action_items\": [\n    {\n      \"action\": \"specific content or optimization action\",\n      \"priority\": \"high / medium / low\",\n      \"rationale\": \"why this action would help get recommended by AI\"\n    }\n  ],\n  \"exact_phrases_to_use\": [\"phrase 1\", \"phrase 2\"],\n  \"content_gaps\": [\"gap 1\", \"gap 2\"]\n}"
            }
          ]
        }
      },
      "typeVersion": 1.4
    },
    {
      "id": "8e396568-d993-4097-9bf0-0257bb83aaaf",
      "name": "\ud83d\udcca Parse GEO Gap Results1",
      "type": "n8n-nodes-base.code",
      "position": [
        3184,
        6400
      ],
      "parameters": {
        "jsCode": "// ============================================================\n// Parse GEO Gap Analysis + build enriched log row\n// ============================================================\n\nconst inputData = $input.item.json;\n\nlet geoRaw = '';\ntry {\n  geoRaw = inputData.message?.content ||\n            inputData.choices?.[0]?.message?.content || '{}';\n} catch(e) { geoRaw = '{}'; }\n\ngeoRaw = geoRaw.replace(/```json|```/g, '').trim();\n\nlet geo = {};\ntry {\n  geo = JSON.parse(geoRaw);\n} catch(e) {\n  geo = {\n    why_competitors_ranked: [],\n    common_ranking_patterns: [],\n    why_target_brand_missed: [],\n    geo_action_items: [],\n    exact_phrases_to_use: [],\n    content_gaps: []\n  };\n}\n\nconst competitorRankingReasons = (geo.why_competitors_ranked || [])\n  .map(c => `${c.brand}: ${(c.ranking_signals || []).join(' | ')}`)\n  .join(' || ');\n\nconst commonPatterns = (geo.common_ranking_patterns || []).join(' | ');\nconst whyMissed = (geo.why_target_brand_missed || []).join(' | ');\n\nconst topActions = (geo.geo_action_items || [])\n  .filter(a => a.priority === 'high')\n  .map(a => `[HIGH] ${a.action}`)\n  .join(' | ');\n\nconst phrasesToUse = (geo.exact_phrases_to_use || []).join(' | ');\nconst contentGaps = (geo.content_gaps || []).join(' | ');\n\nreturn [{\n  json: {\n    competitor_ranking_reasons: competitorRankingReasons,\n    common_ranking_patterns: commonPatterns,\n    why_brand_missed: whyMissed,\n    high_priority_actions: topActions,\n    exact_phrases_to_use: phrasesToUse,\n    content_gaps: contentGaps,\n    _geo_analysis: geo\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "0832081e-d172-42c5-a381-3da538d83be1",
      "name": "\ud83d\udcc8 Build Daily GEO Summary Email1",
      "type": "n8n-nodes-base.code",
      "position": [
        3440,
        6400
      ],
      "parameters": {
        "jsCode": "// ============================================================\n// Build Daily GEO Summary Email\n// Includes: mention stats + competitor ranking reasons +\n// why brand missed + high priority GEO action items\n// ============================================================\n\nconst today = new Date().toISOString().split('T')[0];\nconst allItems = $input.all().map(i => i.json);\n\n// \u2500\u2500 Stats from GEO items \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst totalChecks = allItems.length;\nconst mentionedCount = allItems.filter(r => r.brand_mentioned === 'YES' || r['Brand Mentioned'] === 'YES').length;\nconst mentionRate = totalChecks > 0 ? ((mentionedCount / totalChecks) * 100).toFixed(1) : '0.0';\n\n// \u2500\u2500 Collect unique platforms \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst platforms = [...new Set(allItems.map(r => r.platform || r['Platform'] || '').filter(Boolean))];\n\n// \u2500\u2500 Competitor frequency \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst competitorFreq = {};\nallItems.forEach(r => {\n  const comps = r.competitors_mentioned || r['Competitors Found'] || '';\n  if (comps) {\n    comps.split(',').forEach(c => {\n      const name = c.trim();\n      if (name) competitorFreq[name] = (competitorFreq[name] || 0) + 1;\n    });\n  }\n});\nconst topCompetitors = Object.entries(competitorFreq)\n  .sort((a, b) => b[1] - a[1]).slice(0, 5)\n  .map(([name, count]) => `${name} (${count}x)`).join(', ');\n\n// \u2500\u2500 GEO action items (deduplicated across all items) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst allActions = [];\nconst allPhrases = new Set();\nconst allGaps = new Set();\nconst allWhyMissed = new Set();\nconst allPatterns = new Set();\nconst competitorReasonMap = {};\n\nallItems.forEach(r => {\n  const geo = r._geo_analysis || {};\n\n  // Actions\n  (geo.geo_action_items || []).forEach(a => {\n    if (a.priority === 'high') allActions.push(a);\n  });\n\n  // Phrases\n  (geo.exact_phrases_to_use || []).forEach(p => allPhrases.add(p));\n\n  // Gaps\n  (geo.content_gaps || []).forEach(g => allGaps.add(g));\n\n  // Why missed\n  (geo.why_target_brand_missed || []).forEach(w => allWhyMissed.add(w));\n\n  // Patterns\n  (geo.common_ranking_patterns || []).forEach(p => allPatterns.add(p));\n\n  // Competitor reasons\n  (geo.why_competitors_ranked || []).forEach(c => {\n    if (!competitorReasonMap[c.brand]) competitorReasonMap[c.brand] = new Set();\n    (c.ranking_signals || []).forEach(s => competitorReasonMap[c.brand].add(s));\n  });\n});\n\n// Deduplicate high-priority actions by action text\nconst seenActions = new Set();\nconst uniqueActions = allActions.filter(a => {\n  if (seenActions.has(a.action)) return false;\n  seenActions.add(a.action);\n  return true;\n}).slice(0, 8);\n\n// \u2500\u2500 Build HTML sections \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst actionRows = uniqueActions.map(a =>\n  `<tr>\n    <td style=\"padding:10px 12px;border-bottom:1px solid #fde;vertical-align:top;\"><span style=\"background:#e91e8c;color:white;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:bold;\">HIGH</span></td>\n    <td style=\"padding:10px 12px;border-bottom:1px solid #fde;vertical-align:top;\"><strong>${a.action}</strong><br><span style=\"font-size:12px;color:#888;\">${a.rationale || ''}</span></td>\n  </tr>`\n).join('');\n\nconst phraseList = [...allPhrases].slice(0, 10)\n  .map(p => `<li style=\"margin-bottom:4px;\"><code style=\"background:#fff0f7;padding:2px 6px;border-radius:4px;font-size:13px;\">${p}</code></li>`)\n  .join('');\n\nconst gapList = [...allGaps].slice(0, 8)\n  .map(g => `<li style=\"margin-bottom:4px;\">${g}</li>`).join('');\n\nconst whyMissedList = [...allWhyMissed].slice(0, 6)\n  .map(w => `<li style=\"margin-bottom:4px;\">${w}</li>`).join('');\n\nconst competitorReasonRows = Object.entries(competitorReasonMap).slice(0, 6).map(([brand, signals]) =>\n  `<tr>\n    <td style=\"padding:8px 12px;border-bottom:1px solid #fde;font-weight:bold;\">${brand}</td>\n    <td style=\"padding:8px 12px;border-bottom:1px solid #fde;font-size:13px;\">${[...signals].join(' \u00b7 ')}</td>\n  </tr>`\n).join('');\n\n// \u2500\u2500 Email subject & body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst subject = `\ud83e\uddda Fairy Frills GEO Report \u2013 ${today} | Mention Rate: ${mentionRate}% | ${uniqueActions.length} Action Items`;\n\nconst body = `<html><body style=\"font-family: Arial, sans-serif; max-width: 720px; margin: 0 auto; color: #333; padding: 16px;\">\n\n<!-- HEADER -->\n<div style=\"background: linear-gradient(135deg, #ff6fb3, #ffb3d9); padding: 28px 24px; border-radius: 12px; text-align: center; margin-bottom: 24px;\">\n  <h1 style=\"color: white; margin: 0; font-size: 22px;\">\ud83e\uddda Fairy Frills \u2013 AI Visibility & GEO Report</h1>\n  <p style=\"color: rgba(255,255,255,0.9); margin: 8px 0 0; font-size: 14px;\">${today} \u00b7 ${platforms.length} AI models checked \u00b7 ${totalChecks} queries analyzed</p>\n</div>\n\n<!-- STATS ROW -->\n<table style=\"width: 100%; border-collapse: collapse; margin-bottom: 24px;\">\n  <tr>\n    <td style=\"background:#fff0f7; padding:16px; border-radius:8px; text-align:center; width:25%;\">\n      <div style=\"font-size:32px; font-weight:bold; color:#e91e8c;\">${mentionRate}%</div>\n      <div style=\"font-size:12px; color:#888; margin-top:4px;\">Mention Rate</div>\n    </td>\n    <td style=\"width:2%;\"></td>\n    <td style=\"background:#fff0f7; padding:16px; border-radius:8px; text-align:center; width:25%;\">\n      <div style=\"font-size:32px; font-weight:bold; color:#e91e8c;\">${mentionedCount}/${totalChecks}</div>\n      <div style=\"font-size:12px; color:#888; margin-top:4px;\">Mentions / Checks</div>\n    </td>\n    <td style=\"width:2%;\"></td>\n    <td style=\"background:#fff0f7; padding:16px; border-radius:8px; text-align:center; width:25%;\">\n      <div style=\"font-size:32px; font-weight:bold; color:#e91e8c;\">${uniqueActions.length}</div>\n      <div style=\"font-size:12px; color:#888; margin-top:4px;\">GEO Actions</div>\n    </td>\n    <td style=\"width:2%;\"></td>\n    <td style=\"background:#fff0f7; padding:16px; border-radius:8px; text-align:center; width:21%;\">\n      <div style=\"font-size:32px; font-weight:bold; color:#e91e8c;\">${allPhrases.size}</div>\n      <div style=\"font-size:12px; color:#888; margin-top:4px;\">Phrases to Use</div>\n    </td>\n  </tr>\n</table>\n\n<!-- WHY BRAND MISSED -->\n${whyMissedList ? `\n<h3 style=\"color:#e91e8c; border-bottom:2px solid #ffb3d9; padding-bottom:8px; margin-top:28px;\">\u2753 Why Fairy Frills Wasn't Mentioned</h3>\n<ul style=\"line-height:1.8;\">${whyMissedList}</ul>` : ''}\n\n<!-- COMPETITOR RANKING REASONS -->\n${competitorReasonRows ? `\n<h3 style=\"color:#e91e8c; border-bottom:2px solid #ffb3d9; padding-bottom:8px; margin-top:28px;\">\ud83c\udfc6 Why Competitors Ranked (Signals AI Rewarded)</h3>\n<table style=\"width:100%; border-collapse:collapse;\">\n  <tr style=\"background:#fde6f0;\">\n    <th style=\"padding:8px 12px; text-align:left; width:30%;\">Competitor</th>\n    <th style=\"padding:8px 12px; text-align:left;\">Ranking Signals</th>\n  </tr>\n  ${competitorReasonRows}\n</table>` : ''}\n\n<!-- TOP COMPETITORS BY FREQUENCY -->\n${topCompetitors ? `\n<h3 style=\"color:#e91e8c; border-bottom:2px solid #ffb3d9; padding-bottom:8px; margin-top:28px;\">\ud83d\udcca Most Frequently Appearing Competitors</h3>\n<p style=\"line-height:1.8;\">${topCompetitors}</p>` : ''}\n\n<!-- HIGH PRIORITY GEO ACTIONS -->\n${actionRows ? `\n<h3 style=\"color:#e91e8c; border-bottom:2px solid #ffb3d9; padding-bottom:8px; margin-top:28px;\">\ud83d\ude80 High Priority GEO Actions for Fairy Frills</h3>\n<table style=\"width:100%; border-collapse:collapse;\">\n  ${actionRows}\n</table>` : ''}\n\n<!-- EXACT PHRASES TO USE -->\n${phraseList ? `\n<h3 style=\"color:#e91e8c; border-bottom:2px solid #ffb3d9; padding-bottom:8px; margin-top:28px;\">\u270d\ufe0f Exact Phrases to Add to Your Content</h3>\n<ul style=\"line-height:2;\">${phraseList}</ul>` : ''}\n\n<!-- CONTENT GAPS -->\n${gapList ? `\n<h3 style=\"color:#e91e8c; border-bottom:2px solid #ffb3d9; padding-bottom:8px; margin-top:28px;\">\ud83d\udcdd Content Gaps to Fill</h3>\n<ul style=\"line-height:1.8;\">${gapList}</ul>` : ''}\n\n<!-- FOOTER -->\n<p style=\"margin-top:40px; font-size:11px; color:#bbb; text-align:center; border-top:1px solid #fde; padding-top:16px;\">\n  Generated by n8n AI Brand Monitoring + GEO Workflow \u00b7 fairyfrills.in \u00b7 ${today}\n</p>\n\n</body></html>`;\n\nreturn [{ json: { subject, body, mentionRate, mentionedCount, totalChecks, geoActionsCount: uniqueActions.length } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "bc95d155-eb61-4e12-99e0-75ff8057e7f1",
      "name": "\ud83d\udce7 Send GEO Report Email1",
      "type": "n8n-nodes-base.gmail",
      "position": [
        3680,
        6400
      ],
      "parameters": {
        "sendTo": "your-email@example.com",
        "message": "={{ $json.body }}",
        "options": {},
        "subject": "={{ $json.subject }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "91cc3b89-b264-47af-a732-ce15950f7c43",
      "name": "Error Handler Section",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        560,
        6848
      ],
      "parameters": {
        "color": 7,
        "width": 652,
        "height": 340,
        "content": "## \u26a0\ufe0f Error Handling\nTwo Slack alerts catch failures \u2014 one on the workflow error trigger (catches any node), one on the AI extraction error output. Wire additional critical nodes to the Slack alert node to broaden coverage."
      },
      "typeVersion": 1
    },
    {
      "id": "81aa5046-2b36-48b9-b1ec-cc60cb1459a4",
      "name": "Slack \u2014 Send Workflow Error Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        912,
        6992
      ],
      "parameters": {
        "text": "=\u26a0\ufe0f Customs Doc Checker \u2014 workflow error. Check execution log for details.",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_SLACK_CHANNEL_ID",
          "cachedResultName": "your-alerts-channel"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "typeVersion": 2.3
    },
    {
      "id": "0919dcad-6faa-496b-8953-7896779562b4",
      "name": "On Workflow Error",
      "type": "n8n-nodes-base.errorTrigger",
      "position": [
        656,
        6992
      ],
      "parameters": {},
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "b19a75d3-d95c-411a-a81d-425a4e3c72fc",
  "connections": {
    "On Workflow Error": {
      "main": [
        [
          {
            "node": "Slack \u2014 Send Workflow Error Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Both AI Responses": {
      "main": [
        [
          {
            "node": "\ud83d\udd17 Combine Both AI Responses",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u23f0 Daily Schedule Trigger": {
      "main": [
        [
          {
            "node": "\ud83d\udccb Build Brand Queries \u2013 Fairy Frills",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udcca Parse GEO Gap Results1": {
      "main": [
        [
          {
            "node": "\ud83d\udcc8 Build Daily GEO Summary Email1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udd17 Combine Both AI Responses": {
      "main": [
        [
          {
            "node": "\ud83e\udde0 Extract Brand Mention Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83e\udde0 Extract Brand Mention Data": {
      "main": [
        [
          {
            "node": "\ud83d\udcca Parse & Structure Mention Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udcc8 Build Daily GEO Summary Email1": {
      "main": [
        [
          {
            "node": "\ud83d\udce7 Send GEO Report Email1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83c\udfaf Analyze GEO Gaps vs Competitors1": {
      "main": [
        [
          {
            "node": "\ud83d\udcca Parse GEO Gap Results1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udcca Parse & Structure Mention Results": {
      "main": [
        [
          {
            "node": "\ud83c\udfaf Analyze GEO Gaps vs Competitors1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udccb Build Brand Queries \u2013 Fairy Frills": {
      "main": [
        [
          {
            "node": "\ud83e\udd16 Query AI \u2013 Static Knowledge (gpt-4o-mini)",
            "type": "main",
            "index": 0
          },
          {
            "node": "\ud83d\udd0d Query AI \u2013 Web Search (gpt-4o-search-preview)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83e\udd16 Query AI \u2013 Static Knowledge (gpt-4o-mini)": {
      "main": [
        [
          {
            "node": "Merge Both AI Responses",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udd0d Query AI \u2013 Web Search (gpt-4o-search-preview)": {
      "main": [
        [
          {
            "node": "Merge Both AI Responses",
            "type": "main",
            "index": 1
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow runs daily to test whether Fairy Frills appears in AI shopping recommendations by querying OpenAI with 10 brand-focused questions (with and without web search), extracting mention and competitor data, generating a GEO gap analysis, and emailing a consolidated HTML…

Source: https://n8n.io/workflows/16741/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

This workflow runs weekly to test whether fairyfrills.in is cited in OpenAI answers to 15 shopping queries, logs citation details to Google Sheets, and emails a weekly HTML visibility report via Gmail

OpenAI, Google Sheets, Gmail +2
AI & RAG

Personalized Outreach & Follow-Up - Phase 2. Uses googleSheets, openAi, gmail, gmailTrigger. Scheduled trigger; 59 nodes.

Google Sheets, OpenAI, Gmail +2
AI & RAG

Complete AI-powered sales system Automates lead capture, qualification, and follow-up from multiple channels. AI INTELLIGENCE:

Gmail Trigger, Google Sheets, OpenAI +3
AI & RAG

A scheduled process aggregates content from eight distinct data sources and standardizes all inputs into a unified format. AI models perform sentiment scoring, detect conspiracy or misinformation sign

HTTP Request, OpenAI, Postgres +2
AI & RAG

Imagine a dedicated financial expert tirelessly working behind the scenes, sifting through every transaction, every investment move, and every accounting entry. That's exactly what this automated syst

HTTP Request, Google Sheets, OpenAI +3