{
  "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
          }
        ]
      ]
    }
  }
}