{
  "name": "Weekly Marketing Report Automation",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 7,
              "triggerAtDay": 1
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [0, 300],
      "id": "schedule-trigger",
      "name": "Monday 7am CST"
    },
    {
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "leftValue": "" },
          "conditions": [
            {
              "leftValue": "={{ $now.format('MM-dd') }}",
              "rightValue": "",
              "operator": { "type": "string", "operation": "notEquals" }
            }
          ]
        }
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [200, 300],
      "id": "holiday-check",
      "name": "Skip Holidays",
      "notes": "Add holiday dates to rightValue as comma-separated: 12-25,01-01,etc. Or remove this node if not needed."
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://googleads.googleapis.com/v16/customers/YOUR_CUSTOMER_ID/googleAds:searchStream",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleAdsOAuth2Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "developer-token", "value": "YOUR_DEVELOPER_TOKEN" },
            { "name": "login-customer-id", "value": "YOUR_MCC_ID" }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={ \"query\": \"SELECT campaign.name, metrics.impressions, metrics.clicks, metrics.cost_micros, metrics.conversions, metrics.cost_per_conversion FROM campaign WHERE segments.date DURING LAST_7_DAYS AND campaign.status = 'ENABLED' ORDER BY metrics.cost_micros DESC\" }",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [440, 160],
      "id": "google-ads-pull",
      "name": "Google Ads - Campaign Metrics",
      "credentials": {
        "googleAdsOAuth2Api": {
          "id": "YOUR_CREDENTIAL_ID",
          "name": "Google Ads"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v3/objects/contacts/search",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "hubspotOAuth2Api",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={ \"filterGroups\": [{ \"filters\": [{ \"propertyName\": \"createdate\", \"operator\": \"GTE\", \"value\": \"{{ $now.minus(7, 'days').toMillis() }}\" }] }], \"properties\": [\"firstname\", \"lastname\", \"email\", \"hs_analytics_source\", \"hubspot_owner_id\", \"lifecyclestage\"], \"limit\": 100 }",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [440, 300],
      "id": "hubspot-contacts",
      "name": "HubSpot - New Contacts (7d)",
      "credentials": {
        "hubspotOAuth2Api": {
          "id": "YOUR_CREDENTIAL_ID",
          "name": "HubSpot"
        }
      }
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://graph.facebook.com/v19.0/act_YOUR_AD_ACCOUNT_ID/insights?fields=spend,impressions,clicks,actions,cost_per_action_type&date_preset=last_7d&level=campaign",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "facebookGraphApi",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [440, 440],
      "id": "meta-ads-pull",
      "name": "Meta Ads - Campaign Metrics",
      "credentials": {
        "facebookGraphApi": {
          "id": "YOUR_CREDENTIAL_ID",
          "name": "Meta Ads"
        }
      }
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineAll",
        "options": {}
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [700, 300],
      "id": "merge-data",
      "name": "Merge All Data"
    },
    {
      "parameters": {
        "jsCode": "// Anomaly Detection: Compare this week to 4-week rolling average\nconst items = $input.all();\nconst data = items[0].json;\n\n// Helper: calculate WoW change\nconst wowChange = (current, previous) => {\n  if (!previous || previous === 0) return 'N/A';\n  const change = ((current - previous) / previous * 100).toFixed(1);\n  return change > 0 ? `+${change}%` : `${change}%`;\n};\n\n// Flag anomalies (>15% deviation from rolling average)\nconst flagAnomaly = (current, average) => {\n  if (!average || average === 0) return { flag: false, indicator: '⚪' };\n  const deviation = Math.abs((current - average) / average * 100);\n  if (deviation > 15) {\n    return {\n      flag: true,\n      indicator: current > average ? '🟢 +' + deviation.toFixed(0) + '%' : '🔴 -' + deviation.toFixed(0) + '%'\n    };\n  }\n  return { flag: false, indicator: '⚪' };\n};\n\n// Process Google Ads data\nconst googleAds = data.googleAds || {};\nconst totalSpend = (googleAds.costMicros || 0) / 1000000;\nconst totalClicks = googleAds.clicks || 0;\nconst totalConversions = googleAds.conversions || 0;\nconst cpc = totalClicks > 0 ? (totalSpend / totalClicks).toFixed(2) : 'N/A';\nconst cpa = totalConversions > 0 ? (totalSpend / totalConversions).toFixed(2) : 'N/A';\n\n// Process HubSpot data\nconst newContacts = data.hubspotContacts || 0;\nconst contactsBySource = data.contactsBySource || {};\n\n// Build summary\nreturn [{\n  json: {\n    reportDate: new Date().toISOString().split('T')[0],\n    weekOf: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],\n    google: {\n      spend: totalSpend.toFixed(2),\n      clicks: totalClicks,\n      conversions: totalConversions,\n      cpc, cpa\n    },\n    hubspot: {\n      newContacts,\n      contactsBySource\n    },\n    meta: data.metaAds || {},\n    anomalies: []\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [920, 300],
      "id": "anomaly-detection",
      "name": "Anomaly Detection & Metrics"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "x-api-key", "value": "YOUR_ANTHROPIC_API_KEY" },
            { "name": "anthropic-version", "value": "2023-06-01" }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={ \"model\": \"claude-sonnet-4-20250514\", \"max_tokens\": 500, \"messages\": [{ \"role\": \"user\", \"content\": \"Write a 3-sentence executive summary of this week's marketing performance. Be specific with numbers. Data: {{ JSON.stringify($json) }}\" }] }",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1140, 300],
      "id": "claude-summary",
      "name": "Claude - Executive Summary"
    },
    {
      "parameters": {
        "jsCode": "const data = $('Anomaly Detection & Metrics').first().json;\nconst summary = $input.first().json.content?.[0]?.text || 'Summary unavailable.';\nconst date = new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });\n\nconst html = `\n<!DOCTYPE html>\n<html>\n<head>\n<style>\n  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f5f5f5; padding: 20px; }\n  .container { max-width: 640px; margin: 0 auto; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }\n  .header { background: #1B2838; color: white; padding: 24px 32px; }\n  .header h1 { font-size: 20px; margin: 0; }\n  .header p { color: #9CA3AF; font-size: 13px; margin-top: 4px; }\n  .summary { background: #F0FFF4; padding: 20px 32px; border-left: 4px solid #48BB78; margin: 24px 32px; border-radius: 4px; font-size: 14px; line-height: 1.6; color: #2D3748; }\n  .section { padding: 20px 32px; }\n  .section h2 { font-size: 16px; color: #1B2838; border-bottom: 2px solid #E2E8F0; padding-bottom: 8px; margin-bottom: 16px; }\n  table { width: 100%; border-collapse: collapse; font-size: 14px; }\n  th { text-align: left; padding: 8px 12px; background: #F7FAFC; color: #4A5568; font-weight: 600; border-bottom: 2px solid #E2E8F0; }\n  td { padding: 8px 12px; border-bottom: 1px solid #EDF2F7; color: #2D3748; }\n  .metric-value { font-weight: 700; font-size: 15px; }\n  .footer { padding: 16px 32px; background: #F7FAFC; text-align: center; font-size: 12px; color: #9CA3AF; }\n</style>\n</head>\n<body>\n<div class=\"container\">\n  <div class=\"header\">\n    <h1>Weekly Marketing Report</h1>\n    <p>${date}</p>\n  </div>\n  <div class=\"summary\">${summary}</div>\n  <div class=\"section\">\n    <h2>Google Ads</h2>\n    <table>\n      <tr><th>Metric</th><th>This Week</th><th>Status</th></tr>\n      <tr><td>Spend</td><td class=\"metric-value\">$${data.google.spend}</td><td></td></tr>\n      <tr><td>Clicks</td><td class=\"metric-value\">${data.google.clicks}</td><td></td></tr>\n      <tr><td>Conversions</td><td class=\"metric-value\">${data.google.conversions}</td><td></td></tr>\n      <tr><td>CPC</td><td class=\"metric-value\">$${data.google.cpc}</td><td></td></tr>\n      <tr><td>CPA</td><td class=\"metric-value\">$${data.google.cpa}</td><td></td></tr>\n    </table>\n  </div>\n  <div class=\"section\">\n    <h2>HubSpot Leads</h2>\n    <table>\n      <tr><th>Metric</th><th>This Week</th></tr>\n      <tr><td>New Contacts</td><td class=\"metric-value\">${data.hubspot.newContacts}</td></tr>\n    </table>\n  </div>\n  <div class=\"footer\">Generated by n8n | Weekly Marketing Report</div>\n</div>\n</body>\n</html>`;\n\nreturn [{ json: { html } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [1360, 300],
      "id": "html-template",
      "name": "Render HTML Email"
    },
    {
      "parameters": {
        "fromEmail": "reports@yourdomain.com",
        "toEmail": "team@yourdomain.com",
        "subject": "=Weekly Marketing Report — {{ $now.format('MMM d, yyyy') }}",
        "emailType": "html",
        "html": "={{ $json.html }}",
        "options": {}
      },
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [1580, 300],
      "id": "send-email",
      "name": "Send Report Email",
      "credentials": {
        "smtp": {
          "id": "YOUR_CREDENTIAL_ID",
          "name": "SMTP"
        }
      }
    }
  ],
  "connections": {
    "Monday 7am CST": { "main": [[{ "node": "Skip Holidays", "type": "main", "index": 0 }]] },
    "Skip Holidays": { "main": [[{ "node": "Google Ads - Campaign Metrics", "type": "main", "index": 0 }, { "node": "HubSpot - New Contacts (7d)", "type": "main", "index": 0 }, { "node": "Meta Ads - Campaign Metrics", "type": "main", "index": 0 }]] },
    "Google Ads - Campaign Metrics": { "main": [[{ "node": "Merge All Data", "type": "main", "index": 0 }]] },
    "HubSpot - New Contacts (7d)": { "main": [[{ "node": "Merge All Data", "type": "main", "index": 1 }]] },
    "Meta Ads - Campaign Metrics": { "main": [[{ "node": "Merge All Data", "type": "main", "index": 2 }]] },
    "Merge All Data": { "main": [[{ "node": "Anomaly Detection & Metrics", "type": "main", "index": 0 }]] },
    "Anomaly Detection & Metrics": { "main": [[{ "node": "Claude - Executive Summary", "type": "main", "index": 0 }]] },
    "Claude - Executive Summary": { "main": [[{ "node": "Render HTML Email", "type": "main", "index": 0 }]] },
    "Render HTML Email": { "main": [[{ "node": "Send Report Email", "type": "main", "index": 0 }]] }
  },
  "active": false,
  "settings": { "executionOrder": "v1" },
  "meta": {
    "notes": "SETUP: 1) Add your Google Ads, HubSpot, Meta, SMTP, and Anthropic credentials. 2) Replace YOUR_CUSTOMER_ID, YOUR_AD_ACCOUNT_ID, and email addresses. 3) Adjust the holiday check node if needed. 4) Activate and test with manual execution first.",
    "author": "Edward Chalupa — echalupa.com",
    "article": "echalupa.com/blog/google-ads-budget-cut-framework"
  }
}
