AutomationFlowsEmail & Gmail › Monthly Ad Performance Report via Gmail

Monthly Ad Performance Report via Gmail

Original n8n title: Analyze Ad Performance From Meta, Google and Microsoft Using Groq Llama 3.3 and Gmail

ByAyush Singh @ayushsingh0304 on n8n.io

This workflow automatically generates a detailed AI-powered campaign performance report across Meta, Google and Microsoft Ads and emails it to your team every month.

Cron / scheduled trigger★★★★☆ complexity14 nodesGmailGoogle SheetsHTTP Request
Email & Gmail Trigger: Cron / scheduled Nodes: 14 Complexity: ★★★★☆ Added:

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

This workflow follows the Gmail → Google Sheets 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": "yerOcPTLOaQCMez0",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Analyze ad performance across Meta, Google and Microsoft with AI",
  "tags": [],
  "nodes": [
    {
      "id": "86ede9b9-b627-4fa0-b6c2-5564d868f9dc",
      "name": "Send a message",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1584,
        0
      ],
      "parameters": {
        "sendTo": "user@example.com",
        "message": "={{ $json.html }}",
        "options": {},
        "subject": "={{ $json.subject }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "b942bb9f-15e8-4d27-bab2-2d472796e8c0",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        32,
        -288
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 128,
        "content": "## DATA FETCH & FORMAT\nReads campaign data from Google Sheet.\nFormats rows into a single text string for AI analysis.\nSetup: Add your Sheet ID in the Google Sheets node."
      },
      "typeVersion": 1
    },
    {
      "id": "45c50895-30f4-4362-94f6-e526b23053a9",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -336,
        176
      ],
      "parameters": {
        "color": 7,
        "width": 304,
        "height": 112,
        "content": "## TRIGGER\nRuns every Months 1st day automatically.\nChange schedule in the trigger node settings."
      },
      "typeVersion": 1
    },
    {
      "id": "bf2e5bfb-ef1e-4304-8cfa-6e9b23ed68ae",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1008,
        192
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "content": "## AI ANALYSIS & EMAIL\nSends campaign data to Groq (Llama 3.3) for analysis.\nExtracts the report and emails it automatically.\nSetup: Add your Groq API key and Gmail credentials."
      },
      "typeVersion": 1
    },
    {
      "id": "3dddcc48-d596-47e1-8791-ff871c55e3d8",
      "name": "Read - Meta ads",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        160,
        16
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 2091689278,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1w9IAdOWFl2iSYQN_c-d0lFJTUsXm4-SCR9xj_vO2jv8/edit#gid=2091689278",
          "cachedResultName": "Meta"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1w9IAdOWFl2iSYQN_c-d0lFJTUsXm4-SCR9xj_vO2jv8",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1w9IAdOWFl2iSYQN_c-d0lFJTUsXm4-SCR9xj_vO2jv8/edit?usp=drivesdk",
          "cachedResultName": "Campaign data"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "b35f5427-3c47-4437-b49b-e2c94a4ac1f1",
      "name": "Read - Google ads",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        160,
        -144
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1w9IAdOWFl2iSYQN_c-d0lFJTUsXm4-SCR9xj_vO2jv8/edit#gid=0",
          "cachedResultName": "Google"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1w9IAdOWFl2iSYQN_c-d0lFJTUsXm4-SCR9xj_vO2jv8",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1w9IAdOWFl2iSYQN_c-d0lFJTUsXm4-SCR9xj_vO2jv8/edit?usp=drivesdk",
          "cachedResultName": "Campaign data"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "b2c05241-11aa-4b49-a81f-aa75cdb83f42",
      "name": "Read - Microsoft ads",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        160,
        192
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 1616843299,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1w9IAdOWFl2iSYQN_c-d0lFJTUsXm4-SCR9xj_vO2jv8/edit#gid=1616843299",
          "cachedResultName": "Microsoft"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1w9IAdOWFl2iSYQN_c-d0lFJTUsXm4-SCR9xj_vO2jv8",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1w9IAdOWFl2iSYQN_c-d0lFJTUsXm4-SCR9xj_vO2jv8/edit?usp=drivesdk",
          "cachedResultName": "Campaign data"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "8e2659a4-6c44-428e-bb41-b8ebf2d6f22d",
      "name": "Merge all platform",
      "type": "n8n-nodes-base.merge",
      "position": [
        416,
        0
      ],
      "parameters": {
        "numberInputs": 3
      },
      "typeVersion": 3.2
    },
    {
      "id": "979db74e-e046-477c-a99d-73fec84f9648",
      "name": "Calculate - KPIs & Build Prompt",
      "type": "n8n-nodes-base.code",
      "position": [
        640,
        16
      ],
      "parameters": {
        "jsCode": "// ============================================================\n// Code Node 1: \"Calculate - KPIs & Build Prompt\"\n// Reads merged rows from Google / Meta / Microsoft tabs\n// Outputs: kpi object + groqPrompt string\n// ============================================================\n\nconst num = v => parseFloat(String(v).replace(/[,%$\\s]/g,'')) || 0;\n\nfunction detectPlatform(r) {\n  if (r['Platform']) return r['Platform'];\n  if (r['Campaign name'] !== undefined) return 'Meta';\n  if (r['Campaign status'] !== undefined) return 'Google';\n  if (r['Status'] !== undefined && r['Impr.'] !== undefined) return 'Microsoft';\n  return null;\n}\n\nfunction normalise(r) {\n  const p = detectPlatform(r);\n  if (p === 'Google') return {\n    platform:'Google', campaign:String(r['Campaign']||''),\n    status:String(r['Campaign status']||''), type:String(r['Campaign type']||''),\n    impressions:num(String(r['Impr.']||'').replace(/,/g,'')),\n    clicks:num(r['Clicks']), spend:num(r['Cost']),\n    conversions:num(r['Conversions']), roas:num(r['Conv. value / cost']),\n    cpm:0, purchases:num(r['Conversions']), budget:num(r['Budget']),\n  };\n  if (p === 'Microsoft') return {\n    platform:'Microsoft', campaign:String(r['Campaign']||''),\n    status:String(r['Status']||''), type:String(r['Campaign Type']||''),\n    impressions:num(String(r['Impr.']||'').replace(/,/g,'')),\n    clicks:num(r['Clicks']), spend:num(r['Spend']),\n    conversions:num(r['Conv.']), roas:0,\n    cpm:0, purchases:num(r['Conv.']), budget:num(r['Budget']),\n  };\n  if (p === 'Meta') return {\n    platform:'Meta', campaign:String(r['Campaign name']||''),\n    status:String(r['Campaign delivery']||''), type:'Social',\n    impressions:num(r['Impressions']), clicks:0,\n    spend:num(r['Amount spent (USD)']),\n    conversions:num(r['Results']), roas:num(r['Purchase ROAS (return on ad spend)']),\n    cpm:num(r['CPM (cost per 1,000 impressions) (USD)']),\n    purchases:num(r['Purchases']), budget:num(r['Ad set budget']),\n  };\n  return null;\n}\n\nfunction agg(data) {\n  const impressions = data.reduce((s,r)=>s+r.impressions,0);\n  const clicks      = data.reduce((s,r)=>s+r.clicks,0);\n  const spend       = data.reduce((s,r)=>s+r.spend,0);\n  const conversions = data.reduce((s,r)=>s+r.conversions,0);\n  const purchases   = data.reduce((s,r)=>s+r.purchases,0);\n  return {\n    impressions, clicks, spend:+spend.toFixed(2), conversions, purchases,\n    ctr:  impressions>0&&clicks>0 ? +((clicks/impressions)*100).toFixed(2) : 0,\n    cpc:  clicks>0 ? +(spend/clicks).toFixed(2) : 0,\n    cpa:  conversions>0 ? +(spend/conversions).toFixed(2) : 0,\n    cpm:  impressions>0 ? +((spend/impressions)*1000).toFixed(2) : 0,\n  };\n}\n\nconst rows      = items.map(i=>i.json).map(normalise).filter(Boolean);\nconst google    = rows.filter(r=>r.platform==='Google');\nconst microsoft = rows.filter(r=>r.platform==='Microsoft');\nconst meta      = rows.filter(r=>r.platform==='Meta' && r.spend>0);\nconst all       = [...google, ...microsoft.filter(r=>r.spend>0), ...meta];\n\nconst tG    = agg(google);\nconst tM    = agg(microsoft.filter(r=>r.spend>0));\nconst tMeta = agg(meta);\nconst tAll  = agg(all);\nconst totalSpend = tAll.spend || 1;\n\nconst bestMS      = [...microsoft].filter(r=>r.conversions>0).sort((a,b)=>b.conversions-a.conversions)[0];\nconst worstMeta   = [...meta].filter(r=>r.conversions===0&&r.spend>50).sort((a,b)=>b.spend-a.spend)[0];\nconst bestMeta    = [...meta].filter(r=>r.roas>0).sort((a,b)=>b.roas-a.roas)[0];\nconst budgetPaused = microsoft.filter(r=>String(r.status).toLowerCase().includes('budget'));\n\nfunction campSummary(data) {\n  return data.filter(r=>r.spend>0).map(r=>\n    `  - ${r.campaign} | Spend:$${r.spend.toFixed(2)} | Impr:${r.impressions} | Conv:${r.conversions} | CPA:${r.conversions>0?'$'+(r.spend/r.conversions).toFixed(2):'N/A'} | ROAS:${r.roas>0?r.roas.toFixed(2)+'x':'N/A'}`\n  ).join('\\n');\n}\n\nconst groqPrompt = `You are a senior digital marketing analyst. Analyze this February 2026 ad performance data for a health supplements brand (Biogetica) and provide expert insights.\n\nACCOUNT SUMMARY\nTotal Spend: $${tAll.spend} | Impressions: ${tAll.impressions.toLocaleString()} | Clicks: ${tAll.clicks} | Conversions: ${tAll.conversions} | Blended CPA: ${tAll.cpa>0?'$'+tAll.cpa:'N/A'}\n\nPLATFORM BREAKDOWN\nMeta: Spend $${tMeta.spend} (${((tMeta.spend/totalSpend)*100).toFixed(1)}%) | Impr: ${tMeta.impressions} | Conv: ${tMeta.conversions} | CPA: ${tMeta.cpa>0?'$'+tMeta.cpa:'N/A'} | CPM: $${tMeta.cpm}\nMicrosoft: Spend $${tM.spend} (${((tM.spend/totalSpend)*100).toFixed(1)}%) | Impr: ${tM.impressions} | Clicks: ${tM.clicks} | Conv: ${tM.conversions} | CPA: ${tM.cpa>0?'$'+tM.cpa:'N/A'}\nGoogle: Spend $${tG.spend} (${((tG.spend/totalSpend)*100).toFixed(1)}%) | Impr: ${tG.impressions} | Clicks: ${tG.clicks} | Conv: ${tG.conversions}\n\nMETA CAMPAIGNS\n${campSummary(meta)}\n\nMICROSOFT CAMPAIGNS\n${campSummary(microsoft)}\n\nGOOGLE CAMPAIGNS\n${campSummary(google)}\n\nKEY FLAGS\n- Google: 0 conversions across all campaigns ($${tG.spend} spent)\n- Budget-paused Microsoft: ${budgetPaused.map(r=>r.campaign).join(', ')||'None'}\n- Best Meta ROAS: ${bestMeta?bestMeta.campaign+' ('+bestMeta.roas.toFixed(2)+'x)':'N/A'}\n- Worst Meta waste: ${worstMeta?worstMeta.campaign+' ($'+worstMeta.spend.toFixed(2)+', 0 conv)':'None'}\n- Best Microsoft: ${bestMS?bestMS.campaign+' ('+bestMS.conversions+' conv, $'+(bestMS.spend/bestMS.conversions).toFixed(2)+' CPA)':'N/A'}\n\nProvide analysis in these exact sections. Use ONLY the data above \u2014 do not say you lack data:\n\n### EXECUTIVE SUMMARY\n2-3 sentences on overall account health and the single most important finding.\n\n### WHAT'S WORKING\n3 bullet points on top performers with exact numbers from the data above.\n\n### WHAT NEEDS FIXING\n3 bullet points on underperformers with exact numbers.\n\n### AI RECOMMENDATIONS\n5 prioritized actionable recommendations referencing actual campaign names and dollar amounts.\n\n### MARCH 2026 FOCUS\n3 bullet points on what to prioritize next month.`;\n\nreturn [{\n  json: {\n    kpi: { tG, tM, tMeta, tAll, totalSpend, bestMS, worstMeta, bestMeta,\n           budgetPaused: budgetPaused.map(r=>r.campaign), google, microsoft, meta },\n    groqPrompt\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "45673594-8f7b-4954-a437-804ee2a23348",
      "name": "Build - HTML Report",
      "type": "n8n-nodes-base.code",
      "position": [
        1360,
        0
      ],
      "parameters": {
        "jsCode": "// ============================================================\n// Code Node 2: \"Build - HTML Report\"\n// Reads kpi from Code1 + AI insights from Groq (merged item)\n// ============================================================\n\nconst num = v => parseFloat(String(v).replace(/[,%$\\s]/g,'')) || 0;\nconst fmt = v => Number(v).toLocaleString();\nconst usd = v => '$' + (+v).toFixed(2);\n\n// \u2500\u2500 Pull data \u2014 Merge By Position puts Code1+Groq into items[0] \u2500\u2500\nconst j            = items[0].json;\nconst kpi          = j.kpi          || {};\nconst tG           = kpi.tG         || {};\nconst tM           = kpi.tM         || {};\nconst tMeta        = kpi.tMeta      || {};\nconst tAll         = kpi.tAll       || {};\nconst totalSpend   = kpi.totalSpend || 1;\nconst google       = kpi.google     || [];\nconst microsoft    = kpi.microsoft  || [];\nconst meta         = kpi.meta       || [];\nconst budgetPaused = kpi.budgetPaused || [];\nconst aiRaw        = j.choices?.[0]?.message?.content || '';\n\n// \u2500\u2500 Spend shares \u2500\u2500\nconst metaPct = ((( tMeta.spend||0)/totalSpend)*100).toFixed(1);\nconst msPct   = (((tM.spend||0)/totalSpend)*100).toFixed(1);\nconst gPct    = (((tG.spend||0)/totalSpend)*100).toFixed(1);\n\n// \u2500\u2500 SVG: Spend donut \u2500\u2500\nfunction donut() {\n  const data = [\n    {l:'Meta',      v:tMeta.spend||0, c:'#334155'},\n    {l:'Microsoft', v:tM.spend||0,    c:'#64748B'},\n    {l:'Google',    v:tG.spend||0,    c:'#94A3B8'},\n  ];\n  const total = data.reduce((s,d)=>s+d.v,0)||1;\n  const cx=75,cy=75,r=58,inn=32;\n  let paths='',angle=-Math.PI/2;\n  data.forEach(d=>{\n    const a=(d.v/total)*2*Math.PI,end=angle+a;\n    const x1=cx+r*Math.cos(angle),y1=cy+r*Math.sin(angle);\n    const x2=cx+r*Math.cos(end),  y2=cy+r*Math.sin(end);\n    const i1=cx+inn*Math.cos(angle),j1=cy+inn*Math.sin(angle);\n    const i2=cx+inn*Math.cos(end),  j2=cy+inn*Math.sin(end);\n    const lg=a>Math.PI?1:0;\n    paths+=`<path d=\"M${i1},${j1}L${x1},${y1}A${r},${r} 0 ${lg},1 ${x2},${y2}L${i2},${j2}A${inn},${inn} 0 ${lg},0 ${i1},${j1}Z\" fill=\"${d.c}\"/>`;\n    angle=end;\n  });\n  const legend=data.map((d,i)=>`\n    <rect x=\"162\" y=\"${14+i*26}\" width=\"10\" height=\"10\" fill=\"${d.c}\" rx=\"2\"/>\n    <text x=\"178\" y=\"${24+i*26}\" font-size=\"11\" fill=\"#374151\" font-family=\"Arial\">${d.l}</text>\n    <text x=\"178\" y=\"${36+i*26}\" font-size=\"10\" fill=\"#6B7280\" font-family=\"Arial\">$${d.v.toFixed(0)} \u00b7 ${((d.v/total)*100).toFixed(1)}%</text>`).join('');\n  return `<svg width=\"310\" height=\"152\" xmlns=\"http://www.w3.org/2000/svg\">\n    ${paths}\n    <text x=\"${cx}\" y=\"${cy+5}\" text-anchor=\"middle\" font-size=\"13\" font-weight=\"bold\" fill=\"#0F172A\" font-family=\"Arial\">$${total.toFixed(0)}</text>\n    ${legend}</svg>`;\n}\n\n// \u2500\u2500 SVG: Conversions bar \u2500\u2500\nfunction convBars() {\n  const data=[{l:'Meta',v:tMeta.conversions||0},{l:'Microsoft',v:tM.conversions||0},{l:'Google',v:tG.conversions||0}];\n  const mx=Math.max(...data.map(d=>d.v),1);\n  return `<svg width=\"270\" height=\"100\" xmlns=\"http://www.w3.org/2000/svg\">\n    ${data.map((d,i)=>{\n      const w=Math.round((d.v/mx)*160);\n      return `<text x=\"0\" y=\"${20+i*32}\" font-size=\"11\" fill=\"#374151\" font-family=\"Arial\">${d.l}</text>\n        <rect x=\"75\" y=\"${6+i*32}\" width=\"${Math.max(w,3)}\" height=\"16\" fill=\"#334155\" rx=\"2\"/>\n        <text x=\"${75+Math.max(w,3)+6}\" y=\"${20+i*32}\" font-size=\"11\" fill=\"#374151\" font-family=\"Arial\">${d.v}</text>`;\n    }).join('')}</svg>`;\n}\n\n// \u2500\u2500 Table styles \u2500\u2500\nconst TH  = 'padding:9px 12px;font-size:11px;font-weight:600;color:#fff;text-align:left;background:#1E293B;';\nconst THR = 'padding:9px 12px;font-size:11px;font-weight:600;color:#fff;text-align:right;background:#1E293B;';\nconst TD  = 'padding:8px 12px;font-size:11px;color:#374151;border-bottom:1px solid #F1F5F9;';\nconst TDR = 'padding:8px 12px;font-size:11px;color:#374151;border-bottom:1px solid #F1F5F9;text-align:right;';\nconst TDF = 'padding:8px 12px;font-size:11px;font-weight:700;color:#0F172A;border-top:2px solid #E2E8F0;';\nconst TDFR= 'padding:8px 12px;font-size:11px;font-weight:700;color:#0F172A;border-top:2px solid #E2E8F0;text-align:right;';\n\nfunction dot(s){\n  const ok=s==='active'||s==='Enabled';\n  return `<span style=\"display:inline-block;width:6px;height:6px;border-radius:50%;background:${ok?'#22C55E':'#CBD5E1'};margin-right:6px;vertical-align:middle;\"></span>`;\n}\nfunction badge(txt,color){return `<span style=\"font-size:9px;background:${color};color:#fff;padding:1px 6px;border-radius:3px;margin-left:4px;vertical-align:middle;\">${txt}</span>`;}\nfunction trim(s,n=42){return s.length>n?s.substring(0,n)+'\u2026':s;}\n\nfunction metaRows(){\n  return meta.map((r,i)=>{\n    const bg=i%2===0?'#fff':'#F8FAFC';\n    const cpa=r.conversions>0?usd((r.spend/r.conversions).toFixed(2)):'\u2014';\n    const roas=r.roas>0?r.roas.toFixed(2)+'x':'\u2014';\n    return `<tr style=\"background:${bg}\">\n      <td style=\"${TD}\">${dot(r.status)}${trim(r.campaign)}</td>\n      <td style=\"${TDR}\">${usd(r.spend)}</td>\n      <td style=\"${TDR}\">${fmt(r.impressions)}</td>\n      <td style=\"${TDR}\">${r.cpm>0?usd(r.cpm.toFixed(2)):'\u2014'}</td>\n      <td style=\"${TDR}\">${r.conversions||'\u2014'}</td>\n      <td style=\"${TDR}\">${cpa}</td>\n      <td style=\"${TDR}\">${roas}</td></tr>`;\n  }).join('')+`<tr>\n    <td style=\"${TDF}\">Total</td>\n    <td style=\"${TDFR}\">${usd(tMeta.spend)}</td>\n    <td style=\"${TDFR}\">${fmt(tMeta.impressions)}</td>\n    <td style=\"${TDFR}\">\u2014</td>\n    <td style=\"${TDFR}\">${tMeta.conversions}</td>\n    <td style=\"${TDFR}\">${tMeta.cpa>0?usd(tMeta.cpa.toFixed(2)):'\u2014'}</td>\n    <td style=\"${TDFR}\">\u2014</td></tr>`;\n}\n\nfunction msRows(){\n  return microsoft.map((r,i)=>{\n    const bg=i%2===0?'#fff':'#F8FAFC';\n    const paused=String(r.status).toLowerCase().includes('budget');\n    const cpa=r.conversions>0?usd((r.spend/r.conversions).toFixed(2)):'\u2014';\n    return `<tr style=\"background:${bg}\">\n      <td style=\"${TD}\">${dot(r.status)}${trim(r.campaign)}${paused?badge('BUDGET PAUSED','#94A3B8'):''}</td>\n      <td style=\"${TDR}\">${usd(r.spend)}</td>\n      <td style=\"${TDR}\">${fmt(r.impressions)}</td>\n      <td style=\"${TDR}\">${r.clicks||'\u2014'}</td>\n      <td style=\"${TDR}\">${r.ctr||'\u2014'}</td>\n      <td style=\"${TDR}\">${r.conversions||'\u2014'}</td>\n      <td style=\"${TDR}\">${cpa}</td></tr>`;\n  }).join('')+`<tr>\n    <td style=\"${TDF}\">Total</td>\n    <td style=\"${TDFR}\">${usd(tM.spend)}</td>\n    <td style=\"${TDFR}\">${fmt(tM.impressions)}</td>\n    <td style=\"${TDFR}\">${fmt(tM.clicks)}</td>\n    <td style=\"${TDFR}\">\u2014</td>\n    <td style=\"${TDFR}\">${tM.conversions}</td>\n    <td style=\"${TDFR}\">${tM.cpa>0?usd(tM.cpa.toFixed(2)):'\u2014'}</td></tr>`;\n}\n\nfunction gRows(){\n  return google.map((r,i)=>{\n    const bg=i%2===0?'#fff':'#F8FAFC';\n    return `<tr style=\"background:${bg}\">\n      <td style=\"${TD}\">${dot(r.status)}${trim(r.campaign)}</td>\n      <td style=\"${TDR}\">${usd(r.spend)}</td>\n      <td style=\"${TDR}\">${fmt(r.impressions)}</td>\n      <td style=\"${TDR}\">${r.clicks||'\u2014'}</td>\n      <td style=\"${TDR}\">${r.conversions||'\u2014'}</td>\n      <td style=\"${TDR}\">${r.type||'\u2014'}</td></tr>`;\n  }).join('');\n}\n\n// \u2500\u2500 Format AI insights \u2500\u2500\nfunction formatAI(text) {\n  if(!text||text.length<20) return '<p style=\"color:#94A3B8;font-size:12px;\">AI insights not available for this run.</p>';\n  return text\n    .replace(/### (.*)/g,'<h3 style=\"font-size:13px;font-weight:700;color:#0F172A;margin:20px 0 8px;padding-bottom:6px;border-bottom:1px solid #E2E8F0;\">$1</h3>')\n    .replace(/\\*\\*(.*?)\\*\\*/g,'<strong>$1</strong>')\n    .replace(/^- (.*)/gm,'<div style=\"display:flex;gap:8px;margin-bottom:6px;\"><span style=\"color:#64748B;margin-top:1px;\">\u2022</span><span style=\"font-size:12px;color:#374151;line-height:1.6;\">$1</span></div>')\n    .replace(/\\n\\n/g,'<br/>')\n    .replace(/\\n/g,'');\n}\n\n// \u2500\u2500 Section header \u2500\u2500\nfunction secH(title, sub=''){\n  return `<h2 style=\"font-size:13px;font-weight:700;color:#0F172A;margin:0 0 ${sub?'4':'14'}px;padding-bottom:8px;border-bottom:2px solid #E2E8F0;letter-spacing:0.3px;\">${title}</h2>${sub?`<p style=\"font-size:11px;color:#94A3B8;margin:0 0 12px;\">${sub}</p>`:''}`;\n}\n\nconst now = new Date().toLocaleDateString('en-IN',{month:'long',year:'numeric'});\n\nconst html = `<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"UTF-8\"></head>\n<body style=\"margin:0;padding:0;background:#F8FAFC;font-family:Arial,sans-serif;\">\n<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#F8FAFC;padding:20px 0;\">\n<tr><td align=\"center\">\n<table width=\"660\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#fff;border-radius:8px;overflow:hidden;border:1px solid #E2E8F0;\">\n\n<!-- HEADER -->\n<tr><td style=\"background:#0F172A;padding:24px 32px;\">\n  <table width=\"100%\"><tr valign=\"middle\">\n    <td>\n      <div style=\"font-size:10px;color:#64748B;letter-spacing:2px;text-transform:uppercase;margin-bottom:4px;\">Monthly Performance Report \u00b7 February 2026</div>\n      <h1 style=\"color:#F8FAFC;margin:0;font-size:20px;font-weight:700;\">Campaign Intelligence Report</h1>\n      <p style=\"color:#64748B;margin:5px 0 0;font-size:12px;\">Meta \u00b7 Microsoft \u00b7 Google &nbsp;|&nbsp; Powered by Groq AI (Llama 3.3 70B)</p>\n    </td>\n    <td align=\"right\">\n      <div style=\"text-align:right;\">\n        <div style=\"font-size:22px;font-weight:700;color:#F8FAFC;\">${usd(tAll.spend)}</div>\n        <div style=\"font-size:10px;color:#64748B;\">Total Spend</div>\n      </div>\n    </td>\n  </tr></table>\n</td></tr>\n\n<!-- KPI STRIP -->\n<tr><td style=\"background:#1E293B;padding:0;\">\n  <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"><tr>\n    ${[\n      [fmt(tAll.impressions),'Impressions'],\n      [fmt(tAll.clicks),'Clicks'],\n      [String(tAll.conversions),'Conversions'],\n      [tAll.cpa>0?usd(tAll.cpa.toFixed(2)):'\u2014','Avg. CPA'],\n      [String(tAll.purchases),'Purchases'],\n    ].map(([v,l],i)=>`<td style=\"padding:16px 0;text-align:center;${i<4?'border-right:1px solid #334155;':''}\">\n      <div style=\"font-size:17px;font-weight:700;color:#F8FAFC;\">${v}</div>\n      <div style=\"font-size:10px;color:#64748B;margin-top:3px;text-transform:uppercase;letter-spacing:0.5px;\">${l}</div>\n    </td>`).join('')}\n  </tr></table>\n</td></tr>\n\n<!-- CHARTS -->\n<tr><td style=\"padding:24px 32px 8px;\">\n  ${secH('Performance Breakdown')}\n  <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"><tr valign=\"top\">\n    <td width=\"49%\" style=\"background:#F8FAFC;border-radius:6px;padding:16px;border:1px solid #E2E8F0;text-align:center;\">\n      <div style=\"font-size:10px;font-weight:600;color:#64748B;text-transform:uppercase;letter-spacing:1px;margin-bottom:10px;\">Spend Distribution</div>\n      ${donut()}\n    </td>\n    <td width=\"2%\"></td>\n    <td width=\"49%\" style=\"background:#F8FAFC;border-radius:6px;padding:16px;border:1px solid #E2E8F0;\">\n      <div style=\"font-size:10px;font-weight:600;color:#64748B;text-transform:uppercase;letter-spacing:1px;margin-bottom:10px;text-align:center;\">Conversions by Platform</div>\n      ${convBars()}\n    </td>\n  </tr></table>\n</td></tr>\n\n<!-- PLATFORM SUMMARY -->\n<tr><td style=\"padding:16px 32px 8px;\">\n  ${secH('Platform Summary')}\n  <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"border:1px solid #E2E8F0;border-radius:6px;overflow:hidden;\">\n    <tr><th style=\"${TH}\">Platform</th><th style=\"${THR}\">Spend</th><th style=\"${THR}\">Share</th><th style=\"${THR}\">Impressions</th><th style=\"${THR}\">Clicks</th><th style=\"${THR}\">CPM</th><th style=\"${THR}\">Conv.</th><th style=\"${THR}\">CPA</th></tr>\n    <tr style=\"background:#fff\"><td style=\"${TD}\">Meta</td><td style=\"${TDR}\">${usd(tMeta.spend)}</td><td style=\"${TDR}\">${metaPct}%</td><td style=\"${TDR}\">${fmt(tMeta.impressions)}</td><td style=\"${TDR}\">\u2014</td><td style=\"${TDR}\">${tMeta.cpm>0?usd(tMeta.cpm.toFixed(2)):'\u2014'}</td><td style=\"${TDR}\">${tMeta.conversions}</td><td style=\"${TDR}\">${tMeta.cpa>0?usd(tMeta.cpa.toFixed(2)):'\u2014'}</td></tr>\n    <tr style=\"background:#F8FAFC\"><td style=\"${TD}\">Microsoft</td><td style=\"${TDR}\">${usd(tM.spend)}</td><td style=\"${TDR}\">${msPct}%</td><td style=\"${TDR}\">${fmt(tM.impressions)}</td><td style=\"${TDR}\">${fmt(tM.clicks)}</td><td style=\"${TDR}\">${tM.cpm>0?usd(tM.cpm.toFixed(2)):'\u2014'}</td><td style=\"${TDR}\">${tM.conversions}</td><td style=\"${TDR}\">${tM.cpa>0?usd(tM.cpa.toFixed(2)):'\u2014'}</td></tr>\n    <tr style=\"background:#fff\"><td style=\"${TD}\">Google</td><td style=\"${TDR}\">${usd(tG.spend)}</td><td style=\"${TDR}\">${gPct}%</td><td style=\"${TDR}\">${fmt(tG.impressions)}</td><td style=\"${TDR}\">${fmt(tG.clicks)}</td><td style=\"${TDR}\">\u2014</td><td style=\"${TDR}\">${tG.conversions}</td><td style=\"${TDR}\">\u2014</td></tr>\n    <tr><td style=\"${TDF}\">Total</td><td style=\"${TDFR}\">${usd(tAll.spend)}</td><td style=\"${TDFR}\">100%</td><td style=\"${TDFR}\">${fmt(tAll.impressions)}</td><td style=\"${TDFR}\">${fmt(tAll.clicks)}</td><td style=\"${TDFR}\">\u2014</td><td style=\"${TDFR}\">${tAll.conversions}</td><td style=\"${TDFR}\">${tAll.cpa>0?usd(tAll.cpa.toFixed(2)):'\u2014'}</td></tr>\n  </table>\n</td></tr>\n\n<!-- META TABLE -->\n<tr><td style=\"padding:16px 32px 8px;\">\n  ${secH('Meta Campaigns', `${meta.length} campaigns \u00b7 February 2026`)}\n  <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"border:1px solid #E2E8F0;border-radius:6px;overflow:hidden;\">\n    <tr><th style=\"${TH}\">Campaign</th><th style=\"${THR}\">Spend</th><th style=\"${THR}\">Impr.</th><th style=\"${THR}\">CPM</th><th style=\"${THR}\">Conv.</th><th style=\"${THR}\">CPA</th><th style=\"${THR}\">ROAS</th></tr>\n    ${metaRows()}\n  </table>\n</td></tr>\n\n<!-- MICROSOFT TABLE -->\n<tr><td style=\"padding:16px 32px 8px;\">\n  ${secH('Microsoft Campaigns', `${microsoft.length} campaigns \u00b7 ${budgetPaused.length} budget-paused`)}\n  <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"border:1px solid #E2E8F0;border-radius:6px;overflow:hidden;\">\n    <tr><th style=\"${TH}\">Campaign</th><th style=\"${THR}\">Spend</th><th style=\"${THR}\">Impr.</th><th style=\"${THR}\">Clicks</th><th style=\"${THR}\">CTR</th><th style=\"${THR}\">Conv.</th><th style=\"${THR}\">CPA</th></tr>\n    ${msRows()}\n  </table>\n</td></tr>\n\n<!-- GOOGLE TABLE -->\n<tr><td style=\"padding:16px 32px 8px;\">\n  ${secH('Google Campaigns', 'All campaigns returned 0 conversions \u2014 verify pixel setup')}\n  <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"border:1px solid #E2E8F0;border-radius:6px;overflow:hidden;\">\n    <tr><th style=\"${TH}\">Campaign</th><th style=\"${THR}\">Spend</th><th style=\"${THR}\">Impr.</th><th style=\"${THR}\">Clicks</th><th style=\"${THR}\">Conv.</th><th style=\"${THR}\">Type</th></tr>\n    ${gRows()}\n  </table>\n</td></tr>\n\n<!-- AI ANALYSIS -->\n<tr><td style=\"padding:16px 32px 24px;\">\n  ${secH('AI Analysis \u2014 Powered by Groq (Llama 3.3 70B)', 'Generated in real-time from your campaign data')}\n  <div style=\"background:#F8FAFC;border:1px solid #E2E8F0;border-radius:6px;padding:20px 24px;\">\n    ${formatAI(aiRaw)}\n  </div>\n</td></tr>\n\n<!-- FOOTER -->\n<tr><td style=\"background:#0F172A;padding:14px 32px;text-align:center;\">\n  <p style=\"font-size:11px;color:#475569;margin:0;\">Generated automatically via n8n \u00b7 Groq AI (Llama 3.3 70B) \u00b7 ${now}</p>\n</td></tr>\n\n</table></td></tr></table>\n</body></html>`;\n\nconst subject = `Campaign Report \u2014 Feb 2026 | ${usd(tAll.spend)} Spend | ${tAll.conversions} Conv. | Meta ${metaPct}% / MS ${msPct}% / G ${gPct}%`;\nreturn [{ json: { html, subject } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "f8c0a857-1997-44df-97a5-68af0ed05330",
      "name": "Merge - KPI + AI",
      "type": "n8n-nodes-base.merge",
      "position": [
        1152,
        0
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.2
    },
    {
      "id": "35e4e54c-f41d-4ad2-94d8-3970ead1577b",
      "name": "Groq - Generate Insights",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        912,
        -128
      ],
      "parameters": {
        "url": "https://api.groq.com/openai/v1/chat/completions",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ { \"model\": \"llama-3.3-70b-versatile\", \"messages\": [{ \"role\": \"user\", \"content\": $json.groqPrompt }] } }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "793a889a-8fdd-4367-beff-86b2f28b03c9",
      "name": "Trigger - Monthly Schedule",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -224,
        16
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "months",
              "triggerAtHour": 9
            },
            {}
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "2e1deefb-4555-416e-8c48-b792b8d31d2f",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -880,
        -272
      ],
      "parameters": {
        "width": 464,
        "height": 544,
        "content": "## How it works\nThis workflow automatically generates a detailed AI-powered \ncampaign performance report across Meta, Google and Microsoft \nAds and emails it to your team every month.\n\nIt reads campaign data from three tabs in a Google Sheet \n(Google, Meta, Microsoft), merges all rows, and passes them \nto a Code node that calculates KPIs and builds a structured \nprompt. Groq AI (Llama 3.3 70B) then analyzes the data and \ngenerates expert insights. A second Code node combines the \nKPIs and AI analysis into a full HTML email with platform \ntables, charts, benchmarks and recommendations \u2014 sent \nautomatically via Gmail.\n\n## Setup steps\n1. Create a Google Sheet with 3 tabs: Google, Meta, Microsoft\n2. Paste your monthly ad exports into the matching tab\n3. Connect your Google account in the 3 Sheets nodes and \n   select the correct tab in each\n4. Add your Groq API key in the HTTP Request node header\n5. Connect your Gmail account in the Send node and set your \n   recipient email\n6. Activate \u2014 the workflow runs automatically on schedule"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "5968e428-ca39-4618-99b9-54a5a18b8aa2",
  "connections": {
    "Read - Meta ads": {
      "main": [
        [
          {
            "node": "Merge all platform",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge - KPI + AI": {
      "main": [
        [
          {
            "node": "Build - HTML Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read - Google ads": {
      "main": [
        [
          {
            "node": "Merge all platform",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge all platform": {
      "main": [
        [
          {
            "node": "Calculate - KPIs & Build Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build - HTML Report": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read - Microsoft ads": {
      "main": [
        [
          {
            "node": "Merge all platform",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Groq - Generate Insights": {
      "main": [
        [
          {
            "node": "Merge - KPI + AI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Trigger - Monthly Schedule": {
      "main": [
        [
          {
            "node": "Read - Google ads",
            "type": "main",
            "index": 0
          },
          {
            "node": "Read - Meta ads",
            "type": "main",
            "index": 0
          },
          {
            "node": "Read - Microsoft ads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate - KPIs & Build Prompt": {
      "main": [
        [
          {
            "node": "Groq - Generate Insights",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge - KPI + AI",
            "type": "main",
            "index": 1
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

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

About this workflow

This workflow automatically generates a detailed AI-powered campaign performance report across Meta, Google and Microsoft Ads and emails it to your team every month.

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

More Email & Gmail workflows → · Browse all categories →

Related workflows

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

Email & Gmail

YOUR_ID 4. Uses gmail, googleDrive, googleSheets, httpRequest. Scheduled trigger; 53 nodes.

Gmail, Google Drive, Google Sheets +1
Email & Gmail

Looking for a way to track GitHub bounty issues automatically and get notified in real time? This GitHub Bounty Tracker workflow monitors repositories for issues labeled 💎 Bounty, logs them in Google

Google Sheets, HTTP Request, WhatsApp +1
Email & Gmail

This workflow automatically sends a beautifully designed HTML newsletter every Sunday at 8 AM, featuring products currently on sale from your Algolia-powered e-commerce store.

Google Sheets, HTTP Request, Gmail
Email & Gmail

This n8n template demonstrates how to build a Auto Lead Gen & Outreach System for Local Businesses specifically designed to help businesses that don’t have a website yet.

Google Sheets, HTTP Request, Google Drive +1
Email & Gmail

I created this workflow with care for marketing professionals and agencies who manage multiple Meta Ads (Facebook) accounts and want to track ad account balances automatically — no more logging in eve

HTTP Request, Google Sheets, Gmail