📋 Overview
This workflow automatically fetches Google Ads campaign data from Google Sheets, performs deep statistical analysis, leverages AI to generate actionable insights, and sends professional email reports with prioritized recommendations. Perfect for digital marketers, agencies, and businesses running Google Ads campaigns.
✨ Features
🤖 AI-Powered Analysis using Perplexity AI for comprehensive campaign insights
📊 Automated Daily Reports sent at 9:00 AM with key metrics and trends
📈 Advanced Performance Metrics including ROAS, CTR, CPA, CPC analysis
🎨 Beautiful HTML Email Reports with responsive design and priority badges
📋 Campaign Performance Scoring with top performers and underperformers identification
🔍 Keyword & Device Analysis with conversion rate optimization insights
📊 Google Sheets Integration for data storage and historical tracking
⚡ Automated Data Validation and cleaning for accurate analysis
🚨 Alert System with visual indicators for campaign health status
🛠️ Prerequisites
n8n instance (self-hosted or cloud)
Google Sheets with your Google Ads campaign data
Perplexity AI API Key (or alternative AI service)
Gmail Account with OAuth2 setup for sending reports
Google Sheets API access configured in n8n
📦 Quick Start
1. Import
Open your n8n instance
Go to Import → From File
Select Google-Ads-Performance-Analyzer-Sanitized.json
Click Import
2. Set Up Credentials in n8n
Create these credentials in your n8n instance (Settings → Credentials):
Google Sheets OAuth2 API - for reading campaign data
Gmail OAuth2 - for sending email reports
Perplexity API - for AI analysis (or configure alternative AI service)
3. Configure Your Google Sheets
4. Update Configuration In the n8n workflow, update these placeholders:
1. Fetch Google Ads Data node:
Replace YOUR_GOOGLE_SHEET_ID with your actual Google Sheets ID
Attach Google Sheets OAuth2 credential
2. Send Google Ads Email Report node:
Replace [email protected] with your email address
Attach Gmail OAuth2 credential
3. Export to Google Ads Results Sheet node:
Replace YOUR_GOOGLE_SHEET_ID with your results sheet ID
Replace YOUR_SHEET_GID with your specific sheet tab ID
Attach Google Sheets OAuth2 credential
4. AI Google Ads Analysis node:
Attach your Perplexity API credential
🚀 Usage
Automatic Execution
The workflow runs automatically every day at 9:00 AM
No manual intervention required once configured
Manual Execution
Navigate to your workflow in n8n
Click Execute Workflow
Check your email for the generated report
Customizing the Schedule *To change the execution time, modify the cron expression in the Daily Google Ads Analysis trigger node:
Current: 0 9 * * * (9:00 AM daily)
Example: 0 8 * * 1 (8:00 AM every Monday)
📊 Report Structure
The generated email report includes: *📈 Key Metrics Dashboard
Total Campaigns
Total Cost & Revenue
Overall ROAS, CTR, CPA, CPC
Visual status indicators
🧠 AI Analysis Sections
Executive Summary - High-level account health
Top Performers Analysis - Best performing campaigns
Underperforming Campaigns - Urgent attention needed
Keyword Optimization Strategy - Bid and targeting insights
Budget Reallocation Recommendations - Spend optimization
Device & Targeting Insights - Performance by device
Quality Score Improvements - Ad copy and landing page tips
Action Plan - Prioritized next steps
🎯 Priority-Based Recommendations
High Priority (🔴) - Urgent actions needed
Medium Priority (🟡) - Important optimizations
Low Priority (🔵) - Future considerations
🔧 Customization Options Modify AI Analysis Prompt Edit the prompt in the AI Google Ads Analysis node to:
Focus on specific metrics
Add industry-specific insights
Change analysis depth
Customize recommendation format
Email Template Styling Customize the HTML email template in the Generate Google Ads Email node:
Brand colors and fonts
Logo and header design
Layout and spacing
Additional metrics display
🔐 Security Best Practices
Never commit API keys or credentials to the repository
Use n8n's credential manager for all sensitive data
Set up environment variables for configuration
Regularly rotate API keys and OAuth tokens
Use least-privilege access for Google Sheets and Gmail
Full JSON Code
{
"name": "Google Ads Performance Analyzer (SANITIZED)",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 9 * * *"
}
]
}
},
"id": "6b57627c-99e8-49bd-a7be-11891b12106a",
"name": "Daily Google Ads Analysis",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1,
"position": [
-736,
0
]
},
{
"parameters": {
"documentId": {
"__rl": true,
"value": "YOUR_GOOGLE_SHEET_ID",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Campaign Performance",
"cachedResultUrl": "<https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=0>"
},
"options": {}
},
"id": "10b16bfc-ccde-4575-aae6-dfc58cf754f4",
"name": "Fetch Google Ads Data",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"position": [
-512,
0
],
"notes": "Configure with your Google Sheets OAuth2 credentials in n8n"
},
{
"parameters": {
"jsCode": "// Clean and validate Google Ads campaign data\\nconst validatedData = [];\\n\\nfor (const item of items) {\\n const data = item.json;\\n \\n // Skip empty rows or header rows\\n if (!data.Campaign || data.Campaign === 'Campaign') {\\n continue;\\n }\\n \\n const cleanedData = {\\n date: data.Date || new Date().toISOString().split('T')[0],\\n campaign_name: data.Campaign || 'Unknown Campaign',\\n ad_group_name: data.Ad_Group || 'Unknown Ad Group',\\n keyword: data.Keyword || 'Unknown Keyword',\\n device: data.Device || 'Unknown',\\n impressions: parseInt(String(data.Impressions).replace(/,/g, '')) || 0,\\n clicks: parseInt(String(data.Clicks).replace(/,/g, '')) || 0,\\n ctr: (() => {\\n if (typeof data.CTR === 'string' && data.CTR.includes('%')) {\\n return parseFloat(data.CTR.replace('%', ''));\\n } else {\\n const ctrNum = parseFloat(data.CTR);\\n return ctrNum > 1 ? ctrNum : ctrNum * 100;\\n }\\n })(),\\n cpc: parseFloat(String(data.CPC).replace('$', '')) || 0,\\n cost: parseFloat(String(data.Cost).replace(/[$,]/g, '')) || 0,\\n leads: parseInt(data.Leads) || 0,\\n cpl: parseFloat(String(data.CPL).replace('$', '')) || 0,\\n conversions: parseInt(data.Conversions) || 0,\\n cpa: parseFloat(String(data.CPA).replace('$', '')) || 0,\\n conversion_rate: (() => {\\n const convRate = parseFloat(data['Conversion Rate']);\\n return convRate > 1 ? convRate : convRate * 100;\\n })(),\\n revenue: parseFloat(String(data.Revenue).replace(/[$,]/g, '')) || 0,\\n roas: parseFloat(data.ROAS) || 0\\n };\\n \\n // Add Google Ads specific performance flags\\n cleanedData.low_ctr = cleanedData.ctr < 2.0; // Google Ads typically has higher CTR\\n cleanedData.high_cpc = cleanedData.cpc > 5.0; // Flag expensive keywords\\n cleanedData.poor_roas = cleanedData.roas < 3.0; // Flag poor return on ad spend\\n cleanedData.low_quality_score = cleanedData.ctr < 1.0; // Indicates potential low quality score\\n \\n validatedData.push({ json: cleanedData });\\n}\\n\\nconsole.log(`Processed ${validatedData.length} valid Google Ads records`);\\nconsole.log('Sample CTR values:', validatedData.slice(0,3).map(v => v.json.ctr));\\nreturn validatedData;"
},
"id": "6d542984-ed06-410a-a459-cd895cfc9f54",
"name": "Validate Google Ads Data",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-288,
0
]
},
{
"parameters": {
"jsCode": "// Calculate Google Ads campaign statistics\\nconst campaigns = items.map(item => item.json);\\n\\nconst totalCost = campaigns.reduce((sum, c) => sum + c.cost, 0);\\nconst totalImpressions = campaigns.reduce((sum, c) => sum + c.impressions, 0);\\nconst totalClicks = campaigns.reduce((sum, c) => sum + c.clicks, 0);\\nconst totalConversions = campaigns.reduce((sum, c) => sum + c.conversions, 0);\\nconst totalLeads = campaigns.reduce((sum, c) => sum + c.leads, 0);\\nconst totalRevenue = campaigns.reduce((sum, c) => sum + c.revenue, 0);\\n\\nconst overallCTR = totalImpressions > 0 ? (totalClicks / totalImpressions) * 100 : 0;\\nconst overallCPA = totalConversions > 0 ? totalCost / totalConversions : 0;\\nconst overallCPL = totalLeads > 0 ? totalCost / totalLeads : 0;\\nconst overallCPC = totalClicks > 0 ? totalCost / totalClicks : 0;\\nconst overallROAS = totalCost > 0 ? totalRevenue / totalCost : 0;\\nconst overallConversionRate = totalClicks > 0 ? (totalConversions / totalClicks) * 100 : 0;\\n\\n// Get unique campaigns for campaign count\\nconst uniqueCampaigns = [...new Set(campaigns.map(c => c.campaign_name))];\\n\\n// Top performers (by ROAS and CTR combined)\\nconst campaignPerformance = {};\\ncampaigns.forEach(c => {\\n const key = c.campaign_name;\\n if (!campaignPerformance[key]) {\\n campaignPerformance[key] = {\\n campaign_name: key,\\n total_cost: 0,\\n total_impressions: 0,\\n total_clicks: 0,\\n total_conversions: 0,\\n total_revenue: 0,\\n ad_groups: new Set()\\n };\\n }\\n campaignPerformance[key].total_cost += c.cost;\\n campaignPerformance[key].total_impressions += c.impressions;\\n campaignPerformance[key].total_clicks += c.clicks;\\n campaignPerformance[key].total_conversions += c.conversions;\\n campaignPerformance[key].total_revenue += c.revenue;\\n campaignPerformance[key].ad_groups.add(c.ad_group_name);\\n});\\n\\nconst topPerformers = Object.values(campaignPerformance)\\n .map(camp => ({\\n ...camp,\\n ctr: camp.total_impressions > 0 ? (camp.total_clicks / camp.total_impressions) * 100 : 0,\\n roas: camp.total_cost > 0 ? camp.total_revenue / camp.total_cost : 0,\\n cpa: camp.total_conversions > 0 ? camp.total_cost / camp.total_conversions : 0,\\n ad_groups_count: camp.ad_groups.size\\n }))\\n .filter(camp => camp.total_impressions > 1000)\\n .sort((a, b) => {\\n // Score based on ROAS (50%), CTR (30%), and low CPA (20%)\\n const scoreA = (a.roas * 0.5) + (a.ctr * 0.3) + ((100 - a.cpa) * 0.2);\\n const scoreB = (b.roas * 0.5) + (b.ctr * 0.3) + ((100 - b.cpa) * 0.2);\\n return scoreB - scoreA;\\n })\\n .slice(0, 3);\\n\\n// Underperforming campaigns (low ROAS or high CPA)\\nconst underperformers = Object.values(campaignPerformance)\\n .map(camp => ({\\n ...camp,\\n ctr: camp.total_impressions > 0 ? (camp.total_clicks / camp.total_impressions) * 100 : 0,\\n roas: camp.total_cost > 0 ? camp.total_revenue / camp.total_cost : 0,\\n cpa: camp.total_conversions > 0 ? camp.total_cost / camp.total_conversions : 0\\n }))\\n .filter(camp => camp.total_impressions > 500 && (camp.roas < 2.0 || camp.cpa > 50))\\n .sort((a, b) => a.roas - b.roas) // Sort by worst ROAS first\\n .slice(0, 5);\\n\\n// Keyword analysis\\nconst keywordPerf = {};\\ncampaigns.forEach(c => {\\n const key = c.keyword;\\n if (!keywordPerf[key]) {\\n keywordPerf[key] = {\\n keyword: key,\\n total_cost: 0,\\n total_impressions: 0,\\n total_clicks: 0,\\n total_conversions: 0,\\n campaigns: new Set()\\n };\\n }\\n keywordPerf[key].total_cost += c.cost;\\n keywordPerf[key].total_impressions += c.impressions;\\n keywordPerf[key].total_clicks += c.clicks;\\n keywordPerf[key].total_conversions += c.conversions;\\n keywordPerf[key].campaigns.add(c.campaign_name);\\n});\\n\\nconst topKeywords = Object.values(keywordPerf)\\n .map(kw => ({\\n ...kw,\\n ctr: kw.total_impressions > 0 ? (kw.total_clicks / kw.total_impressions) * 100 : 0,\\n cpc: kw.total_clicks > 0 ? kw.total_cost / kw.total_clicks : 0,\\n conversion_rate: kw.total_clicks > 0 ? (kw.total_conversions / kw.total_clicks) * 100 : 0,\\n campaign_count: kw.campaigns.size\\n }))\\n .filter(kw => kw.total_impressions > 200)\\n .sort((a, b) => b.conversion_rate - a.conversion_rate)\\n .slice(0, 5);\\n\\n// Device performance analysis\\nconst devicePerf = {};\\ncampaigns.forEach(c => {\\n const key = c.device;\\n if (!devicePerf[key]) {\\n devicePerf[key] = {\\n device: key,\\n total_cost: 0,\\n total_impressions: 0,\\n total_clicks: 0,\\n total_conversions: 0,\\n total_revenue: 0\\n };\\n }\\n devicePerf[key].total_cost += c.cost;\\n devicePerf[key].total_impressions += c.impressions;\\n devicePerf[key].total_clicks += c.clicks;\\n devicePerf[key].total_conversions += c.conversions;\\n devicePerf[key].total_revenue += c.revenue;\\n});\\n\\nconst topDevices = Object.values(devicePerf)\\n .map(dev => ({\\n ...dev,\\n ctr: dev.total_impressions > 0 ? (dev.total_clicks / dev.total_impressions) * 100 : 0,\\n roas: dev.total_cost > 0 ? dev.total_revenue / dev.total_cost : 0,\\n conversion_rate: dev.total_clicks > 0 ? (dev.total_conversions / dev.total_clicks) * 100 : 0\\n }))\\n .filter(dev => dev.total_impressions > 100)\\n .sort((a, b) => b.roas - a.roas);\\n\\nconst statistics = {\\n analysis_date: new Date().toISOString().split('T')[0],\\n total_campaigns: uniqueCampaigns.length,\\n total_cost: totalCost,\\n overall_ctr: overallCTR,\\n overall_cpa: overallCPA,\\n overall_cpl: overallCPL,\\n overall_cpc: overallCPC,\\n overall_roas: overallROAS,\\n overall_conversion_rate: overallConversionRate,\\n total_impressions: totalImpressions,\\n total_clicks: totalClicks,\\n total_conversions: totalConversions,\\n total_leads: totalLeads,\\n total_revenue: totalRevenue,\\n top_performers: topPerformers,\\n underperformers: underperformers,\\n top_keywords: topKeywords,\\n top_devices: topDevices,\\n all_campaigns: campaigns\\n};\\n\\nconsole.log('Google Ads Statistics calculated:', {\\n total_campaigns: statistics.total_campaigns,\\n underperformers: statistics.underperformers.length,\\n overall_roas: statistics.overall_roas.toFixed(2)\\n});\\n\\nreturn [{ json: statistics }];"
},
"id": "65593872-a00b-4c53-8e12-261df09a008c",
"name": "Calculate Google Ads Statistics",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-64,
0
]
},
{
"parameters": {
"jsCode": "// Improved Generate Google Ads email report – visually enhanced headers & block separation\\nconsole.log('Input items:', items.length);\\nconsole.log('Item keys:', Object.keys(items[0].json));\\n\\n// Get AI response text properly\\nlet aiAnalysis = 'AI analysis not available';\\nif (items[0].json && items[0].json.message) {\\n aiAnalysis = items[0].json.message;\\n} else if (items[0].json && items[0].json.choices && items[0].json.choices[0]) {\\n aiAnalysis = items[0].json.choices[0].message.content;\\n} else if (typeof items[0].json === 'string') {\\n aiAnalysis = items[0].json;\\n}\\n\\n// Get stats from the Calculate Statistics node\\nconst stats = $('Calculate Google Ads Statistics').first().json;\\n\\nconsole.log('Stats loaded:', {\\n total_campaigns: stats.total_campaigns,\\n total_cost: stats.total_cost,\\n overall_roas: stats.overall_roas\\n});\\n\\n// Function to parse AI analysis into structured blocks (keeps your existing parsing logic)\\nfunction parseAIAnalysisIntoBlocks(text) {\\n const sections = text.split(/(?=\\\\d+\\\\.\\\\s*\\\\*\\\\*|\\\\n\\\\*\\\\*|^\\\\*\\\\*)/m);\\n const blocks = [];\\n const actionItems = [];\\n \\n sections.forEach(section => {\\n section = section.trim();\\n if (!section) return;\\n \\n const titleMatch = section.match(/^(\\\\d+\\\\.\\\\s*)?\\\\*\\\\*([^*]+)\\\\*\\\\*/);\\n if (titleMatch) {\\n const title = titleMatch[2].trim();\\n const content = section.replace(/^(\\\\d+\\\\.\\\\s*)?\\\\*\\\\*[^*]+\\\\*\\\\*/, '').trim();\\n \\n if (title.toLowerCase().includes('action') || title.toLowerCase().includes('next steps') || title.toLowerCase().includes('recommendations')) {\\n const actionMatches = content.match(/\\\\d+\\\\.\\\\s*[^.\\\\n]+/g);\\n if (actionMatches) {\\n actionMatches.forEach((action, index) => {\\n const actionText = action.replace(/^\\\\d+\\\\.\\\\s*/, '').trim();\\n let priority = 'medium';\\n const highPriorityKeywords = ['urgent', 'critical', 'immediate', 'emergency', 'stop', 'pause', 'increase budget', 'optimize'];\\n const lowPriorityKeywords = ['consider', 'optional', 'future', 'long-term', 'eventually', 'monitor', 'review'];\\n const lowerAction = actionText.toLowerCase();\\n if (highPriorityKeywords.some(keyword => lowerAction.includes(keyword))) priority = 'high';\\n if (lowPriorityKeywords.some(keyword => lowerAction.includes(keyword))) priority = 'low';\\n actionItems.push({\\n text: actionText,\\n priority: priority,\\n order: priority === 'high' ? 1 : priority === 'medium' ? 2 : 3\\n });\\n });\\n }\\n return;\\n }\\n \\n let priority = 'medium';\\n const skipPriorityTitles = ['executive summary', 'top performers analysis', 'top performing'];\\n if (!skipPriorityTitles.some(skipTitle => title.toLowerCase().includes(skipTitle))) {\\n const highPriorityKeywords = ['urgent', 'critical', 'immediate', 'emergency', 'underperforming'];\\n const lowPriorityKeywords = ['consider', 'optional', 'future', 'long-term', 'eventually'];\\n const lowerTitle = title.toLowerCase();\\n const lowerContent = content.toLowerCase();\\n if (highPriorityKeywords.some(keyword => lowerTitle.includes(keyword) || lowerContent.includes(keyword))) priority = 'high';\\n else if (lowPriorityKeywords.some(keyword => lowerTitle.includes(keyword) || lowerContent.includes(keyword))) priority = 'low';\\n } else {\\n priority = null;\\n }\\n \\n blocks.push({\\n title: title,\\n content: content.replace(/\\\\n/g, '<br>'),\\n priority: priority\\n });\\n }\\n });\\n \\n if (blocks.length === 0) {\\n const paragraphs = text.split('\\\\n\\\\n').filter(p => p.trim().length > 50);\\n paragraphs.forEach((paragraph, index) => {\\n const sentences = paragraph.split('.');\\n const title = sentences[0] ? sentences[0].trim() + '.' : `Recommendation ${index + 1}`;\\n const content = sentences.slice(1).join('.').trim();\\n blocks.push({\\n title: title.length > 80 ? title.substring(0, 80) + '...' : title,\\n content: content || paragraph.replace(/\\\\n/g, '<br>'),\\n priority: 'medium'\\n });\\n });\\n }\\n \\n return { blocks, actionItems };\\n}\\n\\nconst { blocks: analysisBlocks, actionItems } = parseAIAnalysisIntoBlocks(aiAnalysis);\\nconst sortedActionItems = actionItems.sort((a, b) => a.order - b.order);\\n\\nfunction formatCurrency(amount) {\\n return `$${(amount || 0).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;\\n}\\nfunction formatROAS(roas) {\\n return `${(roas || 0).toFixed(2)}x`;\\n}\\nfunction formatPercent(value) {\\n return `${(value || 0).toFixed(2)}%`;\\n}\\n\\n// Determine alert status based on performance\\nconst getAlertStatus = () => {\\n const underperformerCount = stats.underperformers ? stats.underperformers.length : 0;\\n if (stats.overall_roas < 2.0 || underperformerCount > 2) {\\n return { level: 'critical', message: `🚨 Critical: ${underperformerCount} underperforming campaigns`, color: '#dc3545' };\\n } else if (stats.overall_roas < 3.0 || underperformerCount > 0) {\\n return { level: 'warning', message: `⚠️ Warning: ${underperformerCount} campaigns need attention`, color: '#fd7e14' };\\n } else {\\n return { level: 'good', message: '✅ All campaigns performing well', color: '#28a745' };\\n }\\n};\\nconst alertStatus = getAlertStatus();\\n\\n// ===== Updated email HTML with stronger header visuals & block separation =====\\nconst emailHTML = `\\n<html>\\n<head>\\n <meta charset=\\"utf-8\\" />\\n <meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1.0\\"/>\\n <style>\\n /* General safe stack */\\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; margin: 0; padding: 20px; background: #f5f7fa; color: #1f2937; -webkit-font-smoothing:antialiased; }\\n .container { max-width: 900px; margin: 0 auto; background: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 6px 22px rgba(16,24,40,0.08); }\\n\\n /* Top header: kept bold and prominent */\\n .header { background: linear-gradient(90deg,#1a73e8 0%, #4285f4 100%); color: #ffffff; padding: 22px 20px; text-align: left; }\\n .header h1 { margin: 0; font-size: 20px; font-weight: 800; letter-spacing: -0.2px; }\\n .header p { margin: 6px 0 0 0; font-size: 13px; opacity: 0.95; }\\n .badge { display:inline-block; margin-top:10px; font-weight:700; font-size:12px; padding:6px 12px; border-radius:20px; background: rgba(255,255,255,0.12); color:#fff; }\\n\\n /* Metrics */\\n .metrics { padding: 18px 20px; border-bottom: 1px solid #eef2f7; }\\n .metrics-row { display:flex; flex-wrap:wrap; gap:12px; justify-content:flex-start; }\\n .metric { flex:0 0 140px; background:#fbfdff; border-radius:8px; padding:12px; border:1px solid #eef2f7; }\\n .metric .v { font-size:18px; font-weight:800; color:#0f172a; }\\n .metric .l { font-size:11px; color:#64748b; font-weight:700; margin-top:6px; text-transform:uppercase; letter-spacing:0.6px; }\\n\\n /* Alert */\\n .alert { padding:14px 18px; border-left:6px solid ${alertStatus.color}; background:${alertStatus.color}18; margin:16px 20px; border-radius:6px; }\\n .alert .title { font-weight:800; font-size:14px; color:${alertStatus.color}; display:flex; align-items:center; gap:8px; }\\n\\n /* Section title – distinct background to separate blocks */\\n .section-title {\\n display:block;\\n background:#e8f0fe;\\n padding:12px 18px;\\n margin:18px 20px 8px 20px;\\n border-radius:8px;\\n font-size:16px;\\n font-weight:800;\\n color:#1a56db;\\n box-shadow: inset 0 -1px 0 rgba(255,255,255,0.6);\\n }\\n\\n /* Each recommendation block with bold header + colored header row */\\n .recommendation-block { margin: 8px 20px 0 20px; border-radius:8px; overflow:hidden; border:1px solid #e6eef8; background:#fff; }\\n .block-header {\\n background: #f0f7ff; /* distinct header color */\\n padding:12px 16px;\\n display:flex;\\n justify-content:space-between;\\n align-items:center;\\n border-bottom:1px solid #e6eef8;\\n }\\n .block-header .title { font-weight:800; font-size:14px; color:#0f172a; margin:0; }\\n .block-header .meta { font-size:12px; color:#475569; font-weight:700; text-transform:uppercase; letter-spacing:0.4px; }\\n\\n .block-body { padding:14px 16px; font-size:13px; color:#374151; line-height:1.55; }\\n\\n /* Priority badges – stronger contrast */\\n .priority-badge { padding:6px 10px; border-radius:18px; font-weight:800; font-size:11px; text-transform:uppercase; }\\n .priority-high { background:#fff5f5; color:#b91c1c; border:1px solid #fecaca; }\\n .priority-medium { background:#fffbeb; color:#92400e; border:1px solid #fcd34d; }\\n .priority-low { background:#eef2ff; color:#075985; border:1px solid #bfdbfe; }\\n\\n /* Action plan items – header with different background so they pop */\\n .action-plan { margin:16px 20px 20px 20px; padding:12px; background:#f8fafc; border-radius:8px; border:1px solid #eef2f7; }\\n .action-row { display:flex; gap:12px; align-items:center; padding:10px; background:#ffffff; border-radius:6px; border:1px solid #eef2f7; margin-bottom:10px; }\\n .action-num { width:34px; height:34px; border-radius:50%; background:#1a73e8; color:#fff; display:flex; align-items:center; justify-content:center; font-weight:800; }\\n .action-text { font-weight:700; color:#0f172a; font-size:13px; flex:1; }\\n .action-meta { font-size:12px; color:#475569; font-weight:700; }\\n\\n /* Footer */\\n .footer { background:#f8fafc; padding:16px 20px; border-top:1px solid #eef2f7; font-size:13px; color:#64748b; text-align:left; }\\n .footer strong { color:#0f172a; font-weight:800; }\\n\\n /* Responsiveness for mobile */\\n @media only screen and (max-width:520px) {\\n .metrics-row { gap:10px; }\\n .metric { flex: 1 1 48%; min-width: 120px; }\\n .block-header, .block-body, .action-row { padding-left:12px; padding-right:12px; }\\n .section-title { margin-left:12px; margin-right:12px; }\\n .recommendation-block { margin-left:12px; margin-right:12px; }\\n }\\n </style>\\n</head>\\n<body>\\n <div class=\\"container\\">\\n <div class=\\"header\\">\\n <h1>🎯 Google Ads Performance Report</h1>\\n <p>Campaign Analysis Dashboard – ${new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}</p>\\n <span class=\\"badge\\">Powered by AI Analytics</span>\\n </div>\\n\\n <div class=\\"metrics\\">\\n <div class=\\"metrics-row\\">\\n <div class=\\"metric\\">\\n <div class=\\"v\\">${stats.total_campaigns || 0}</div>\\n <div class=\\"l\\">Total Campaigns</div>\\n </div>\\n <div class=\\"metric\\">\\n <div class=\\"v\\">${formatCurrency(stats.total_cost || 0)}</div>\\n <div class=\\"l\\">Total Cost</div>\\n </div>\\n <div class=\\"metric\\">\\n <div class=\\"v\\">${formatROAS(stats.overall_roas || 0)}</div>\\n <div class=\\"l\\">Overall ROAS</div>\\n </div>\\n <div class=\\"metric\\">\\n <div class=\\"v\\">${formatPercent(stats.overall_ctr || 0)}</div>\\n <div class=\\"l\\">Overall CTR</div>\\n </div>\\n <div class=\\"metric\\">\\n <div class=\\"v\\">${formatCurrency(stats.overall_cpa || 0)}</div>\\n <div class=\\"l\\">Overall CPA</div>\\n </div>\\n <div class=\\"metric\\">\\n <div class=\\"v\\">${formatCurrency(stats.overall_cpc || 0)}</div>\\n <div class=\\"l\\">Overall CPC</div>\\n </div>\\n </div>\\n </div>\\n\\n <div class=\\"alert\\" role=\\"note\\" aria-label=\\"account alert\\">\\n <div class=\\"title\\"><span style=\\"width:12px;height:12px;border-radius:50%;display:inline-block;background:${alertStatus.color};\\"></span> ${alertStatus.message}</div>\\n ${(stats.overall_roas || 0) < 3.0 ? `<div style=\\"margin-top:8px;color:#475569;font-weight:700;\\">ROAS below target – immediate optimization recommended.</div>` : ''}\\n </div>\\n\\n <div class=\\"section-title\\">AI Analysis & Recommendations</div>\\n\\n ${analysisBlocks.map(block => `\\n <div class=\\"recommendation-block\\">\\n <div class=\\"block-header\\">\\n <div class=\\"title\\">${block.title}</div>\\n <div class=\\"meta\\">${block.priority ? `<span class=\\"priority-badge priority-${block.priority}\\">${block.priority}</span>` : ''}</div>\\n </div>\\n <div class=\\"block-body\\">${block.content}</div>\\n </div>\\n `).join('')}\\n\\n ${sortedActionItems.length > 0 ? `\\n <div class=\\"section-title\\">Action Plan – Prioritized Steps</div>\\n <div class=\\"action-plan\\">\\n ${sortedActionItems.map((action, idx) => `\\n <div class=\\"action-row\\">\\n <div class=\\"action-num\\">${idx + 1}</div>\\n <div class=\\"action-text\\">${action.text}</div>\\n <div class=\\"action-meta\\"><span class=\\"priority-badge priority-${action.priority}\\">${action.priority}</span></div>\\n </div>\\n `).join('')}\\n </div>\\n ` : ''}\\n\\n <div class=\\"footer\\">\\n <div><strong>📊 Google Ads Intelligence Report</strong></div>\\n <div style=\\"margin-top:8px;\\">Generated automatically on ${new Date().toLocaleString()} • Next report: Tomorrow at 9:00 AM</div>\\n <div style=\\"margin-top:10px;\\">🤖 Created by [Your Name]</div>\\n <div style=\\"margin-top:12px;color:#506277;font-weight:700;\\">Account Performance: ${stats.total_campaigns || 0} campaigns • ${formatCurrency(stats.total_cost || 0)} spend • ${formatROAS(stats.overall_roas || 0)} avg ROAS</div>\\n </div>\\n </div>\\n</body>\\n</html>\\n`;\\n\\nconst subject = `🎯 Google Ads Report - ${alertStatus.level === 'critical' ? '🚨' : alertStatus.level === 'warning' ? '⚠️' : '✅'} ${formatROAS(stats.overall_roas || 0)} ROAS | ${stats.total_campaigns || 0} campaigns | ${formatCurrency(stats.total_cost || 0)} spend`;\\n\\nreturn [{\\n json: {\\n subject: subject,\\n html: emailHTML\\n }\\n}];"
},
"id": "",
"name": "Generate Google Ads Email",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
416,
176
]
},
{
"parameters": {
"sendTo": "[email protected]",
"subject": "={{ $json.subject }}",
"message": "={{ $json.html }}",
"options": {
"appendAttribution": false
}
},
"id": "",
"name": "Send Google Ads Email Report",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2,
"position": [
624,
176
],
"notes": "Configure with your Gmail OAuth2 credentials in n8n"
},
{
"parameters": {
"operation": "append",
"documentId": {
"__rl": true,
"value": "YOUR_GOOGLE_SHEET_ID",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "YOUR_SHEET_GID",
"mode": "list",
"cachedResultName": "AI_Analysis_Results",
"cachedResultUrl": "<https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=YOUR_SHEET_GID>"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"analysis_date": "={{ $('Calculate Google Ads Statistics').item.json.analysis_date }}",
"total_campaigns": "={{ $('Calculate Google Ads Statistics').item.json.total_campaigns }}",
"total_cost": "={{ $('Calculate Google Ads Statistics').item.json.total_cost }}",
"total_revenue": "={{ $('Calculate Google Ads Statistics').item.json.total_revenue }}",
"overall_roas": "={{ $('Calculate Google Ads Statistics').item.json.overall_roas }}",
"overall_ctr": "={{ $('Calculate Google Ads Statistics').item.json.overall_ctr }}",
"overall_cpa": "={{ $('Calculate Google Ads Statistics').item.json.overall_cpa }}",
"overall_cpl": "={{ $('Calculate Google Ads Statistics').item.json.overall_cpl }}",
"overall_cpc": "={{ $('Calculate Google Ads Statistics').item.json.overall_cpc }}",
"overall_conversion_rate": "={{ $('Calculate Google Ads Statistics').item.json.overall_conversion_rate }}",
"underperformers_count": "={{ $('Calculate Google Ads Statistics').item.json.underperformers }}",
"top_performer_1": "={{ $('Calculate Google Ads Statistics').item.json.top_performers[0] }}",
"top_performer_2": "={{ $('Calculate Google Ads Statistics').item.json.top_performers[1] }}",
"top_performer_3": "={{ $('Calculate Google Ads Statistics').item.json.top_performers[2] }}",
"top_keyword_1": "={{ $('Calculate Google Ads Statistics').item.json.top_keywords[0] }}",
"top_keyword_2": "={{ $('Calculate Google Ads Statistics').item.json.top_keywords[1] }}",
"best_device": "={{ $('Calculate Google Ads Statistics').item.json.top_devices[0] }}"
},
"matchingColumns": [
"id"
],
"schema": [
{
"id": "analysis_date",
"displayName": "analysis_date",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "total_campaigns",
"displayName": "total_campaigns",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "total_cost",
"displayName": "total_cost",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "total_revenue",
"displayName": "total_revenue",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "overall_roas",
"displayName": "overall_roas",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "overall_ctr",
"displayName": "overall_ctr",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "overall_cpa",
"displayName": "overall_cpa",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "overall_cpl",
"displayName": "overall_cpl",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "overall_cpc",
"displayName": "overall_cpc",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "overall_conversion_rate",
"displayName": "overall_conversion_rate",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "underperformers_count",
"displayName": "underperformers_count",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "top_performer_1",
"displayName": "top_performer_1",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "top_performer_2",
"displayName": "top_performer_2",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "top_performer_3",
"displayName": "top_performer_3",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "top_keyword_1",
"displayName": "top_keyword_1",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "top_keyword_2",
"displayName": "top_keyword_2",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "best_device",
"displayName": "best_device",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"id": "",
"name": "Export to Google Ads Results Sheet",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"position": [
416,
-112
],
"notes": "Configure with your Google Sheets OAuth2 credentials in n8n"
},
{
"parameters": {
"messages": {
"message": [
{
"content": "=Analyze this Google Ads campaign performance data:\\n\\nOVERALL PERFORMANCE:\\n- Total Campaigns: {{ $json.total_campaigns }}\\n- Total Cost: ${{ $json.total_cost.toFixed(2) }}\\n- Total Revenue: ${{ $json.total_revenue.toFixed(2) }}\\n- Overall ROAS: {{ $json.overall_roas.toFixed(2) }}x\\n- Overall CTR: {{ $json.overall_ctr.toFixed(2) }}%\\n- Overall CPA: ${{ $json.overall_cpa.toFixed(2) }}\\n- Overall CPL: ${{ $json.overall_cpl.toFixed(2) }}\\n- Overall CPC: ${{ $json.overall_cpc.toFixed(2) }}\\n- Overall Conversion Rate: {{ $json.overall_conversion_rate.toFixed(2) }}%\\n\\nTOP PERFORMING CAMPAIGNS:\\n{{ $json.top_performers.map(p => `- ${p.campaign_name}: ${p.roas.toFixed(2)}x ROAS, ${p.ctr.toFixed(2)}% CTR, $${p.cpa.toFixed(2)} CPA, ${p.ad_groups_count} ad groups`).join('\\\\n') }}\\n\\nUNDERPERFORMING CAMPAIGNS (Need Attention):\\n{{ $json.underperformers.map(u => `- ${u.campaign_name}: ${u.roas.toFixed(2)}x ROAS, ${u.ctr.toFixed(2)}% CTR, $${u.cpa.toFixed(2)} CPA`).join('\\\\n') }}\\n\\nTOP KEYWORDS BY CONVERSION RATE:\\n{{ $json.top_keywords.map(k => `- \\"${k.keyword}\\": ${k.conversion_rate.toFixed(2)}% Conv Rate, ${k.ctr.toFixed(2)}% CTR, $${k.cpc.toFixed(2)} CPC, used in ${k.campaign_count} campaigns`).join('\\\\n') }}\\n\\nDEVICE PERFORMANCE:\\n{{ $json.top_devices.map(d => `- ${d.device}: ${d.roas.toFixed(2)}x ROAS, ${d.ctr.toFixed(2)}% CTR, ${d.conversion_rate.toFixed(2)}% Conv Rate`).join('\\\\n') }}\\n\\nKEY INSIGHTS:\\n- Your overall ROAS is {{ $json.overall_roas.toFixed(2) }}x (target: 4x+)\\n- Overall CTR is {{ $json.overall_ctr.toFixed(2) }}% (Google Ads avg: 2-3%)\\n- You have {{ $json.underperformers.length }} campaigns with ROAS < 2.0x that need immediate attention\\n- Overall CPA is ${{ $json.overall_cpa.toFixed(2) }}\\n\\nProvide comprehensive analysis with:\\n\\n1. Executive Summary - Overall account health and key findings\\n\\n2. Top Performers Analysis - Why these campaigns are succeeding and how to scale them\\n\\n3. URGENT: Underperforming Campaigns - Immediate actions needed for campaigns with poor ROAS/high CPA\\n\\n4. Keyword Optimization Strategy - \\n - High-performing keywords to expand\\n - Negative keyword recommendations\\n - Bid optimization suggestions\\n\\n5. Budget Reallocation Recommendations - \\n - Where to increase/decrease spend\\n - Specific budget shifts between campaigns\\n\\n6. Device & Targeting Insights - \\n - Device performance differences\\n - Bidding adjustments needed\\n\\n7. Quality Score Improvements - \\n - Ad copy optimization suggestions\\n - Landing page recommendations\\n - Keyword relevance improvements\\n\\n8. Action Plan - Prioritized steps with expected impact\\n\\nFocus on ROAS optimization, Quality Score improvements, and specific Google Ads best practices. Be actionable and include specific bid adjustments, budget shifts, and optimization tactics."
}
]
},
"simplify": true,
"options": {
"maxTokens": 2000
},
"requestOptions": {}
},
"type": "n8n-nodes-base.perplexity",
"typeVersion": 1,
"position": [
144,
0
],
"id": "",
"name": "AI Google Ads Analysis",
"notes": "Configure with your Perplexity API credentials in n8n"
}
],
"pinData": {},
"connections": {
"Daily Google Ads Analysis": {
"main": [
[
{
"node": "Fetch Google Ads Data",
"type": "main",
"index": 0
}
]
]
},
"Fetch Google Ads Data": {
"main": [
[
{
"node": "Validate Google Ads Data",
"type": "main",
"index": 0
}
]
]
},
"Validate Google Ads Data": {
"main": [
[
{
"node": "Calculate Google Ads Statistics",
"type": "main",
"index": 0
}
]
]
},
"Calculate Google Ads Statistics": {
"main": [
[
{
"node": "AI Google Ads Analysis",
"type": "main",
"index": 0
}
]
]
},
"Generate Google Ads Email": {
"main": [
[
{
"node": "Send Google Ads Email Report",
"type": "main",
"index": 0
}
]
]
},
"AI Google Ads Analysis": {
"main": [
[
{
"node": "Export to Google Ads Results Sheet",
"type": "main",
"index": 0
},
{
"node": "Generate Google Ads Email",
"type": "main",
"index": 0
}
]
]
},
"Send Google Ads Email Report": {
"main": [
[]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"meta": {
"templateCredsSetupCompleted": false
},
"tags": []
}
