{
  "id": "7sF6TGsrR5dlGVAx",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Weekly GA4 Analytics Report For Website/App",
  "tags": [],
  "nodes": [
    {
      "id": "242dcef9-b89b-4d05-b9dd-616c8f51942c",
      "name": "GA4 - Overview Current Week",
      "type": "n8n-nodes-base.googleAnalytics",
      "position": [
        -544,
        -464
      ],
      "parameters": {
        "endDate": "={{$now.minus({days: 1}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "dateRange": "custom",
        "startDate": "={{$now.minus({days: 7}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "metricsGA4": {
          "metricValues": [
            {},
            {
              "listName": "sessions"
            },
            {
              "name": "bounce_rate",
              "listName": "custom",
              "expression": "bounceRate"
            },
            {
              "name": "average_session_dur",
              "listName": "custom",
              "expression": "averageSessionDuration"
            },
            {
              "name": "new_users",
              "listName": "custom",
              "expression": "newUsers"
            }
          ]
        },
        "propertyId": {
          "__rl": true,
          "mode": "id",
          "value": "481410811"
        },
        "dimensionsGA4": {
          "dimensionValues": [
            {}
          ]
        },
        "additionalFields": {}
      },
      "credentials": {
        "googleAnalyticsOAuth2": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 2
    },
    {
      "id": "eb6868d0-0dab-49ab-99ba-b34108d16a17",
      "name": "GA4 - Overview Previous Week",
      "type": "n8n-nodes-base.googleAnalytics",
      "position": [
        -320,
        -464
      ],
      "parameters": {
        "endDate": "={{$now.minus({days: 8}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "dateRange": "custom",
        "startDate": "={{$now.minus({days: 14}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "metricsGA4": {
          "metricValues": [
            {},
            {
              "listName": "sessions"
            },
            {
              "name": "bounce_rate",
              "listName": "custom",
              "expression": "bounceRate"
            },
            {
              "name": "average_session_dur",
              "listName": "custom",
              "expression": "averageSessionDuration"
            },
            {
              "name": "new_users",
              "listName": "custom",
              "expression": "newUsers"
            }
          ]
        },
        "propertyId": {
          "__rl": true,
          "mode": "id",
          "value": "481410811"
        },
        "dimensionsGA4": {
          "dimensionValues": [
            {}
          ]
        },
        "additionalFields": {}
      },
      "credentials": {
        "googleAnalyticsOAuth2": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 2
    },
    {
      "id": "dca8f70f-f4ad-439a-b8b8-e9395d5b540e",
      "name": "GA4 - Top 5 Pages",
      "type": "n8n-nodes-base.googleAnalytics",
      "position": [
        -544,
        -272
      ],
      "parameters": {
        "limit": 5,
        "endDate": "={{$now.minus({days: 1}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "dateRange": "custom",
        "startDate": "={{$now.minus({days: 7}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "metricsGA4": {
          "metricValues": [
            {
              "name": "screen_page_view",
              "listName": "custom",
              "expression": "screenPageViews"
            },
            {
              "name": "average_session_duration",
              "listName": "custom",
              "expression": "averageSessionDuration"
            }
          ]
        },
        "propertyId": {
          "__rl": true,
          "mode": "id",
          "value": "481410811"
        },
        "dimensionsGA4": {
          "dimensionValues": [
            {
              "name": "unifiedScreenName",
              "listName": "other"
            }
          ]
        },
        "additionalFields": {}
      },
      "credentials": {
        "googleAnalyticsOAuth2": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 2
    },
    {
      "id": "85950514-c48e-4cab-b3c5-159ecc0d7d4a",
      "name": "GA4 - Top 5 Referrals",
      "type": "n8n-nodes-base.googleAnalytics",
      "position": [
        -544,
        -80
      ],
      "parameters": {
        "limit": 5,
        "endDate": "={{$now.minus({days: 1}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "dateRange": "custom",
        "startDate": "={{$now.minus({days: 7}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "metricsGA4": {
          "metricValues": [
            {
              "listName": "sessions"
            }
          ]
        },
        "propertyId": {
          "__rl": true,
          "mode": "id",
          "value": "481410811"
        },
        "dimensionsGA4": {
          "dimensionValues": [
            {
              "name": "sessionSourceMedium",
              "listName": "other"
            }
          ]
        },
        "additionalFields": {
          "orderByUI": {
            "metricOrderBy": [
              {
                "desc": true,
                "metricName": "sessions"
              }
            ]
          }
        }
      },
      "credentials": {
        "googleAnalyticsOAuth2": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 2
    },
    {
      "id": "0d766056-e67f-4827-838c-e6fe339231d9",
      "name": "GA4 - Top 5 Events",
      "type": "n8n-nodes-base.googleAnalytics",
      "position": [
        -544,
        112
      ],
      "parameters": {
        "limit": 5,
        "endDate": "={{$now.minus({days: 1}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "dateRange": "custom",
        "startDate": "={{$now.minus({days: 7}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "metricsGA4": {
          "metricValues": [
            {
              "name": "event_count",
              "listName": "custom",
              "expression": "eventCount"
            }
          ]
        },
        "propertyId": {
          "__rl": true,
          "mode": "id",
          "value": "481410811"
        },
        "dimensionsGA4": {
          "dimensionValues": [
            {
              "name": "eventName",
              "listName": "other"
            }
          ]
        },
        "additionalFields": {
          "orderByUI": {
            "metricOrderBy": [
              {
                "desc": true,
                "metricName": "=event_count"
              }
            ]
          }
        }
      },
      "credentials": {
        "googleAnalyticsOAuth2": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 2
    },
    {
      "id": "9f0dabdd-36c0-41d3-9052-b9d2cdf8ea85",
      "name": "GA4 - Top 5 Countries",
      "type": "n8n-nodes-base.googleAnalytics",
      "position": [
        -544,
        304
      ],
      "parameters": {
        "limit": 5,
        "endDate": "={{$now.minus({days: 1}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "dateRange": "custom",
        "startDate": "={{$now.minus({days: 7}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "metricsGA4": {
          "metricValues": [
            {
              "listName": "sessions"
            }
          ]
        },
        "propertyId": {
          "__rl": true,
          "mode": "id",
          "value": "481410811"
        },
        "dimensionsGA4": {
          "dimensionValues": [
            {
              "listName": "country"
            }
          ]
        },
        "additionalFields": {
          "orderByUI": {
            "metricOrderBy": [
              {
                "desc": true,
                "metricName": "sessions"
              }
            ]
          }
        }
      },
      "credentials": {
        "googleAnalyticsOAuth2": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 2
    },
    {
      "id": "1c982d79-9f8a-47c4-bb35-1b210abb1524",
      "name": "GA4 - Device Breakdown",
      "type": "n8n-nodes-base.googleAnalytics",
      "position": [
        -544,
        496
      ],
      "parameters": {
        "endDate": "={{$now.minus({days: 1}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "dateRange": "custom",
        "startDate": "={{$now.minus({days: 7}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "metricsGA4": {
          "metricValues": [
            {
              "listName": "sessions"
            }
          ]
        },
        "propertyId": {
          "__rl": true,
          "mode": "id",
          "value": "481410811"
        },
        "dimensionsGA4": {
          "dimensionValues": [
            {
              "listName": "deviceCategory"
            }
          ]
        },
        "additionalFields": {
          "orderByUI": {
            "metricOrderBy": [
              {
                "desc": true,
                "metricName": "sessions"
              }
            ]
          }
        }
      },
      "credentials": {
        "googleAnalyticsOAuth2": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 2
    },
    {
      "id": "7de9e216-c86a-44b3-9ae5-1872e44e264a",
      "name": "New vs Returning Users Breakdown",
      "type": "n8n-nodes-base.googleAnalytics",
      "position": [
        -544,
        688
      ],
      "parameters": {
        "endDate": "={{$now.minus({days: 1}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "dateRange": "custom",
        "startDate": "={{$now.minus({days: 7}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "metricsGA4": {
          "metricValues": [
            {
              "listName": "sessions"
            },
            {}
          ]
        },
        "propertyId": {
          "__rl": true,
          "mode": "id",
          "value": "481410811"
        },
        "dimensionsGA4": {
          "dimensionValues": [
            {
              "name": "newVsReturning",
              "listName": "other"
            }
          ]
        },
        "additionalFields": {}
      },
      "credentials": {
        "googleAnalyticsOAuth2": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 2
    },
    {
      "id": "0ea9bb61-586a-4d25-8a15-c7b875464470",
      "name": "Weekly Monday Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -976,
        112
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 8
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "43f5b7c2-562e-4003-9517-7022ab148529",
      "name": "Wait for All GA4 Data",
      "type": "n8n-nodes-base.merge",
      "position": [
        64,
        32
      ],
      "parameters": {
        "mode": "combine",
        "options": {
          "includeUnpaired": true
        },
        "combineBy": "combineByPosition",
        "numberInputs": 7
      },
      "typeVersion": 3.2
    },
    {
      "id": "404f04c1-57ff-456e-917e-62ee967655be",
      "name": "Generate AI Summary",
      "type": "@n8n/n8n-nodes-langchain.googleGemini",
      "position": [
        368,
        112
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "models/gemini-3.1-flash-lite-preview",
          "cachedResultName": "models/gemini-3.1-flash-lite-preview"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "content": "=You are a professional digital analytics consultant. Analyze the full week-over-week data below and write a concise executive summary.\n\n=== OVERVIEW ===\nTHIS WEEK \u2014 Users: {{$('GA4 - Overview Current Week').first().json.totalUsers}} | Sessions: {{$('GA4 - Overview Current Week').first().json.sessions}} | New Users: {{$('GA4 - Overview Current Week').first().json.new_users}} | Bounce Rate: {{$('GA4 - Overview Current Week').first().json.bounce_rate}} | Avg Duration (sec): {{$('GA4 - Overview Current Week').first().json.average_session_dur}}\nPREV WEEK \u2014 Users: {{$('GA4 - Overview Previous Week').first().json.totalUsers}} | Sessions: {{$('GA4 - Overview Previous Week').first().json.sessions}} | New Users: {{$('GA4 - Overview Previous Week').first().json.new_users}} | Bounce Rate: {{$('GA4 - Overview Previous Week').first().json.bounce_rate}} | Avg Duration (sec): {{$('GA4 - Overview Previous Week').first().json.average_session_dur}}\n\n=== TOP SCREENS ===\nTHIS WEEK: {{$('GA4 - Top 5 Pages').all().map((i,idx) => `${idx+1}. ${i.json.unifiedScreenName} (${i.json.screen_page_view} views)`).join(' | ')}}\nPREV WEEK: {{$('GA4 - Top 5 Pages Previous Week').all().map((i,idx) => `${idx+1}. ${i.json.unifiedScreenName} (${i.json.screen_page_view} views)`).join(' | ')}}\n\n=== TRAFFIC SOURCES ===\nTHIS WEEK: {{$('GA4 - Top 5 Referrals').all().map((i,idx) => `${idx+1}. ${i.json.sessionSourceMedium} (${i.json.sessions} sessions)`).join(' | ')}}\nPREV WEEK: {{$('GA4 - Top 5 Referrals Previous Week').all().map((i,idx) => `${idx+1}. ${i.json.sessionSourceMedium} (${i.json.sessions} sessions)`).join(' | ')}}\n\n=== TOP EVENTS ===\nTHIS WEEK: {{$('GA4 - Top 5 Events').all().map((i,idx) => `${idx+1}. ${i.json.eventName} (${i.json.event_count})`).join(' | ')}}\nPREV WEEK: {{$('GA4 - Top 5 Events Previous Week').all().map((i,idx) => `${idx+1}. ${i.json.eventName} (${i.json.event_count})`).join(' | ')}}\n\n=== TOP COUNTRIES ===\nTHIS WEEK: {{$('GA4 - Top 5 Countries').all().map((i,idx) => `${idx+1}. ${i.json.country} (${i.json.sessions} sessions)`).join(' | ')}}\nPREV WEEK: {{$('GA4 - Top 5 Countries Previous Week').all().map((i,idx) => `${idx+1}. ${i.json.country} (${i.json.sessions} sessions)`).join(' | ')}}\n\n=== DEVICES ===\nTHIS WEEK: {{$('GA4 - Device Breakdown').all().map(i => `${i.json.deviceCategory}: ${i.json.sessions}`).join(' | ')}}\nPREV WEEK: {{$('GA4 - Device Breakdown Previous Week').all().map(i => `${i.json.deviceCategory}: ${i.json.sessions}`).join(' | ')}}\n\n=== NEW VS RETURNING ===\nTHIS WEEK: {{$('New vs Returning Users Breakdown').all().map(i => `${i.json.newVsReturning}: ${i.json.totalUsers} users`).join(' | ')}}\nPREV WEEK: {{$('GA4 - New vs Returning Previous Week').all().map(i => `${i.json.newVsReturning}: ${i.json.totalUsers} users`).join(' | ')}}\n\n=== END OF DATA ===\n\nWrite exactly 3 to 5 bullet points summarizing the most important trends and changes. If a country, screen, or source appears in one week but not the other, treat it as entered or dropped from the top 5.\n\nRules:\n- Start each bullet with a dash (-)\n- Single sentence per bullet, maximum 25 words\n- Reference specific numbers and week-over-week changes\n- No markdown, no bold, no headers\n- Do not start any bullet with \"Based on the data\""
            }
          ]
        },
        "builtInTools": {}
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 1.1
    },
    {
      "id": "b173f712-0bfe-4ec1-8ad4-ef011a7bb3a1",
      "name": "Build Report & Email HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        864,
        112
      ],
      "parameters": {
        "jsCode": "// ============================================================\n// GA4 WEEKLY REPORT v9 \u2014 ALL ALIGNMENT FIXED\n// AppStoneLab Technologies\n// ============================================================\n\n// \u2500\u2500 1. PULL DATA \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst currentWeek  = $('GA4 - Overview Current Week').first().json;\nconst previousWeek = $('GA4 - Overview Previous Week').first().json;\n\nconst pages = $('GA4 - Top 5 Pages').all()\n  .map(i => i.json)\n  .map(p => ({ ...p, unifiedScreenName: (!p.unifiedScreenName || p.unifiedScreenName === '') ? '(empty)' : p.unifiedScreenName }))\n  .slice(0, 5);\n\nconst pagesPrev = $('GA4 - Top 5 Pages Previous Week').all()\n  .map(i => i.json)\n  .map(p => ({ ...p, unifiedScreenName: (!p.unifiedScreenName || p.unifiedScreenName === '') ? '(empty)' : p.unifiedScreenName }));\n\nconst referrals = $('GA4 - Top 5 Referrals').all()\n  .map(i => i.json)\n  .map(r => ({ ...r, sessionSourceMedium: r.sessionSourceMedium === '(direct) / (none)' ? 'Direct / App Open' : (r.sessionSourceMedium || 'Direct / App Open') }));\n\nconst referralsPrev = $('GA4 - Top 5 Referrals Previous Week').all()\n  .map(i => i.json)\n  .map(r => ({ ...r, sessionSourceMedium: r.sessionSourceMedium === '(direct) / (none)' ? 'Direct / App Open' : (r.sessionSourceMedium || 'Direct / App Open') }));\n\nconst countries     = $('GA4 - Top 5 Countries').all().map(i => i.json);\nconst countriesPrev = $('GA4 - Top 5 Countries Previous Week').all().map(i => i.json);\nconst devices       = $('GA4 - Device Breakdown').all().map(i => i.json);\nconst devicesPrev   = $('GA4 - Device Breakdown Previous Week').all().map(i => i.json);\nconst newVsRet      = $('New vs Returning Users Breakdown').all().map(i => i.json);\nconst newVsRetPrev  = $('GA4 - New vs Returning Previous Week').all().map(i => i.json);\n\nconst EXCLUDE_EVENTS = ['first_visit','first_open','page_view','app_remove','os_update','app_update','app_store_refund'];\nconst events     = $('GA4 - Top 5 Events').all().map(i => i.json).filter(e => !EXCLUDE_EVENTS.includes(e.eventName)).slice(0, 5);\nconst eventsPrev = $('GA4 - Top 5 Events Previous Week').all().map(i => i.json).filter(e => !EXCLUDE_EVENTS.includes(e.eventName));\n\n// Gemini\nlet aiSummary = '';\ntry {\n  const g = $('Generate AI Summary').first().json;\n  aiSummary = g.content?.parts?.[0]?.text || g.text || g.output || g.response || g.message?.content || g.candidates?.[0]?.content?.parts?.[0]?.text || '';\n} catch(e) { aiSummary = ''; }\naiSummary = typeof aiSummary === 'string' ? aiSummary : '';\n\nlet formattedAiSummary = '';\nif (aiSummary.trim().length > 0) {\n  const lines = aiSummary.split('\\n').map(l => l.trim()).filter(l => l.length > 0);\n  const items = lines.map(l => `<li style=\"margin-bottom:10px;padding-left:4px;\">${l.replace(/^[\\*\\-\u2022]\\s*/, '')}</li>`).join('');\n  formattedAiSummary = `<ul style=\"margin:0;padding-left:18px;font-family:Georgia,serif;font-size:13.5px;color:#3D3530;line-height:1.7;\">${items}</ul>`;\n}\n\n// \u2500\u2500 2. HELPERS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction wowChange(current, previous) {\n  const c = parseFloat(current), p = parseFloat(previous);\n  if (!p || p === 0) return { text: 'N/A', color: '#9CA3AF', arrow: '\u2014', pill: '#F3F4F6', pillText: '#6B7280' };\n  const pct = ((c - p) / p) * 100;\n  const up = pct >= 0;\n  return { text: (up ? '+' : '') + pct.toFixed(1) + '%', color: up ? '#2D6A4F' : '#B91C1C', arrow: up ? '\u2191' : '\u2193', pill: up ? '#ECFDF5' : '#FEF2F2', pillText: up ? '#2D6A4F' : '#B91C1C' };\n}\n\nfunction wowChangeBounce(current, previous) {\n  const c = parseFloat(current), p = parseFloat(previous);\n  if (!p || p === 0) return { text: 'N/A', color: '#9CA3AF', arrow: '\u2014', pill: '#F3F4F6', pillText: '#6B7280' };\n  const pct = ((c - p) / p) * 100;\n  const isGood = pct <= 0;\n  return { text: (pct >= 0 ? '+' : '') + pct.toFixed(1) + '%', color: isGood ? '#2D6A4F' : '#B91C1C', arrow: isGood ? '\u2193' : '\u2191', pill: isGood ? '#ECFDF5' : '#FEF2F2', pillText: isGood ? '#2D6A4F' : '#B91C1C' };\n}\n\nfunction listWoWWithPrev(currItem, prevItems, nameField, valueField) {\n  const prev = prevItems.find(p => p[nameField] === currItem[nameField]);\n  const currVal = parseInt(currItem[valueField] || 0);\n  if (!prev) return { wow: { text: 'New', arrow: '\u2605', pill: '#FBF5EB', pillText: '#C9A96E' }, prevValue: null };\n  const prevVal = parseInt(prev[valueField] || 0);\n  if (!prevVal || prevVal === 0) return { wow: { text: 'New', arrow: '\u2605', pill: '#FBF5EB', pillText: '#C9A96E' }, prevValue: null };\n  const pct = ((currVal - prevVal) / prevVal) * 100;\n  const up = pct >= 0;\n  return {\n    wow: { text: (up ? '+' : '') + pct.toFixed(1) + '%', arrow: up ? '\u2191' : '\u2193', pill: up ? '#ECFDF5' : '#FEF2F2', pillText: up ? '#2D6A4F' : '#B91C1C' },\n    prevValue: prevVal,\n  };\n}\n\nfunction formatDuration(s) {\n  const sec = parseFloat(s);\n  if (isNaN(sec)) return '0s';\n  const m = Math.floor(sec / 60), r = Math.round(sec % 60);\n  return m > 0 ? `${m}m ${r}s` : `${r}s`;\n}\nfunction fmt(n) { const num = parseInt(n); return isNaN(num) ? '0' : num.toLocaleString('en-IN'); }\nfunction fmtPct(n) { return (parseFloat(n) * 100).toFixed(1) + '%'; }\n\n// \u2500\u2500 3. WoW \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst wow = {\n  totalUsers:  wowChange(currentWeek.totalUsers,          previousWeek.totalUsers),\n  sessions:    wowChange(currentWeek.sessions,            previousWeek.sessions),\n  newUsers:    wowChange(currentWeek.new_users,           previousWeek.new_users),\n  bounceRate:  wowChangeBounce(currentWeek.bounce_rate,   previousWeek.bounce_rate),\n  avgDuration: wowChange(currentWeek.average_session_dur, previousWeek.average_session_dur),\n};\n\nconst newUsersRow     = newVsRet.find(r => r.newVsReturning === 'new')           || { totalUsers: 0 };\nconst returningRow    = newVsRet.find(r => r.newVsReturning === 'returning')     || { totalUsers: 0 };\nconst newUsersPrevRow = newVsRetPrev.find(r => r.newVsReturning === 'new')       || { totalUsers: 0 };\nconst retUsersPrevRow = newVsRetPrev.find(r => r.newVsReturning === 'returning') || { totalUsers: 0 };\nconst totalNvR        = (parseInt(newUsersRow.totalUsers) + parseInt(returningRow.totalUsers)) || 1;\nconst wowNewUsers     = wowChange(newUsersRow.totalUsers,  newUsersPrevRow.totalUsers);\nconst wowRetUsers     = wowChange(returningRow.totalUsers, retUsersPrevRow.totalUsers);\nconst totalDeviceSessions = devices.reduce((s, d) => s + parseInt(d.sessions || 0), 0) || 1;\n\nconst startLabel     = $now.minus({days: 7}).toFormat('MMM dd, yyyy');\nconst endLabel       = $now.minus({days: 1}).toFormat('MMM dd, yyyy');\nconst prevStartLabel = $now.minus({days: 14}).toFormat('MMM dd');\nconst prevEndLabel   = $now.minus({days: 8}).toFormat('MMM dd');\n\n// \u2500\u2500 4. DESIGN TOKENS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst C = {\n  bg: '#F7F5F2', card: '#FFFFFF', headerBg: '#1C1C1C', headerSub: '#A89880',\n  gold: '#C9A96E', sectionText: '#1C1C1C', label: '#6B6560', value: '#1C1C1C',\n  prevValue: '#B0A89E', rowAlt: '#FDFCFB', rowBase: '#FFFFFF', divider: '#EDE8E2',\n  footerBg: '#1C1C1C', footerText: '#6B6560',\n};\n\n// \u2500\u2500 5. HTML COMPONENTS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst sectionGap = `<tr><td colspan=\"5\" style=\"padding:8px 0;background:${C.bg};font-size:0;line-height:0;\">&nbsp;</td></tr>`;\n\nconst sectionHeader = (title, subtitle) => `\n<tr><td colspan=\"5\" style=\"padding:0;\">\n  <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">\n    <tr><td style=\"padding:18px 24px 13px;border-bottom:2px solid ${C.gold};background:${C.card};\">\n      <span style=\"font-family:Georgia,serif;font-size:15px;font-weight:bold;color:${C.sectionText};letter-spacing:0.3px;\">${title}</span>\n      ${subtitle ? `<span style=\"font-family:Arial,sans-serif;font-size:10px;color:${C.headerSub};margin-left:10px;text-transform:uppercase;letter-spacing:0.8px;\">${subtitle}</span>` : ''}\n    </td></tr>\n  </table>\n</td></tr>`;\n\n// colHeaders accepts array of {label, align} \u2014 aligns header text to match data cells\nconst colHeaders = (cols) => `\n<tr style=\"background:#F9F7F4;\">\n  ${cols.map((c, i) => {\n    const align = c.align || 'left';\n    const padLeft  = (align === 'left' && i === 0) ? '24px' : '12px';\n    const padRight = align === 'right' ? '20px' : '12px';\n    return `<td style=\"font-family:Arial,sans-serif;font-size:10px;font-weight:bold;color:${C.label};\n      padding:10px ${padRight} 10px ${padLeft};border-bottom:1px solid ${C.divider};\n      text-transform:uppercase;letter-spacing:1px;text-align:${align};white-space:nowrap;\">${c.label}</td>`;\n  }).join('')}\n</tr>`;\n\n// Change cell: \"was X\" + pill both right-aligned, stacked, in a right-aligned block\n// Both elements are display:block and text-align:right so they stack flush right\nfunction changeCell(wowObj, prevValue) {\n  const wasLine = prevValue !== null\n    ? `<span style=\"display:block;font-family:Arial,sans-serif;font-size:10px;color:#B0A89E;text-align:right;margin-bottom:4px;\">was ${fmt(prevValue)}</span>`\n    : '';\n  const pillLine = `<span style=\"display:block;text-align:right;\">\n    <span style=\"display:inline-block;background:${wowObj.pill};border-radius:12px;padding:3px 8px;\n      font-family:Arial,sans-serif;font-size:11px;font-weight:bold;color:${wowObj.pillText};white-space:nowrap;\">\n      ${wowObj.arrow}&thinsp;${wowObj.text}\n    </span>\n  </span>`;\n  return `<td style=\"padding:13px 20px 13px 12px;border-bottom:1px solid ${C.divider};vertical-align:middle;\">${wasLine}${pillLine}</td>`;\n}\n\n// Inline pill (no \"was\" \u2014 used in overview where Last Week column already exists)\nfunction pillOnly(wowObj) {\n  return `<span style=\"display:inline-block;background:${wowObj.pill};border-radius:12px;padding:3px 8px;\n    font-family:Arial,sans-serif;font-size:11px;font-weight:bold;color:${wowObj.pillText};white-space:nowrap;\">\n    ${wowObj.arrow}&thinsp;${wowObj.text}\n  </span>`;\n}\n\n// Overview row: label(left) / this week(right) / last week(right) / pill(right)\nconst overviewRow = (label, curr, prev, wowObj, isAlt) => `\n<tr style=\"background:${isAlt ? C.rowAlt : C.rowBase};\">\n  <td style=\"font-family:Arial,sans-serif;font-size:13px;color:${C.label};padding:14px 12px 14px 24px;border-bottom:1px solid ${C.divider};width:34%;text-align:left;\">${label}</td>\n  <td style=\"font-family:Georgia,serif;font-size:15px;font-weight:bold;color:${C.value};padding:14px 12px;border-bottom:1px solid ${C.divider};width:18%;text-align:right;\">${curr}</td>\n  <td style=\"font-family:Arial,sans-serif;font-size:13px;color:${C.prevValue};padding:14px 12px;border-bottom:1px solid ${C.divider};width:18%;text-align:right;\">${prev}</td>\n  <td style=\"padding:14px 20px 14px 12px;border-bottom:1px solid ${C.divider};width:30%;text-align:right;\">${pillOnly(wowObj)}</td>\n</tr>`;\n\n// List row: rank(center) / name(left) / value(right) / change cell(right-stacked)\nconst listRowWoW = (rank, name, value, wowObj, prevValue, isAlt) => `\n<tr style=\"background:${isAlt ? C.rowAlt : C.rowBase};\">\n  <td style=\"font-family:Arial,sans-serif;font-size:12px;color:${C.gold};font-weight:bold;padding:13px 0 13px 24px;border-bottom:1px solid ${C.divider};width:6%;text-align:center;\">${rank}</td>\n  <td style=\"font-family:Arial,sans-serif;font-size:13px;color:${C.value};padding:13px 12px;border-bottom:1px solid ${C.divider};text-align:left;\">${name}</td>\n  <td style=\"font-family:Georgia,serif;font-size:14px;font-weight:bold;color:${C.value};padding:13px 12px;border-bottom:1px solid ${C.divider};text-align:right;white-space:nowrap;\">${value}</td>\n  ${changeCell(wowObj, prevValue)}\n</tr>`;\n\n// Bar row: label(left) / bar / sessions n+%(right) / change cell(right-stacked)\nconst barRowWoW = (label, value, total, barColor, wowObj, prevValue, isAlt) => {\n  const pct = ((parseInt(value || 0) / total) * 100).toFixed(1);\n  const barWidth = Math.min(99, Math.max(1, Math.round(parseFloat(pct))));\n  return `\n<tr style=\"background:${isAlt ? C.rowAlt : C.rowBase};\">\n  <td style=\"font-family:Arial,sans-serif;font-size:13px;color:${C.value};padding:14px 24px 14px 24px;border-bottom:1px solid ${C.divider};width:22%;text-align:left;\">${label}</td>\n  <td style=\"padding:14px 12px;border-bottom:1px solid ${C.divider};width:26%;\">\n    <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"><tr>\n      <td style=\"width:${barWidth}%;background:${barColor};height:6px;border-radius:3px 0 0 3px;font-size:0;line-height:0;\"></td>\n      <td style=\"width:${100-barWidth}%;background:#EDE8E2;height:6px;border-radius:0 3px 3px 0;font-size:0;line-height:0;\"></td>\n    </tr></table>\n  </td>\n  <td style=\"font-family:Georgia,serif;font-size:14px;font-weight:bold;color:${C.value};padding:14px 12px;border-bottom:1px solid ${C.divider};text-align:right;white-space:nowrap;width:24%;\">\n    ${fmt(value)}&thinsp;<span style=\"font-family:Arial,sans-serif;font-size:11px;color:${C.prevValue};font-weight:normal;\">${pct}%</span>\n  </td>\n  ${changeCell(wowObj, prevValue)}\n</tr>`;\n};\n\n// \u2500\u2500 6. BUILD ROWS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst deviceColors = { mobile: '#C9A96E', desktop: '#5C6B73', tablet: '#8C7B6E' };\nconst deviceLabels = { mobile: 'Mobile', desktop: 'Desktop', tablet: 'Tablet' };\n\nconst deviceRows = devices.map((d, i) => {\n  const cat = (d.deviceCategory || 'other').toLowerCase();\n  const { wow: wowDev, prevValue } = listWoWWithPrev(d, devicesPrev, 'deviceCategory', 'sessions');\n  return barRowWoW(deviceLabels[cat] || cat, d.sessions, totalDeviceSessions, deviceColors[cat] || C.gold, wowDev, prevValue, i % 2 !== 0);\n}).join('');\n\nconst nvrRows = [\n  barRowWoW('New Users',       newUsersRow.totalUsers,  totalNvR, '#2D6A4F', wowNewUsers, parseInt(newUsersPrevRow.totalUsers) || null, false),\n  barRowWoW('Returning Users', returningRow.totalUsers, totalNvR, C.gold,    wowRetUsers, parseInt(retUsersPrevRow.totalUsers) || null, true),\n].join('');\n\nconst pageRows = pages.map((p, i) => {\n  const { wow: w, prevValue } = listWoWWithPrev(p, pagesPrev, 'unifiedScreenName', 'screen_page_view');\n  return listRowWoW(i + 1, p.unifiedScreenName, fmt(p.screen_page_view), w, prevValue, i % 2 !== 0);\n}).join('');\n\nconst referralRows = referrals.map((r, i) => {\n  const { wow: w, prevValue } = listWoWWithPrev(r, referralsPrev, 'sessionSourceMedium', 'sessions');\n  return listRowWoW(i + 1, r.sessionSourceMedium, fmt(r.sessions), w, prevValue, i % 2 !== 0);\n}).join('');\n\nconst eventRows = events.map((e, i) => {\n  const { wow: w, prevValue } = listWoWWithPrev(e, eventsPrev, 'eventName', 'event_count');\n  return listRowWoW(i + 1, e.eventName, fmt(e.event_count), w, prevValue, i % 2 !== 0);\n}).join('');\n\nconst countryRows = countries.map((c, i) => {\n  const { wow: w, prevValue } = listWoWWithPrev(c, countriesPrev, 'country', 'sessions');\n  return listRowWoW(i + 1, c.country, fmt(c.sessions), w, prevValue, i % 2 !== 0);\n}).join('');\n\n// \u2500\u2500 7. COLUMN HEADER DEFINITIONS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// align MUST match the text-align of the data cell below it\n\nconst hdrsOverview   = [{label:'Metric',align:'left'},{label:'This Week',align:'right'},{label:'Last Week',align:'right'},{label:'Change',align:'right'}];\nconst hdrsAudience   = [{label:'Segment',align:'left'},{label:'Share',align:'left'},{label:'Users',align:'right'},{label:'Change',align:'right'}];\nconst hdrsScreens    = [{label:'#',align:'center'},{label:'Screen / Page',align:'left'},{label:'Views',align:'right'},{label:'Change',align:'right'}];\nconst hdrsTraffic    = [{label:'#',align:'center'},{label:'Source / Medium',align:'left'},{label:'Sessions',align:'right'},{label:'Change',align:'right'}];\nconst hdrsEvents     = [{label:'#',align:'center'},{label:'Event Name',align:'left'},{label:'Count',align:'right'},{label:'Change',align:'right'}];\nconst hdrsGeo        = [{label:'#',align:'center'},{label:'Country',align:'left'},{label:'Sessions',align:'right'},{label:'Change',align:'right'}];\nconst hdrsDevices    = [{label:'Device',align:'left'},{label:'Share',align:'left'},{label:'Sessions',align:'right'},{label:'Change',align:'right'}];\n\n// \u2500\u2500 8. BUILD EMAIL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n  <title>Weekly Performance Report</title>\n</head>\n<body style=\"margin:0;padding:0;background:${C.bg};font-family:Arial,sans-serif;\">\n<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:${C.bg};\">\n<tr><td align=\"center\" style=\"padding:32px 16px 40px;\">\n\n  <table width=\"600\" cellpadding=\"0\" cellspacing=\"0\"\n    style=\"max-width:600px;width:100%;background:${C.card};border-radius:4px;overflow:hidden;box-shadow:0 2px 40px rgba(0,0,0,0.09);\">\n\n    <!-- HEADER -->\n    <tr>\n      <td style=\"background:${C.headerBg};padding:40px 32px 36px;\">\n        <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"><tr><td>\n          <div style=\"font-family:Arial,sans-serif;font-size:10px;font-weight:bold;color:${C.gold};letter-spacing:2.5px;text-transform:uppercase;margin-bottom:14px;\">Analytics Report</div>\n          <div style=\"font-family:Georgia,serif;font-size:28px;font-weight:bold;color:#FFFFFF;line-height:1.2;margin-bottom:10px;letter-spacing:-0.5px;\">Weekly Performance<br>Summary</div>\n          <div style=\"font-family:Arial,sans-serif;font-size:13px;color:${C.headerSub};margin-bottom:24px;\">${startLabel} &nbsp;\u2014&nbsp; ${endLabel}</div>\n          <div style=\"height:1px;background:linear-gradient(to right,${C.gold},transparent);margin-bottom:24px;\"></div>\n          <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"><tr>\n            <td style=\"text-align:center;padding:0 12px 0 0;width:25%;border-right:1px solid #2E2E2E;\">\n              <div style=\"font-family:Georgia,serif;font-size:22px;font-weight:bold;color:#FFFFFF;\">${fmt(currentWeek.totalUsers)}</div>\n              <div style=\"font-family:Arial,sans-serif;font-size:10px;color:${C.headerSub};margin-top:4px;text-transform:uppercase;letter-spacing:0.8px;\">Users</div>\n              <div style=\"font-family:Arial,sans-serif;font-size:11px;font-weight:bold;color:${wow.totalUsers.color};margin-top:5px;\">${wow.totalUsers.arrow} ${wow.totalUsers.text}</div>\n            </td>\n            <td style=\"text-align:center;padding:0 12px;width:25%;border-right:1px solid #2E2E2E;\">\n              <div style=\"font-family:Georgia,serif;font-size:22px;font-weight:bold;color:#FFFFFF;\">${fmt(currentWeek.sessions)}</div>\n              <div style=\"font-family:Arial,sans-serif;font-size:10px;color:${C.headerSub};margin-top:4px;text-transform:uppercase;letter-spacing:0.8px;\">Sessions</div>\n              <div style=\"font-family:Arial,sans-serif;font-size:11px;font-weight:bold;color:${wow.sessions.color};margin-top:5px;\">${wow.sessions.arrow} ${wow.sessions.text}</div>\n            </td>\n            <td style=\"text-align:center;padding:0 12px;width:25%;border-right:1px solid #2E2E2E;\">\n              <div style=\"font-family:Georgia,serif;font-size:22px;font-weight:bold;color:#FFFFFF;\">${fmtPct(currentWeek.bounce_rate)}</div>\n              <div style=\"font-family:Arial,sans-serif;font-size:10px;color:${C.headerSub};margin-top:4px;text-transform:uppercase;letter-spacing:0.8px;\">Bounce</div>\n              <div style=\"font-family:Arial,sans-serif;font-size:11px;font-weight:bold;color:${wow.bounceRate.color};margin-top:5px;\">${wow.bounceRate.arrow} ${wow.bounceRate.text}</div>\n            </td>\n            <td style=\"text-align:center;padding:0 0 0 12px;width:25%;\">\n              <div style=\"font-family:Georgia,serif;font-size:22px;font-weight:bold;color:#FFFFFF;\">${formatDuration(currentWeek.average_session_dur)}</div>\n              <div style=\"font-family:Arial,sans-serif;font-size:10px;color:${C.headerSub};margin-top:4px;text-transform:uppercase;letter-spacing:0.8px;\">Duration</div>\n              <div style=\"font-family:Arial,sans-serif;font-size:11px;font-weight:bold;color:${wow.avgDuration.color};margin-top:5px;\">${wow.avgDuration.arrow} ${wow.avgDuration.text}</div>\n            </td>\n          </tr></table>\n        </td></tr></table>\n      </td>\n    </tr>\n\n    <!-- BODY -->\n    <tr><td style=\"padding:0;background:${C.bg};\">\n      <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">\n\n        ${sectionGap}\n\n        <!-- AI SUMMARY -->\n        ${formattedAiSummary ? `\n        <tr><td colspan=\"5\" style=\"padding:0;background:${C.card};\">\n          <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">\n            <tr><td style=\"padding:20px 24px 14px;border-bottom:2px solid ${C.gold};\">\n              <span style=\"font-family:Georgia,serif;font-size:15px;font-weight:bold;color:${C.sectionText};letter-spacing:0.3px;\">AI Executive Summary</span>\n              <span style=\"font-family:Arial,sans-serif;font-size:10px;color:${C.headerSub};margin-left:10px;text-transform:uppercase;letter-spacing:0.8px;\">Generated by Gemini</span>\n            </td></tr>\n            <tr><td style=\"padding:20px 24px 24px;background:#FDFCFB;\">${formattedAiSummary}</td></tr>\n          </table>\n        </td></tr>\n        ${sectionGap}` : ''}\n\n        <!-- OVERVIEW -->\n        <tr><td colspan=\"5\" style=\"padding:0;background:${C.card};\">\n          <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">\n            ${sectionHeader('Overview', `vs ${prevStartLabel} \u2013 ${prevEndLabel}`)}\n            ${colHeaders(hdrsOverview)}\n            ${overviewRow('Total Users',           fmt(currentWeek.totalUsers),                     fmt(previousWeek.totalUsers),                     wow.totalUsers,  false)}\n            ${overviewRow('Sessions',              fmt(currentWeek.sessions),                       fmt(previousWeek.sessions),                       wow.sessions,    true)}\n            ${overviewRow('New Users',             fmt(currentWeek.new_users),                      fmt(previousWeek.new_users),                      wow.newUsers,    false)}\n            ${overviewRow('Bounce Rate',           fmtPct(currentWeek.bounce_rate),                 fmtPct(previousWeek.bounce_rate),                 wow.bounceRate,  true)}\n            ${overviewRow('Avg. Session Duration', formatDuration(currentWeek.average_session_dur), formatDuration(previousWeek.average_session_dur), wow.avgDuration, false)}\n          </table>\n        </td></tr>\n\n        ${sectionGap}\n\n        <!-- AUDIENCE -->\n        <tr><td colspan=\"5\" style=\"padding:0;background:${C.card};\">\n          <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">\n            ${sectionHeader('Audience', 'New vs Returning \u00b7 WoW')}\n            ${colHeaders(hdrsAudience)}\n            ${nvrRows}\n          </table>\n        </td></tr>\n\n        ${sectionGap}\n\n        <!-- TOP SCREENS -->\n        <tr><td colspan=\"5\" style=\"padding:0;background:${C.card};\">\n          <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">\n            ${sectionHeader('Top Screens', 'By views \u00b7 WoW')}\n            ${colHeaders(hdrsScreens)}\n            ${pageRows}\n          </table>\n        </td></tr>\n\n        ${sectionGap}\n\n        <!-- TRAFFIC SOURCES -->\n        <tr><td colspan=\"5\" style=\"padding:0;background:${C.card};\">\n          <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">\n            ${sectionHeader('Traffic Sources', 'Top channels \u00b7 WoW')}\n            ${colHeaders(hdrsTraffic)}\n            ${referralRows}\n          </table>\n        </td></tr>\n\n        ${sectionGap}\n\n        <!-- TOP EVENTS -->\n        ${events.length > 0 ? `\n        <tr><td colspan=\"5\" style=\"padding:0;background:${C.card};\">\n          <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">\n            ${sectionHeader('Top Events', 'User interactions \u00b7 WoW')}\n            ${colHeaders(hdrsEvents)}\n            ${eventRows}\n          </table>\n        </td></tr>\n        ${sectionGap}` : ''}\n\n        <!-- GEOGRAPHY -->\n        <tr><td colspan=\"5\" style=\"padding:0;background:${C.card};\">\n          <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">\n            ${sectionHeader('Geography', 'Top 5 countries \u00b7 WoW')}\n            ${colHeaders(hdrsGeo)}\n            ${countryRows}\n          </table>\n        </td></tr>\n\n        ${sectionGap}\n\n        <!-- DEVICES -->\n        <tr><td colspan=\"5\" style=\"padding:0;background:${C.card};\">\n          <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">\n            ${sectionHeader('Devices', 'By device category \u00b7 WoW')}\n            ${colHeaders(hdrsDevices)}\n            ${deviceRows}\n          </table>\n        </td></tr>\n\n        ${sectionGap}\n\n      </table>\n    </td></tr>\n\n    <!-- FOOTER -->\n    <tr>\n      <td style=\"background:${C.footerBg};padding:32px;\">\n        <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">\n          <tr><td style=\"padding-bottom:16px;\">\n            <div style=\"height:1px;background:linear-gradient(to right,${C.gold},transparent);margin-bottom:20px;\"></div>\n            <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"><tr>\n              <td>\n                <div style=\"font-family:Georgia,serif;font-size:14px;color:#FFFFFF;font-weight:bold;margin-bottom:4px;\">AppStoneLab Technologies</div>\n                <a href=\"https://appstonelab.com\" style=\"font-family:Arial,sans-serif;font-size:11px;color:${C.gold};text-decoration:none;\">appstonelab.com</a>\n              </td>\n              <td style=\"text-align:right;vertical-align:top;\">\n                <div style=\"font-family:Arial,sans-serif;font-size:10px;color:${C.footerText};line-height:1.8;\">\n                  ${startLabel} \u2014 ${endLabel}<br>Google Analytics 4<br>\n                  <a href=\"#\" style=\"color:${C.footerText};text-decoration:underline;\">Unsubscribe</a>\n                </div>\n              </td>\n            </tr></table>\n          </td></tr>\n          <tr><td>\n            <div style=\"font-family:Arial,sans-serif;font-size:10px;color:#3A3A3A;line-height:1.6;\">\n              This report is automatically generated every Monday via n8n workflow automation. Data reflects the 7-day period ending ${endLabel}.\n            </div>\n          </td></tr>\n        </table>\n      </td>\n    </tr>\n\n  </table>\n</td></tr>\n</table>\n</body>\n</html>`;\n\n// \u2500\u2500 9. OUTPUT \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nreturn [{\n  json: {\n    subject: `Weekly Performance Report \u2014 ${startLabel} to ${endLabel}`,\n    html,\n    recipients: 'user@example.com',\n    _debug: {\n      currentWeekUsers: currentWeek.totalUsers,\n      previousWeekUsers: previousWeek.totalUsers,\n      pagesReturned: pages.length,\n      eventsReturned: events.length,\n      devicesReturned: devices.length,\n      aiSummaryLength: aiSummary.length,\n    }\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "a50596dc-72d1-4efa-8d73-d9f9ed96d5c2",
      "name": "Send Weekly Report",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        1248,
        112
      ],
      "parameters": {
        "html": "={{ $json.html }}",
        "options": {},
        "subject": "={{ $json.subject }}",
        "toEmail": "={{ $json.recipients }}, jignesh.sanghani@dev.appstonelab.com",
        "fromEmail": "={{ $json.recipients }}"
      },
      "credentials": {
        "smtp": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "402d7cd9-f814-49f5-9ae9-985d2ae86492",
      "name": "GA4 - Top 5 Pages Previous Week",
      "type": "n8n-nodes-base.googleAnalytics",
      "position": [
        -320,
        -272
      ],
      "parameters": {
        "limit": 5,
        "endDate": "={{$now.minus({days: 8}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "dateRange": "custom",
        "startDate": "={{$now.minus({days: 14}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "metricsGA4": {
          "metricValues": [
            {
              "name": "screen_page_view",
              "listName": "custom",
              "expression": "screenPageViews"
            },
            {
              "name": "average_session_duration",
              "listName": "custom",
              "expression": "averageSessionDuration"
            }
          ]
        },
        "propertyId": {
          "__rl": true,
          "mode": "id",
          "value": "481410811"
        },
        "dimensionsGA4": {
          "dimensionValues": [
            {
              "name": "unifiedScreenName",
              "listName": "other"
            }
          ]
        },
        "additionalFields": {}
      },
      "credentials": {
        "googleAnalyticsOAuth2": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 2
    },
    {
      "id": "98a79977-3d6d-4200-ba26-4b2f59868ada",
      "name": "GA4 - Top 5 Referrals Previous Week",
      "type": "n8n-nodes-base.googleAnalytics",
      "position": [
        -320,
        -80
      ],
      "parameters": {
        "limit": 5,
        "endDate": "={{$now.minus({days: 8}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "dateRange": "custom",
        "startDate": "={{$now.minus({days: 14}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "metricsGA4": {
          "metricValues": [
            {
              "listName": "sessions"
            }
          ]
        },
        "propertyId": {
          "__rl": true,
          "mode": "id",
          "value": "481410811"
        },
        "dimensionsGA4": {
          "dimensionValues": [
            {
              "name": "sessionSourceMedium",
              "listName": "other"
            }
          ]
        },
        "additionalFields": {
          "orderByUI": {
            "metricOrderBy": [
              {
                "desc": true,
                "metricName": "sessions"
              }
            ]
          }
        }
      },
      "credentials": {
        "googleAnalyticsOAuth2": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 2
    },
    {
      "id": "cec80406-03d0-4592-9646-e489216e32a7",
      "name": "GA4 - Top 5 Events Previous Week",
      "type": "n8n-nodes-base.googleAnalytics",
      "position": [
        -320,
        112
      ],
      "parameters": {
        "limit": 5,
        "endDate": "={{$now.minus({days: 8}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "dateRange": "custom",
        "startDate": "={{$now.minus({days: 14}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "metricsGA4": {
          "metricValues": [
            {
              "name": "event_count",
              "listName": "custom",
              "expression": "eventCount"
            }
          ]
        },
        "propertyId": {
          "__rl": true,
          "mode": "id",
          "value": "481410811"
        },
        "dimensionsGA4": {
          "dimensionValues": [
            {
              "name": "eventName",
              "listName": "other"
            }
          ]
        },
        "additionalFields": {
          "orderByUI": {
            "metricOrderBy": [
              {
                "desc": true,
                "metricName": "=event_count"
              }
            ]
          }
        }
      },
      "credentials": {
        "googleAnalyticsOAuth2": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 2
    },
    {
      "id": "31a96f65-3ff9-47da-b6ea-62e059d636f3",
      "name": "GA4 - Top 5 Countries Previous Week",
      "type": "n8n-nodes-base.googleAnalytics",
      "position": [
        -320,
        304
      ],
      "parameters": {
        "limit": 5,
        "endDate": "={{$now.minus({days: 8}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "dateRange": "custom",
        "startDate": "={{$now.minus({days: 14}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "metricsGA4": {
          "metricValues": [
            {
              "listName": "sessions"
            }
          ]
        },
        "propertyId": {
          "__rl": true,
          "mode": "id",
          "value": "481410811"
        },
        "dimensionsGA4": {
          "dimensionValues": [
            {
              "listName": "country"
            }
          ]
        },
        "additionalFields": {
          "orderByUI": {
            "metricOrderBy": [
              {
                "desc": true,
                "metricName": "sessions"
              }
            ]
          }
        }
      },
      "credentials": {
        "googleAnalyticsOAuth2": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 2
    },
    {
      "id": "7ac51236-fc7e-4e90-9ebc-44c4a897697e",
      "name": "GA4 - Device Breakdown Previous Week",
      "type": "n8n-nodes-base.googleAnalytics",
      "position": [
        -320,
        496
      ],
      "parameters": {
        "endDate": "={{$now.minus({days: 8}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "dateRange": "custom",
        "startDate": "={{$now.minus({days: 14}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "metricsGA4": {
          "metricValues": [
            {
              "listName": "sessions"
            }
          ]
        },
        "propertyId": {
          "__rl": true,
          "mode": "id",
          "value": "481410811"
        },
        "dimensionsGA4": {
          "dimensionValues": [
            {
              "listName": "deviceCategory"
            }
          ]
        },
        "additionalFields": {
          "orderByUI": {
            "metricOrderBy": [
              {
                "desc": true,
                "metricName": "sessions"
              }
            ]
          }
        }
      },
      "credentials": {
        "googleAnalyticsOAuth2": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 2
    },
    {
      "id": "814f5d15-8fef-4db4-a676-deece444adb0",
      "name": "GA4 - New vs Returning Previous Week",
      "type": "n8n-nodes-base.googleAnalytics",
      "position": [
        -320,
        688
      ],
      "parameters": {
        "endDate": "={{$now.minus({days: 8}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "dateRange": "custom",
        "startDate": "={{$now.minus({days: 14}).startOf('day').toFormat('yyyy-MM-dd')}}",
        "metricsGA4": {
          "metricValues": [
            {
              "listName": "sessions"
            },
            {}
          ]
        },
        "propertyId": {
          "__rl": true,
          "mode": "id",
          "value": "481410811"
        },
        "dimensionsGA4": {
          "dimensionValues": [
            {
              "name": "newVsReturning",
              "listName": "other"
            }
          ]
        },
        "additionalFields": {}
      },
      "credentials": {
        "googleAnalyticsOAuth2": {
          "name": "<your credential>"
        }
      },
      "executeOnce": true,
      "typeVersion": 2
    },
    {
      "id": "7b51be87-b503-40e8-8516-3965f3ae013d",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1888,
        -48
      ],
      "parameters": {
        "color": 6,
        "width": 544,
        "height": 480,
        "content": "## \ud83d\udcca Weekly GA4 Business Intelligence Report\n\nAutomatically runs **every Monday at 8:00 AM** and delivers a fully formatted HTML performance report to stakeholders via email.\n\n**What this workflow does:**\n- Fetches 14 separate reports from Google Analytics 4 (Current & Previous week data for 7 key metrics)\n- Generates an AI executive summary analyzing Week-over-Week (WoW) trends using Gemini\n- Calculates WoW % changes for ALL metrics (Overview, Pages, Referrals, Events, Countries, Devices, New vs Returning)\n- Builds a premium HTML email and delivers it via Gmail/SMTP\n\n**Before using this template:**\n1. Set your GA4 Property ID in all 14 GA4 nodes\n2. Connect your Google Analytics OAuth2 credential\n3. Connect your Gemini API credential\n4. Connect your Gmail / SMTP credential\n5. Set your recipient email(s) in the Email node or Code node\n6. Set your timezone in workflow settings"
      },
      "typeVersion": 1
    },
    {
      "id": "3b842436-77b7-4605-8969-a9b187cbcff2",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1248,
        -464
      ],
      "parameters": {
        "color": 3,
        "width": 688,
        "height": 448,
        "content": "## \ud83d\udce1 Google Analytics 4 \u2014 Data Nodes\n\nFetches data for 7 key categories, running both **Current Week** and **Previous Week** nodes to generate deep Week-over-Week (WoW) comparisons.\n\n**\u26a0\ufe0f Required: Set your Property ID**\nOpen each of the 14 nodes \u2192 Property ID field \u2192 replace {YOUR_PROPERTY_ID} with your GA4 numeric property ID.\nFound at: GA4 Admin \u2192 Property Settings \u2192 Property ID\n\n**Node breakdown (Each has a Current & Previous week version):**\n- **Overview** \u2014 totals for sessions, users, bounce rate, etc.\n- **Top 5 Pages** \u2014 screens/pages by views\n- **Top 5 Referrals** \u2014 traffic sources by sessions\n- **Top 5 Events** \u2014 user interactions by count\n- **Top 5 Countries** \u2014 geographic breakdown by sessions\n- **Device Breakdown** \u2014 mobile / desktop / tablet split\n- **New vs Returning** \u2014 audience loyalty breakdown\n\n**All nodes have Execute Once = ON** to prevent duplicate rows."
      },
      "typeVersion": 1
    },
    {
      "id": "7e2f82c1-abb2-45ff-bfdb-d876182d3495",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1328,
        48
      ],
      "parameters": {
        "color": 7,
        "width": 512,
        "height": 288,
        "content": "## \u23f0 Schedule Trigger\n\nFires every **Monday at 8:00 AM IST**.\n\n\n\n\n\n\n\n\n**To change timezone or time:**\nOpen workflow settings \u2192 change the Timezone to your preferred one.\n\nDefault timezone: Your Local Timezone"
      },
      "typeVersion": 1
    },
    {
      "id": "d05075f0-2f6d-49ac-978f-fa03e5c90715",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -48,
        -272
      ],
      "parameters": {
        "color": 4,
        "width": 304,
        "height": 624,
        "content": "## \ud83d\udd00 Merge\n\nWaits for **all GA4 data chains (14 nodes total)** to complete before passing data forward.\n\nWithout this node, Gemini and Code nodes could trigger before all GA4 data is ready - causing empty or undefined values in the report.\n\nMode: Combine by Position"
      },
      "typeVersion": 1
    },
    {
      "id": "b4fa1e17-e992-49f6-9264-65a8e2569f47",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        272,
        -272
      ],
      "parameters": {
        "color": 5,
        "width": 416,
        "height": 624,
        "content": "## \ud83e\udd16 Gemini \u2014 AI Executive Summary\n\nGenerates a **3-paragraph executive summary** covering overall performance, audience behaviour, and actionable recommendations. It analyzes the full Week-over-Week dataset from all 14 GA4 nodes to spot trends.\n\n**Credential required:** Google Gemini API\nGet your key at: aistudio.google.com\n\n**Model:** gemini-3.1-flash-lite-preview\n\nIf Gemini fails or returns empty, the report still sends normally \u2014 the AI section is hidden from the email automatically."
      },
      "typeVersion": 1
    },
    {
      "id": "130b47cc-f010-46a4-b926-340d8237ec7f",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        704,
        -272
      ],
      "parameters": {
        "color": 4,
        "width": 432,
        "height": 624,
        "content": "## \ud83e\uddee Code Node \u2014 Data Processing + Email Builder\n\nDoes 4 things:\n1. Pulls data from all 14 GA4 nodes and Gemini by node name.\n2. Calculates week-over-week % changes for **every single section** (Overview, Pages, Sources, Events, Countries, Devices, New vs Returning).\n3. Builds the full HTML email with inline CSS and alignment fixes.\n4. Outputs subject, html, and recipients for the Email node.\n\n**To change recipients:**\nFind `recipients:` near the bottom of the code \u2192 update the email address.\nMultiple: `'email1@gmail.com,email2@gmail.com'`\n\n\n\n\n\n\n\n\n\n\n\n\n**To change client name in footer:**\nFind `AppStoneLab Technologies` in the HTML and replace with your client's brand name."
      },
      "typeVersion": 1
    },
    {
      "id": "8c2b8818-4b6f-460b-9896-5d13240bd3fb",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1152,
        -272
      ],
      "parameters": {
        "color": 2,
        "width": 288,
        "height": 624,
        "content": "## \ud83d\udce7 Send Email\n\nSends the HTML report to all recipients defined in the Code node.\n\n**Fields mapped from Code node output:**\n- To \u2192 {{ $json.recipients }}\n- Subject \u2192 {{ $json.subject }}\n- HTML Body \u2192 {{ $json.html }}\n\n**Credential required:** Gmail OAuth2 or SMTP\nFor SMTP: update host, port, and login credentials in the credential settings."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "6ee0e082-565e-46a8-b079-623ec18623df",
  "connections": {
    "GA4 - Top 5 Pages": {
      "main": [
        [
          {
            "node": "GA4 - Top 5 Pages Previous Week",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GA4 - Top 5 Events": {
      "main": [
        [
          {
            "node": "GA4 - Top 5 Events Previous Week",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate AI Summary": {
      "main": [
        [
          {
            "node": "Build Report & Email HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GA4 - Top 5 Countries": {
      "main": [
        [
          {
            "node": "GA4 - Top 5 Countries Previous Week",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GA4 - Top 5 Referrals": {
      "main": [
        [
          {
            "node": "GA4 - Top 5 Referrals Previous Week",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for All GA4 Data": {
      "main": [
        [
          {
            "node": "Generate AI Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Weekly Monday Trigger": {
      "main": [
        [
          {
            "node": "GA4 - Overview Current Week",
            "type": "main",
            "index": 0
          },
          {
            "node": "GA4 - Top 5 Pages",
            "type": "main",
            "index": 0
          },
          {
            "node": "GA4 - Top 5 Referrals",
            "type": "main",
            "index": 0
          },
          {
            "node": "GA4 - Top 5 Events",
            "type": "main",
            "index": 0
          },
          {
            "node": "GA4 - Top 5 Countries",
            "type": "main",
            "index": 0
          },
          {
            "node": "GA4 - Device Breakdown",
            "type": "main",
            "index": 0
          },
          {
            "node": "New vs Returning Users Breakdown",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GA4 - Device Breakdown": {
      "main": [
        [
          {
            "node": "GA4 - Device Breakdown Previous Week",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Report & Email HTML": {
      "main": [
        [
          {
            "node": "Send Weekly Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GA4 - Overview Current Week": {
      "main": [
        [
          {
            "node": "GA4 - Overview Previous Week",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GA4 - Overview Previous Week": {
      "main": [
        [
          {
            "node": "Wait for All GA4 Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GA4 - Top 5 Pages Previous Week": {
      "main": [
        [
          {
            "node": "Wait for All GA4 Data",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "GA4 - Top 5 Events Previous Week": {
      "main": [
        [
          {
            "node": "Wait for All GA4 Data",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "New vs Returning Users Breakdown": {
      "main": [
        [
          {
            "node": "GA4 - New vs Returning Previous Week",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GA4 - Top 5 Countries Previous Week": {
      "main": [
        [
          {
            "node": "Wait for All GA4 Data",
            "type": "main",
            "index": 4
          }
        ]
      ]
    },
    "GA4 - Top 5 Referrals Previous Week": {
      "main": [
        [
          {
            "node": "Wait for All GA4 Data",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "GA4 - Device Breakdown Previous Week": {
      "main": [
        [
          {
            "node": "Wait for All GA4 Data",
            "type": "main",
            "index": 5
          }
        ]
      ]
    },
    "GA4 - New vs Returning Previous Week": {
      "main": [
        [
          {
            "node": "Wait for All GA4 Data",
            "type": "main",
            "index": 6
          }
        ]
      ]
    }
  }
}