This workflow corresponds to n8n.io template #16742 — 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 →
{
"id": "C9zCh580qaElODp0",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "AI Citation Monitoring & Brand Visibility Analytics System",
"tags": [],
"nodes": [
{
"id": "394e1942-6564-47d2-a4ef-11a42aa3ec89",
"name": "\ud83d\udccc Overview \u2013 How This Workflow Works",
"type": "n8n-nodes-base.stickyNote",
"position": [
-240,
-592
],
"parameters": {
"width": 700,
"height": 512,
"content": "## \ud83d\udcca Fairy Frills \u2013 AI Citation Monitoring\n\n### How it works\nThis workflow runs every week and checks whether fairyfrills.in gets cited as a source when AI models answer shopping queries about baby and kids fashion in India. Fifteen queries go to two OpenAI models in parallel \u2014 one using static training knowledge, one browsing the live web. A citation extractor then pulls every URL referenced in each response, identifies whether your domain appeared, and notes the surrounding context.\n\nResults are logged to a Google Sheet row by row. After the 15th query, the workflow fetches the week's data and sends an HTML report covering citation rate, which pages were cited, how competitors compared, and which queries represent content gaps to fill.\n\n### Setup steps\n1. Add your **OpenAI API** credential to all three OpenAI nodes.\n2. Create a Google Sheet with a tab named **Citation Log** \u2014 add the column headers listed in the Log node's field mapping.\n3. Replace `YOUR_GOOGLE_SHEET_ID` in both Google Sheets nodes with your actual Sheet ID.\n4. Connect **Gmail OAuth2** to the email report node and update the recipient address.\n5. Optionally connect **Slack OAuth2** and replace `YOUR_SLACK_CHANNEL_ID` for error alerts.\n6. Activate the Weekly Schedule Trigger."
},
"typeVersion": 1
},
{
"id": "58650488-107f-4b73-a821-3a041c486452",
"name": "Section \u2013 Trigger & Query Generation",
"type": "n8n-nodes-base.stickyNote",
"position": [
320,
128
],
"parameters": {
"color": 7,
"width": 580,
"height": 300,
"content": "## \u23f0 Trigger & Query Generation\nFires once a week and outputs 15 shopping queries pre-loaded with the target domain, brand name, and competitor list. Every downstream node receives this context without additional configuration."
},
"typeVersion": 1
},
{
"id": "1d3a2653-4911-4725-93d9-c806e6ceb465",
"name": "Section \u2013 Parallel AI Queries",
"type": "n8n-nodes-base.stickyNote",
"position": [
912,
-80
],
"parameters": {
"color": 7,
"width": 420,
"height": 664,
"content": "## \ud83e\udd16 Parallel AI Queries\ngpt-4o-mini answers from training data; gpt-4o-search-preview browses the live web and includes source URLs. Running them in parallel reveals the difference between static and real-time citation coverage for your domain."
},
"typeVersion": 1
},
{
"id": "f926253b-75f7-4869-a625-4bfca060edbf",
"name": "Section \u2013 Merge & Citation Extraction",
"type": "n8n-nodes-base.stickyNote",
"position": [
1360,
-96
],
"parameters": {
"color": 7,
"width": 788,
"height": 666,
"content": "## \ud83d\udd2c Merge & Citation Extraction\nCombines both AI responses into one item per query, then a citation-focused prompt extracts every URL referenced \u2014 identifying your domain's position, anchor context, and competitor citations. Temperature is 0 for consistent extraction."
},
"typeVersion": 1
},
{
"id": "2482a066-f376-47d4-8fe6-0cedd0791075",
"name": "Section \u2013 Logging & Weekly Report",
"type": "n8n-nodes-base.stickyNote",
"position": [
2176,
-80
],
"parameters": {
"color": 7,
"width": 1360,
"height": 666,
"content": "## \ud83d\udcdd Logging & Weekly Report\nParses the citation JSON, maps fields to Google Sheets columns, and appends one row per query+platform. After the 15th query completes, it fetches the full week's data and builds an HTML email with citation rate, cited pages, competitor breakdown, and content gap opportunities."
},
"typeVersion": 1
},
{
"id": "95f777be-782b-4ff0-9257-0c9d38c15660",
"name": "Section \u2013 Error Handling",
"type": "n8n-nodes-base.stickyNote",
"position": [
672,
720
],
"parameters": {
"color": 7,
"width": 528,
"height": 392,
"content": "## \u26a0\ufe0f Error Handling\nA workflow-level error trigger catches failures in any node and posts an alert to Slack. Remove this section if you don't use Slack \u2014 the main pipeline runs independently."
},
"typeVersion": 1
},
{
"id": "d647c645-995f-4dc2-ab9b-9a2b5fcfa0d5",
"name": "\ud83d\udd11 Credentials & Security",
"type": "n8n-nodes-base.stickyNote",
"position": [
3440,
720
],
"parameters": {
"color": 3,
"width": 380,
"height": 200,
"content": "## \ud83d\udd11 Credentials Required\n- **OpenAI API** \u2014 all three OpenAI nodes\n- **Google Sheets OAuth2** \u2014 log and fetch nodes\n- **Gmail OAuth2** \u2014 weekly report email\n- **Slack OAuth2** \u2014 error alert (optional)\n\nUse placeholder credential names in shared templates. Never commit real API keys."
},
"typeVersion": 1
},
{
"id": "7a2781ce-c645-4c3d-9590-10acb58bd8bd",
"name": "\u23f0 Weekly Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
448,
272
],
"parameters": {
"rule": {
"interval": [
{
"field": "weeks"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "f434794c-a5d5-4443-95e2-4da0ef95cadb",
"name": "\ud83d\udccb Build Citation Queries \u2013 Fairy Frills",
"type": "n8n-nodes-base.code",
"position": [
688,
272
],
"parameters": {
"jsCode": "// ============================================================\n// FAIRY FRILLS \u2013 AI Citation Monitoring Query Builder\n// Domain: fairyfrills.in\n// ============================================================\n\nconst domain = 'fairyfrills.in';\nconst brand = 'Fairy Frills';\nconst competitors = [\n 'hopscotch.in',\n 'firstcry.com',\n 'mothercare.in',\n 'littlemuffet.com',\n 'olele.in'\n];\n\nconst queries = [\n 'best handcrafted baby girl birthday dresses India',\n 'mother daughter matching outfits India',\n 'first birthday dress for baby girl India',\n 'custom birthday dress toddler India',\n 'fairytale birthday dress baby girl India',\n 'where to buy ethnic wear baby girls India',\n 'handmade princess dress toddler India',\n 'twinning outfits mom daughter India',\n 'D2C kids fashion brands India',\n 'special occasion dresses baby girl India',\n 'birthday photoshoot outfit baby girl India',\n 'smash cake dress baby girl India',\n 'annaprashana dress baby girl India',\n 'naming ceremony dress baby India',\n 'half saree baby girl birthday outfit India'\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 domain,\n brand,\n competitors,\n date: today,\n weekStart: today\n }\n}));"
},
"typeVersion": 2
},
{
"id": "11ec9c63-6939-408e-90c4-5dc0fecb4a71",
"name": "\ud83e\udd16 Query AI \u2013 Static Knowledge (gpt-4o-mini)",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
976,
128
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini",
"cachedResultName": "GPT-4O-MINI"
},
"options": {
"maxTokens": 800,
"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": "07b08027-0743-4f25-9a08-a2fac1d00148",
"name": "\ud83d\udd0d Query AI \u2013 Web Search (gpt-4o-search-preview)",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
976,
416
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-search-preview",
"cachedResultName": "GPT-4O-SEARCH-PREVIEW"
},
"options": {
"maxTokens": 800
},
"messages": {
"values": [
{
"role": "system",
"content": "You are a helpful search assistant. Answer the user's question with detailed information, citing specific websites and URLs as sources. Always include direct URLs to the most relevant pages."
},
{
"content": "={{ $json.query }}"
}
]
}
},
"typeVersion": 1.4
},
{
"id": "65796c63-19b2-47c6-bcfe-8b38c169a472",
"name": "Merge Both AI Responses",
"type": "n8n-nodes-base.merge",
"position": [
1424,
272
],
"parameters": {},
"typeVersion": 3.2
},
{
"id": "f54375b5-f4f8-4396-b6ef-e0c11cbc15da",
"name": "\ud83d\udd17 Combine & Prepare Responses",
"type": "n8n-nodes-base.code",
"position": [
1648,
272
],
"parameters": {
"jsCode": "// ============================================================\n// Extract text from GPT-4o-mini + GPT-4o-search-preview\n// ============================================================\n\nconst queryData = $('\ud83d\udccb Build Citation Queries \u2013 Fairy Frills').item.json;\n\n// GPT-4o-mini response\nlet gptResponse = '';\ntry {\n gptResponse = $('\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) { gptResponse = ''; }\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: gptResponse\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": "cd8e2521-5e1d-444b-bae3-c40a0c00d3ac",
"name": "\ud83d\udd2c Extract Citation Data",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
1872,
272
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini",
"cachedResultName": "GPT-4O-MINI"
},
"options": {
"maxTokens": 800,
"temperature": 0
},
"messages": {
"values": [
{
"role": "system",
"content": "You are a citation analysis expert. Your job is to scan AI-generated responses and extract every URL and domain reference found in them. You also check whether a target domain is cited. Always respond in valid JSON only, no markdown, no explanation."
},
{
"content": "=Analyze this AI response for citations and domain references.\n\nTarget Domain: {{ $json.domain }}\nTarget Brand: {{ $json.brand }}\nCompetitor domains: {{ $json.competitors.join(', ') }}\nQuery that was asked: \"{{ $json.query }}\"\nPlatform: {{ $json.platform }}\n\nAI Response to scan:\n\"\"\"{{ $json.aiResponse }}\"\"\"\n\nReturn ONLY a JSON object (no markdown) with these exact keys:\n{\n \"target_domain_cited\": true or false,\n \"target_citation_urls\": [\"full URLs from target domain if found, else empty array\"],\n \"target_citation_count\": number,\n \"target_anchor_context\": \"the sentence or phrase surrounding the target domain citation, or empty string\",\n \"competitor_citations\": [\n {\n \"domain\": \"competitor domain\",\n \"urls_cited\": [\"full URLs\"],\n \"citation_count\": number,\n \"anchor_context\": \"surrounding sentence\"\n }\n ],\n \"all_cited_domains\": [\"every domain URL mentioned in the response\"],\n \"total_citations_in_response\": number,\n \"citation_position\": \"first\" | \"middle\" | \"last\" | \"not cited\",\n \"response_has_citations\": true or false\n}"
}
]
}
},
"typeVersion": 1.4
},
{
"id": "80675d66-4ed3-42c7-b901-638e3cbf4431",
"name": "\ud83d\udcca Parse & Structure Citation Results",
"type": "n8n-nodes-base.code",
"position": [
2208,
272
],
"parameters": {
"jsCode": "// ============================================================\n// Parse Citation Extractor output + build log row\n// ============================================================\n\nconst inputData = $('\ud83d\udd17 Combine & Prepare Responses').item.json;\n\nlet citationRaw = '';\ntry {\n citationRaw = $('\ud83d\udd2c Extract Citation Data').item.json.message?.content ||\n $('\ud83d\udd2c Extract Citation Data').item.json.choices?.[0]?.message?.content || '{}';\n} catch(e) { citationRaw = '{}'; }\n\ncitationRaw = citationRaw.replace(/```json|```/g, '').trim();\n\nlet citation = {};\ntry {\n citation = JSON.parse(citationRaw);\n} catch(e) {\n citation = {\n target_domain_cited: false,\n target_citation_urls: [],\n target_citation_count: 0,\n target_anchor_context: '',\n competitor_citations: [],\n all_cited_domains: [],\n total_citations_in_response: 0,\n citation_position: 'not cited',\n response_has_citations: false\n };\n}\n\nconst competitorCitationSummary = (citation.competitor_citations || [])\n .map(c => `${c.domain}(${c.citation_count}x)`)\n .join(', ');\n\nconst competitorCitationDetails = JSON.stringify(\n (citation.competitor_citations || []).reduce((acc, c) => {\n acc[c.domain] = c.citation_count;\n return acc;\n }, {})\n);\n\nreturn [{\n json: {\n date: inputData.date,\n week_start: inputData.weekStart,\n platform: inputData.platform,\n query_id: inputData.queryId,\n query_text: inputData.query,\n domain: inputData.domain,\n domain_cited: citation.target_domain_cited ? 'YES' : 'NO',\n citation_count: citation.target_citation_count || 0,\n citation_position: citation.citation_position || 'not cited',\n cited_urls: (citation.target_citation_urls || []).join(', '),\n anchor_context: citation.target_anchor_context || '',\n competitor_citations: competitorCitationSummary,\n competitor_citation_details: competitorCitationDetails,\n all_cited_domains: (citation.all_cited_domains || []).join(', '),\n total_citations_in_response: citation.total_citations_in_response || 0,\n response_has_citations: citation.response_has_citations ? 'YES' : 'NO',\n ai_response_preview: inputData.aiResponse.substring(0, 300),\n _citation: citation,\n _full_response: inputData.aiResponse\n }\n}]);"
},
"typeVersion": 2
},
{
"id": "afa68cb1-278e-44bb-a6fd-c0942d66b152",
"name": "\ud83d\udcdd Append Results to Google Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [
2432,
272
],
"parameters": {
"columns": {
"value": {
"Date": "={{ $json.date }}",
"Query": "={{ $json.query_text }}",
"Domain": "={{ $json.domain }}",
"Platform": "={{ $json.platform }}",
"Query ID": "={{ $json.query_id }}",
"Cited URLs": "={{ $json.cited_urls }}",
"Week Start": "={{ $json.week_start }}",
"Domain Cited": "={{ $json.domain_cited }}",
"Anchor Context": "={{ $json.anchor_context }}",
"Citation Count": "={{ $json.citation_count }}",
"All Cited Domains": "={{ $json.all_cited_domains }}",
"Citation Position": "={{ $json.citation_position }}",
"AI Response Preview": "={{ $json.ai_response_preview }}",
"Competitor Citations": "={{ $json.competitor_citations }}",
"Response Has Citations": "={{ $json.response_has_citations }}",
"Competitor Citation Details": "={{ $json.competitor_citation_details }}",
"Total Citations in Response": "={{ $json.total_citations_in_response }}"
},
"mappingMode": "defineBelow"
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Citation Log"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "YOUR_GOOGLE_SHEET_ID"
}
},
"typeVersion": 4.5
},
{
"id": "78a769c6-8522-4e1b-b70f-202f803ec82a",
"name": "\ud83d\udd00 Check If Final Query (ID = 15)",
"type": "n8n-nodes-base.if",
"position": [
2656,
272
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": false,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "check-last-query",
"operator": {
"type": "number",
"operation": "equals"
},
"leftValue": "={{ $json.query_id }}",
"rightValue": 15
}
]
}
},
"typeVersion": 2.2
},
{
"id": "f2c8167d-9b63-4304-8c08-31fc2bdddf31",
"name": "\ud83d\udce5 Fetch This Week's Logged Results",
"type": "n8n-nodes-base.googleSheets",
"position": [
2880,
144
],
"parameters": {
"operation": "getAll",
"documentId": {
"__rl": true,
"mode": "id",
"value": "YOUR_GOOGLE_SHEET_ID"
}
},
"typeVersion": 4.5
},
{
"id": "c94274a3-22ea-41fb-ad39-7417cbe45fca",
"name": "\u23ed\ufe0f Skip \u2013 Not Final Query",
"type": "n8n-nodes-base.noOp",
"position": [
2928,
432
],
"parameters": {},
"typeVersion": 1
},
{
"id": "2c8a1cc2-9728-46df-b2e6-6bf197a2f069",
"name": "\ud83d\udcc8 Build Weekly Citation Report",
"type": "n8n-nodes-base.code",
"position": [
3104,
144
],
"parameters": {
"jsCode": "// ============================================================\n// Build Weekly Citation Report Email\n// ============================================================\n\nconst today = new Date().toISOString().split('T')[0];\nconst allRows = $input.all().map(i => i.json);\n\n// Filter last 7 days\nconst sevenDaysAgo = new Date();\nsevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);\nconst weekRows = allRows.filter(r => new Date(r['Date'] || r.date || '') >= sevenDaysAgo);\nconst dataRows = weekRows.length > 0 ? weekRows : allRows.slice(-30);\n\nif (dataRows.length === 0) {\n return [{ json: { subject: 'No citation data this week', body: 'No citation data was logged this week.' } }];\n}\n\n// Core stats\nconst totalChecks = dataRows.length;\nconst citedCount = dataRows.filter(r => (r['Domain Cited'] || r.domain_cited) === 'YES').length;\nconst citationRate = ((citedCount / totalChecks) * 100).toFixed(1);\nconst platforms = [...new Set(dataRows.map(r => r['Platform'] || r.platform || '').filter(Boolean))];\nconst totalQueries = [...new Set(dataRows.map(r => r['Query ID'] || r.query_id))].length;\n\n// Cited pages\nconst citedUrlMap = {};\ndataRows.forEach(r => {\n const urls = r['Cited URLs'] || r.cited_urls || '';\n if (urls) urls.split(',').forEach(u => {\n const url = u.trim();\n if (url) citedUrlMap[url] = (citedUrlMap[url] || 0) + 1;\n });\n});\nconst topCitedPages = Object.entries(citedUrlMap).sort((a, b) => b[1] - a[1]).slice(0, 8);\n\n// Uncited queries\nconst uncitedQueries = dataRows\n .filter(r => (r['Domain Cited'] || r.domain_cited) === 'NO')\n .map(r => ({ query: r['Query'] || r.query_text, platform: r['Platform'] || r.platform }))\n .filter((v, i, a) => a.findIndex(t => t.query === v.query) === i)\n .slice(0, 10);\n\n// Competitor citation frequency\nconst compFreq = {};\ndataRows.forEach(r => {\n const comps = r['Competitor Citations'] || r.competitor_citations || '';\n if (comps) comps.split(',').forEach(c => {\n const match = c.trim().match(/^(.+?)\\((\\d+)x\\)$/);\n if (match) compFreq[match[1]] = (compFreq[match[1]] || 0) + parseInt(match[2]);\n });\n});\nconst topCompetitors = Object.entries(compFreq).sort((a, b) => b[1] - a[1]).slice(0, 6);\n\n// Per-platform breakdown\nconst platformBreakdown = {};\ndataRows.forEach(r => {\n const p = r['Platform'] || r.platform || 'Unknown';\n if (!platformBreakdown[p]) platformBreakdown[p] = { total: 0, cited: 0 };\n platformBreakdown[p].total++;\n if ((r['Domain Cited'] || r.domain_cited) === 'YES') platformBreakdown[p].cited++;\n});\n\n// Anchor context snippets\nconst anchorSnippets = dataRows\n .filter(r => ((r['Anchor Context'] || r.anchor_context) || '').length > 5)\n .slice(0, 3)\n .map(r => ({ platform: r['Platform'] || r.platform, context: r['Anchor Context'] || r.anchor_context, query: r['Query'] || r.query_text }));\n\n// Build HTML\nconst platformRows = Object.entries(platformBreakdown).map(([p, d]) =>\n `<tr>\n <td style=\"padding:8px 12px;border-bottom:1px solid #e8f4fd;\">${p}</td>\n <td style=\"padding:8px 12px;border-bottom:1px solid #e8f4fd;text-align:center;\">${d.cited}/${d.total}</td>\n <td style=\"padding:8px 12px;border-bottom:1px solid #e8f4fd;text-align:center;\"><strong>${((d.cited/d.total)*100).toFixed(0)}%</strong></td>\n </tr>`\n).join('');\n\nconst citedPagesRows = topCitedPages.map(([url, count]) =>\n `<tr>\n <td style=\"padding:8px 12px;border-bottom:1px solid #e8f4fd;font-size:12px;\"><a href=\"${url}\" style=\"color:#1a73e8;\">${url.replace(/https?:\\/\\//, '')}</a></td>\n <td style=\"padding:8px 12px;border-bottom:1px solid #e8f4fd;text-align:center;\"><strong>${count}x</strong></td>\n </tr>`\n).join('');\n\nconst competitorRows = topCompetitors.map(([domain, count]) =>\n `<tr>\n <td style=\"padding:8px 12px;border-bottom:1px solid #e8f4fd;\">${domain}</td>\n <td style=\"padding:8px 12px;border-bottom:1px solid #e8f4fd;text-align:center;\">\n <span style=\"background:#ff6b6b;color:white;padding:2px 10px;border-radius:12px;font-size:12px;\">${count}x</span>\n </td>\n </tr>`\n).join('');\n\nconst uncitedList = uncitedQueries.map(q =>\n `<li style=\"margin-bottom:6px;\"><strong>${q.query}</strong> <span style=\"color:#888;font-size:12px;\">(${q.platform})</span></li>`\n).join('');\n\nconst anchorList = anchorSnippets.map(s =>\n `<div style=\"background:#f8f9fa;border-left:3px solid #1a73e8;padding:10px 14px;margin-bottom:10px;border-radius:4px;\">\n <div style=\"font-size:11px;color:#888;margin-bottom:4px;\">${s.platform} \u00b7 \"${s.query}\"</div>\n <div style=\"font-size:13px;\">${s.context}</div>\n </div>`\n).join('');\n\nconst subject = `\ud83d\udcca Fairy Frills Citation Report \u2013 Week of ${today} | ${citationRate}% Citation Rate | ${citedCount}/${totalChecks} cited`;\n\nconst body = `<html><body style=\"font-family: Arial, sans-serif; max-width: 720px; margin: 0 auto; color: #333; padding: 16px;\">\n\n<div style=\"background: linear-gradient(135deg, #1a73e8, #4da3ff); padding: 28px 24px; border-radius: 12px; text-align: center; margin-bottom: 24px;\">\n <h1 style=\"color: white; margin: 0; font-size: 22px;\">\ud83d\udcca Fairy Frills \u2013 AI Citation Report</h1>\n <p style=\"color: rgba(255,255,255,0.9); margin: 8px 0 0; font-size: 14px;\">Week of ${today} \u00b7 ${platforms.length} AI models \u00b7 ${totalQueries} queries</p>\n</div>\n\n<table style=\"width:100%; border-collapse:collapse; margin-bottom:24px;\">\n <tr>\n <td style=\"background:#e8f4fd; padding:16px; border-radius:8px; text-align:center; width:23%;\">\n <div style=\"font-size:32px; font-weight:bold; color:#1a73e8;\">${citationRate}%</div>\n <div style=\"font-size:12px; color:#888; margin-top:4px;\">Citation Rate</div>\n </td>\n <td style=\"width:2%;\"></td>\n <td style=\"background:#e8f4fd; padding:16px; border-radius:8px; text-align:center; width:23%;\">\n <div style=\"font-size:32px; font-weight:bold; color:#1a73e8;\">${citedCount}/${totalChecks}</div>\n <div style=\"font-size:12px; color:#888; margin-top:4px;\">Cited / Checks</div>\n </td>\n <td style=\"width:2%;\"></td>\n <td style=\"background:#e8f4fd; padding:16px; border-radius:8px; text-align:center; width:23%;\">\n <div style=\"font-size:32px; font-weight:bold; color:#1a73e8;\">${topCitedPages.length}</div>\n <div style=\"font-size:12px; color:#888; margin-top:4px;\">Pages Cited</div>\n </td>\n <td style=\"width:2%;\"></td>\n <td style=\"background:#e8f4fd; padding:16px; border-radius:8px; text-align:center; width:23%;\">\n <div style=\"font-size:32px; font-weight:bold; color:#1a73e8;\">${uncitedQueries.length}</div>\n <div style=\"font-size:12px; color:#888; margin-top:4px;\">Missed Opportunities</div>\n </td>\n </tr>\n</table>\n\n<h3 style=\"color:#1a73e8; border-bottom:2px solid #c5dff8; padding-bottom:8px; margin-top:28px;\">\ud83d\udce1 Citation Rate by AI Model</h3>\n<table style=\"width:100%; border-collapse:collapse;\">\n <tr style=\"background:#e8f4fd;\"><th style=\"padding:8px 12px;text-align:left;\">AI Model</th><th style=\"padding:8px 12px;\">Citations</th><th style=\"padding:8px 12px;\">Rate</th></tr>\n ${platformRows}\n</table>\n\n${citedPagesRows ? `\n<h3 style=\"color:#1a73e8; border-bottom:2px solid #c5dff8; padding-bottom:8px; margin-top:28px;\">\u2705 Pages Being Cited by AI</h3>\n<table style=\"width:100%; border-collapse:collapse;\">\n <tr style=\"background:#e8f4fd;\"><th style=\"padding:8px 12px;text-align:left;\">Page URL</th><th style=\"padding:8px 12px;\">Times Cited</th></tr>\n ${citedPagesRows}\n</table>` : `<h3 style=\"color:#1a73e8; margin-top:28px;\">\u2705 Pages Being Cited</h3><p style=\"color:#888;\">No pages from fairyfrills.in were cited this week.</p>`}\n\n${anchorList ? `\n<h3 style=\"color:#1a73e8; border-bottom:2px solid #c5dff8; padding-bottom:8px; margin-top:28px;\">\ud83d\udcac How AI Cited Your Site</h3>\n${anchorList}` : ''}\n\n${competitorRows ? `\n<h3 style=\"color:#1a73e8; border-bottom:2px solid #c5dff8; padding-bottom:8px; margin-top:28px;\">\ud83c\udfc6 Competitors Being Cited Instead</h3>\n<table style=\"width:100%; border-collapse:collapse;\">\n <tr style=\"background:#e8f4fd;\"><th style=\"padding:8px 12px;text-align:left;\">Competitor Domain</th><th style=\"padding:8px 12px;\">Citation Count</th></tr>\n ${competitorRows}\n</table>` : ''}\n\n${uncitedList ? `\n<h3 style=\"color:#1a73e8; border-bottom:2px solid #c5dff8; padding-bottom:8px; margin-top:28px;\">\u26a0\ufe0f Queries Where fairyfrills.in Was NOT Cited (Content Opportunities)</h3>\n<ul style=\"line-height:1.9;\">${uncitedList}</ul>` : ''}\n\n<p style=\"margin-top:40px; font-size:11px; color:#bbb; text-align:center; border-top:1px solid #e8f4fd; padding-top:16px;\">\n Generated by n8n AI Citation Monitoring Workflow \u00b7 fairyfrills.in \u00b7 ${today}\n</p>\n\n</body></html>`;\n\nreturn [{ json: { subject, body, citationRate, citedCount, totalChecks } }];"
},
"typeVersion": 2
},
{
"id": "075d01d0-7928-40b3-8f7a-9d87747204aa",
"name": "\ud83d\udce7 Send Weekly Citation Report",
"type": "n8n-nodes-base.gmail",
"position": [
3328,
144
],
"parameters": {
"sendTo": "user@example.com",
"message": "={{ $json.body }}",
"options": {},
"subject": "={{ $json.subject }}"
},
"typeVersion": 2.1
},
{
"id": "6baa9206-de1e-418c-81de-713220d273b6",
"name": "On Workflow Error",
"type": "n8n-nodes-base.errorTrigger",
"position": [
736,
896
],
"parameters": {},
"typeVersion": 1
},
{
"id": "99f0e567-7c7e-463f-85d6-398fa4c47b67",
"name": "Slack \u2013 Post Workflow Error Alert",
"type": "n8n-nodes-base.slack",
"position": [
976,
896
],
"parameters": {
"text": "=\u26a0\ufe0f Fairy Frills Citation Monitor \u2014 workflow error on {{ $now.toISO() }}. Check n8n 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
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "3bdc6ce4-f2b9-4692-a921-5c6d192c809e",
"connections": {
"On Workflow Error": {
"main": [
[
{
"node": "Slack \u2013 Post Workflow Error Alert",
"type": "main",
"index": 0
}
]
]
},
"Merge Both AI Responses": {
"main": [
[
{
"node": "\ud83d\udd17 Combine & Prepare Responses",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udd2c Extract Citation Data": {
"main": [
[
{
"node": "\ud83d\udcca Parse & Structure Citation Results",
"type": "main",
"index": 0
}
]
]
},
"\u23f0 Weekly Schedule Trigger": {
"main": [
[
{
"node": "\ud83d\udccb Build Citation Queries \u2013 Fairy Frills",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udd17 Combine & Prepare Responses": {
"main": [
[
{
"node": "\ud83d\udd2c Extract Citation Data",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udcc8 Build Weekly Citation Report": {
"main": [
[
{
"node": "\ud83d\udce7 Send Weekly Citation Report",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udd00 Check If Final Query (ID = 15)": {
"main": [
[
{
"node": "\ud83d\udce5 Fetch This Week's Logged Results",
"type": "main",
"index": 0
}
],
[
{
"node": "\u23ed\ufe0f Skip \u2013 Not Final Query",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udcdd Append Results to Google Sheets": {
"main": [
[
{
"node": "\ud83d\udd00 Check If Final Query (ID = 15)",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udce5 Fetch This Week's Logged Results": {
"main": [
[
{
"node": "\ud83d\udcc8 Build Weekly Citation Report",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udcca Parse & Structure Citation Results": {
"main": [
[
{
"node": "\ud83d\udcdd Append Results to Google Sheets",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udccb Build Citation 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
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
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, with optional Slack alerts on workflow errors. Runs once a week on a schedule…
Source: https://n8n.io/workflows/16742/ — 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.
Personalized Outreach & Follow-Up - Phase 2. Uses googleSheets, openAi, gmail, gmailTrigger. Scheduled trigger; 59 nodes.
Complete AI-powered sales system Automates lead capture, qualification, and follow-up from multiple channels. AI INTELLIGENCE:
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
This workflow accepts construction work permit requests via webhook, logs them to Google Sheets, uses OpenAI (gpt-4o-mini) to detect conflicts against active permits, routes the request to a superviso
Automate your social media content pipeline from idea to scheduled post. This workflow reads content ideas from a Google Sheet, uses OpenAI to generate platform-optimized posts for LinkedIn, X (Twitte