{
  "id": "FpauF9jfmoRaFe8q",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Automated Client Ad Reporting",
  "tags": [],
  "nodes": [
    {
      "id": "67e27c4e-8a15-4951-9e58-491375e963f7",
      "name": "Main Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2720,
        1248
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 1444,
        "content": "## \ud83d\udcca Automated Client Ad Reporting\n\nEvery Monday at 8am, this workflow fetches spend and performance metrics from Meta and Google Ads APIs, blends them with GA4 conversion data, generates an AI-powered summary of key insights, and automatically delivers a branded HTML report to the client via email and posts a summary to your internal Slack channel.\n\n**Perfect for:** Agency media buyers, account managers, and client success teams automating weekly performance reports.\n\n---\n\n## How it works\n\n1. **Every Monday 8am** \u2014 Triggers the workflow on a weekly schedule.\n2. **Fetch Meta Ads Insights** \u2014 Queries the Meta Ads API for spend, impressions, clicks, and actions from the past 7 days.\n3. **Fetch Google Ads Report** \u2014 Queries the Google Ads API for campaign names, cost, clicks, and conversions from the past 7 days.\n4. **Fetch GA4 Conversions** \u2014 Queries the GA4 Reporting API for conversion counts and revenue by channel from the past 7 days.\n5. **Blend Data Sources** \u2014 Combines the three data streams into a single merged object.\n6. **Compute Blended Metrics** \u2014 Calculates total spend, total conversions, total revenue, and blended ROAS across all platforms.\n7. **Summarize What Moved (AI Agent)** \u2014 Uses an AI model to analyze the metrics and generate a plain-language summary of key insights.\n8. **OpenAI Chat Model** \u2014 Language model that powers the AI analysis in the previous step.\n9. **Build Report** \u2014 Assembles the computed metrics and AI summary into an HTML report template.\n10. **Email Report to Client** \u2014 Sends the formatted HTML report to the client's email address.\n11. **Post to Agency Channel** \u2014 Posts a brief performance summary to your internal Slack channel.\n12. **Error Trigger** \u2014 *(optional path)* Detects if the main workflow fails at any point.\n13. **Notify Ops** \u2014 *(optional path)* Alerts your ops team in Slack if an error occurs.\n\n---\n\n## Setup (~20 minutes)\n\n1. **Meta Ads API** \u2014 Add your API token in the *Fetch Meta Ads Insights* node. Replace `YOUR_AD_ACCOUNT` with your actual account ID.\n2. **Google Ads API** \u2014 Add your OAuth credentials in the *Fetch Google Ads Report* node. Replace `YOUR_CUSTOMER_ID` with your account ID.\n3. **GA4 API** \u2014 Add your OAuth credentials in the *Fetch GA4 Conversions* node. Replace `YOUR_GA4_PROPERTY_ID` with your property ID.\n4. **OpenAI API** \u2014 Add your API key in the *OpenAI Chat Model* node for the AI summary analysis.\n5. **Gmail** \u2014 Authenticate in the *Email Report to Client* node and set the recipient email address.\n6. **Slack** \u2014 Authenticate in both *Post to Agency Channel* and *Notify Ops* nodes, then set your channel IDs.\n\n> This workflow sends live API calls every Monday. If you need custom date ranges or additional metrics, edit the query parameters in the fetch nodes before activating."
      },
      "typeVersion": 1
    },
    {
      "id": "7b20be6f-be59-49bc-9472-2950e517b264",
      "name": "1\ufe0f\u20e3 Trigger & Data Intake",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2128,
        1136
      ],
      "parameters": {
        "color": 5,
        "width": 696,
        "height": 688,
        "content": "## 1\ufe0f\u20e3 Trigger & Data Intake\n\nThe **Every Monday 8am** trigger fires the workflow every Monday at 8am UTC. Three HTTP requests execute in parallel: **Fetch Meta Ads Insights** pulls spend and engagement metrics from your Meta ad account, **Fetch Google Ads Report** retrieves campaign performance and costs from Google Ads, and **Fetch GA4 Conversions** queries your analytics property for conversion counts and revenue data by channel."
      },
      "typeVersion": 1
    },
    {
      "id": "4bde520c-ef68-4d8f-8e68-4d7ab8d15f7f",
      "name": "2\ufe0f\u20e3 Data Processing & AI Analysis",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1392,
        1200
      ],
      "parameters": {
        "color": 3,
        "width": 744,
        "height": 636,
        "content": "## 2\ufe0f\u20e3 Data Processing & AI Analysis\n\nThe **Blend Data Sources** node merges the three data streams into one unified object. The **Compute Blended Metrics** code node then calculates critical KPIs: total spend across both platforms, combined conversion count, total revenue, and blended ROAS. The **Summarize What Moved (AI Agent)** node passes all computed metrics to the **OpenAI Chat Model**, which generates a concise, plain-language analysis of performance trends, standout metrics, risks, and one actionable recommendation suitable for client consumption."
      },
      "typeVersion": 1
    },
    {
      "id": "b6c7aede-aae8-4170-845c-5d6da5d7f524",
      "name": "3\ufe0f\u20e3 Report Assembly & Delivery",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -608,
        1200
      ],
      "parameters": {
        "color": 6,
        "width": 664,
        "height": 608,
        "content": "## 3\ufe0f\u20e3 Report Assembly & Delivery\n\nThe **Build Report** node takes the computed metrics and AI summary and renders them into a formatted HTML report with client-friendly styling and the week's key numbers prominently displayed. The **Email Report to Client** node then sends this HTML report directly to your client's inbox with a timestamped subject line. Simultaneously, the **Post to Agency Channel** node posts a quick Slack summary to your internal team channel so the account manager and agency leadership stay in the loop."
      },
      "typeVersion": 1
    },
    {
      "id": "d8af1fc7-2ced-4f7e-805b-eef3b8494352",
      "name": "4\ufe0f\u20e3 Error Handling",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2032,
        1920
      ],
      "parameters": {
        "color": 4,
        "width": 712,
        "height": 388,
        "content": "## 4\ufe0f\u20e3 Error Handling\n\nIf the main workflow fails at any step, the **Error Trigger** catches the failure and passes it to the **Notify Ops** node, which immediately posts an alert to your ops Slack channel with the error message so your team can investigate before the client notices a missing report."
      },
      "typeVersion": 1
    },
    {
      "id": "fb467fbb-5e04-4f39-8678-7c7e33935f53",
      "name": "Every Monday 8am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -2080,
        1520
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 8
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "315398ac-6023-4363-8cdf-14c4209402c9",
      "name": "Fetch Meta Ads Insights",
      "type": "n8n-nodes-base.httpRequest",
      "maxTries": 3,
      "position": [
        -1840,
        1376
      ],
      "parameters": {
        "url": "https://graph.facebook.com/v20.0/act_YOUR_AD_ACCOUNT/insights",
        "options": {
          "timeout": 15000
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "fields",
              "value": "spend,impressions,clicks,actions"
            },
            {
              "name": "date_preset",
              "value": "last_7d"
            }
          ]
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.2
    },
    {
      "id": "4526033b-4ca6-4daa-bb73-b96f903a54f7",
      "name": "Fetch Google Ads Report",
      "type": "n8n-nodes-base.httpRequest",
      "maxTries": 3,
      "position": [
        -1840,
        1520
      ],
      "parameters": {
        "url": "https://googleads.googleapis.com/v17/customers/YOUR_CUSTOMER_ID/googleAds:searchStream",
        "method": "POST",
        "options": {
          "timeout": 15000
        },
        "jsonBody": "={{ { query: \"SELECT campaign.name, metrics.cost_micros, metrics.clicks, metrics.conversions FROM campaign WHERE segments.date DURING LAST_7_DAYS\" } }}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "retryOnFail": true,
      "typeVersion": 4.2
    },
    {
      "id": "1990fbf0-691a-4f2f-98c5-48df2c0f71e4",
      "name": "Fetch GA4 Conversions",
      "type": "n8n-nodes-base.httpRequest",
      "maxTries": 3,
      "position": [
        -1840,
        1664
      ],
      "parameters": {
        "url": "https://analyticsdata.googleapis.com/v1beta/properties/YOUR_GA4_PROPERTY_ID:runReport",
        "method": "POST",
        "options": {
          "timeout": 15000
        },
        "jsonBody": "={{ { dateRanges: [{ startDate: \"7daysAgo\", endDate: \"today\" }], dimensions: [{ name: \"sessionDefaultChannelGroup\" }], metrics: [{ name: \"conversions\" }, { name: \"totalRevenue\" }] } }}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "retryOnFail": true,
      "typeVersion": 4.2
    },
    {
      "id": "50369356-08b5-4820-acda-eb1f79dd1907",
      "name": "Blend Data Sources",
      "type": "n8n-nodes-base.merge",
      "position": [
        -1600,
        1520
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineAll"
      },
      "typeVersion": 3.2
    },
    {
      "id": "ea5777be-0f88-4398-9548-c07ed1440d23",
      "name": "Compute Blended Metrics",
      "type": "n8n-nodes-base.code",
      "position": [
        -1360,
        1520
      ],
      "parameters": {
        "jsCode": "const [meta, google, ga4] = [$('Fetch Meta Ads Insights').all(), $('Fetch Google Ads Report').all(), $('Fetch GA4 Conversions').all()];\nconst metaSpend = (meta[0]?.json?.data || []).reduce((s, r) => s + Number(r.spend || 0), 0);\nconst googleSpend = (google[0]?.json?.results || []).reduce((s, r) => s + Number(r.metrics?.costMicros || 0) / 1e6, 0);\nconst totalSpend = metaSpend + googleSpend;\nconst conversions = (ga4[0]?.json?.rows || []).reduce((s, r) => s + Number(r.metricValues?.[0]?.value || 0), 0);\nconst revenue = (ga4[0]?.json?.rows || []).reduce((s, r) => s + Number(r.metricValues?.[1]?.value || 0), 0);\nconst roas = totalSpend > 0 ? (revenue / totalSpend) : 0;\nreturn [{ json: { metaSpend, googleSpend, totalSpend, conversions, revenue, roas: Number(roas.toFixed(2)) } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "978e3757-f006-46ff-882d-bf4fec2fe5f8",
      "name": "Summarize What Moved (AI Agent)",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -1120,
        1520
      ],
      "parameters": {
        "text": "=Weekly ad performance data: {{ JSON.stringify($json) }}. Write a 3-4 sentence plain-language summary of what moved and why, suitable for a client email.",
        "options": {
          "systemMessage": "You are a paid media analyst writing a concise weekly client summary. Highlight the standout metric, one risk, and one recommendation. No jargon."
        },
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "dc670394-6ed5-4f39-83d6-d9d71b9b6685",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -1120,
        1696
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5-mini"
        },
        "options": {
          "temperature": 0.5
        },
        "builtInTools": {}
      },
      "typeVersion": 1.3
    },
    {
      "id": "1422f7ca-7e38-469d-b9c8-d3c2016c1557",
      "name": "Build Report",
      "type": "n8n-nodes-base.set",
      "position": [
        -880,
        1520
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "rep1",
              "name": "reportHtml",
              "type": "string",
              "value": "=<h2>Weekly Ad Performance Report</h2><p><b>Total Spend:</b> ${{ $('Compute Blended Metrics').item.json.totalSpend.toFixed(2) }}</p><p><b>Conversions:</b> {{ $('Compute Blended Metrics').item.json.conversions }}</p><p><b>ROAS:</b> {{ $('Compute Blended Metrics').item.json.roas }}x</p><p>{{ $json.output }}</p>"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "45dce2e3-efef-4339-aef7-82397c45c799",
      "name": "Email Report to Client",
      "type": "n8n-nodes-base.gmail",
      "maxTries": 2,
      "position": [
        -384,
        1456
      ],
      "parameters": {
        "sendTo": "client@REPLACE_WITH_CLIENT_DOMAIN.com",
        "message": "={{ $json.reportHtml }}",
        "options": {
          "appendAttribution": false
        },
        "subject": "=Your Weekly Ad Performance Report \u2014 {{ $now.toFormat('dd LLL yyyy') }}"
      },
      "retryOnFail": true,
      "typeVersion": 2.1
    },
    {
      "id": "ef34e4b4-0c57-463c-8099-0244661e5a7e",
      "name": "Post to Agency Channel",
      "type": "n8n-nodes-base.slack",
      "position": [
        -384,
        1648
      ],
      "parameters": {
        "text": "=\ud83d\udcca Weekly report sent \u2014 Spend ${{ $('Compute Blended Metrics').item.json.totalSpend.toFixed(2) }}, ROAS {{ $('Compute Blended Metrics').item.json.roas }}x",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "REPLACE_WITH_CHANNEL_ID",
          "cachedResultName": "client-reports"
        },
        "otherOptions": {}
      },
      "typeVersion": 2.3
    },
    {
      "id": "28085d80-fa2c-48e1-9fa9-f607b85e426c",
      "name": "Error Trigger",
      "type": "n8n-nodes-base.errorTrigger",
      "position": [
        -1936,
        2096
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "829752d6-94f6-407c-98ea-bd9be2b40060",
      "name": "Notify Ops",
      "type": "n8n-nodes-base.slack",
      "position": [
        -1696,
        2096
      ],
      "parameters": {
        "text": "=\ud83d\udea8 Automated Client Ad Reporting failed: {{ $json.execution.error.message }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "REPLACE_WITH_CHANNEL_ID",
          "cachedResultName": "ops-alerts"
        },
        "otherOptions": {}
      },
      "typeVersion": 2.3
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "213ec136-d0b3-4b72-a2f1-c351116f6d16",
  "nodeGroups": [],
  "connections": {
    "Build Report": {
      "main": [
        [
          {
            "node": "Email Report to Client",
            "type": "main",
            "index": 0
          },
          {
            "node": "Post to Agency Channel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Error Trigger": {
      "main": [
        [
          {
            "node": "Notify Ops",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every Monday 8am": {
      "main": [
        [
          {
            "node": "Fetch Meta Ads Insights",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Google Ads Report",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch GA4 Conversions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Summarize What Moved (AI Agent)",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Blend Data Sources": {
      "main": [
        [
          {
            "node": "Compute Blended Metrics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compute Blended Metrics": {
      "main": [
        [
          {
            "node": "Summarize What Moved (AI Agent)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Google Ads Report": {
      "main": [
        [
          {
            "node": "Blend Data Sources",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Fetch Meta Ads Insights": {
      "main": [
        [
          {
            "node": "Blend Data Sources",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Summarize What Moved (AI Agent)": {
      "main": [
        [
          {
            "node": "Build Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}