{
  "id": "DemoApparelWf006",
  "name": "06 · Weekly results report (Demo Apparel Co.)",
  "nodes": [
    {
      "parameters": {
        "content": "## 1 · Sales\nLast 7 days of Shopify orders, attributed by UTM and discount code; new vs repeat customer.",
        "height": 550,
        "width": 680,
        "color": 5
      },
      "id": "9e4d9a4b-f57b-4f9e-bb34-74e8233f53b0",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -50,
        50
      ]
    },
    {
      "parameters": {
        "content": "## 2 · Channel metrics\nKlaviyo campaign report + Instagram post insights, joined to stories via the Content calendar.",
        "height": 550,
        "width": 920,
        "color": 6
      },
      "id": "d275f77c-9e3a-4674-b0cf-b14a92fa1b38",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        670,
        50
      ]
    },
    {
      "parameters": {
        "content": "## 3 · Report\nClaude: 5-line summary + 3 next actions, emailed to the owner. Table appended to the Results sheet.",
        "height": 550,
        "width": 440,
        "color": 4
      },
      "id": "932b6e67-99c0-431c-b8ca-e33bee77f2ba",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1630,
        50
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 8
            }
          ]
        }
      },
      "id": "dd42cd8d-715b-4f58-a853-245d8b4a3927",
      "name": "Every Monday 08:00",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.4,
      "position": [
        0,
        300
      ]
    },
    {
      "parameters": {
        "authentication": "accessToken",
        "operation": "getAll",
        "returnAll": true,
        "options": {
          "createdAtMin": "={{ $now.minus({ days: 7 }).toISO() }}",
          "status": "any"
        }
      },
      "id": "aa8bc86d-1ad8-478b-96ee-cb80a28a28c4",
      "name": "Shopify: orders last 7 days",
      "type": "n8n-nodes-base.shopify",
      "typeVersion": 1,
      "position": [
        240,
        300
      ],
      "credentials": {
        "shopifyAccessTokenApi": {
          "id": "credShopify00001",
          "name": "Shopify (client)"
        }
      },
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "jsCode": "// Last-touch attribution: utm_campaign (= calendar source_id) in landing_site / referring_site,\n// otherwise the discount code. New vs repeat from the customer's order count.\nconst param = (url, key) => {\n  try { return new URL(String(url), 'https://shop.invalid').searchParams.get(key) || ''; } catch { return ''; }\n};\nconst bySource = {};\nlet orders = 0, revenue = 0;\nfor (const { json: o } of $input.all()) {\n  if (!o.id) continue; // empty item when there were no orders\n  let source = param(o.landing_site, 'utm_campaign') || param(o.referring_site, 'utm_campaign');\n  const code = (o.discount_codes || [])[0]?.code;\n  if (!source && code) source = 'code:' + code.toUpperCase();\n  if (!source) source = '(unattributed)';\n  const repeat = Number(o.customer?.orders_count || 1) > 1;\n  const total = Number(o.total_price || 0);\n  const s = (bySource[source] ??= { orders: 0, revenue: 0, repeat_purchases: 0, new_customers: 0 });\n  s.orders += 1;\n  s.revenue += total;\n  if (repeat) s.repeat_purchases += 1; else s.new_customers += 1;\n  orders += 1;\n  revenue += total;\n}\nreturn [{ json: { bySource, totals: { orders, revenue: Math.round(revenue * 100) / 100 } } }];"
      },
      "id": "c26ee610-241e-43d9-a1e4-7bf593189d50",
      "name": "Attribute orders to content",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://a.klaviyo.com/api/campaign-values-reports/",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "revision",
              "value": "2024-10-15"
            },
            {
              "name": "accept",
              "value": "application/vnd.api+json"
            },
            {
              "name": "content-type",
              "value": "application/vnd.api+json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ data: {\n  type: 'campaign-values-report',\n  attributes: {\n    statistics: ['opens', 'clicks', 'conversions', 'conversion_value'],\n    timeframe: { key: 'last_7_days' },\n    conversion_metric_id: 'REPLACE_WITH_PLACED_ORDER_METRIC_ID'\n  }\n} }) }}",
        "options": {}
      },
      "id": "6d0fae72-466f-4c84-b61b-8af2523ef353",
      "name": "Klaviyo: campaign metrics",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        720,
        300
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "credKlaviyo00001",
          "name": "Klaviyo private API key (client)"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "url": "https://graph.facebook.com/v23.0/REPLACE_WITH_IG_BUSINESS_ACCOUNT_ID/media",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "facebookGraphApi",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "fields",
              "value": "id,caption,permalink,timestamp,insights.metric(reach,saved,shares,total_interactions)"
            },
            {
              "name": "since",
              "value": "={{ $now.minus({ days: 7 }).toUnixInteger() }}"
            },
            {
              "name": "limit",
              "value": "50"
            }
          ]
        },
        "options": {}
      },
      "id": "fed2577e-91b7-413e-9478-25cb900ed887",
      "name": "Instagram: post insights",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        960,
        300
      ],
      "credentials": {
        "facebookGraphApi": {
          "id": "credMetaGraph001",
          "name": "Meta Graph API (client)"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/REPLACE_WITH_CONTENT_CALENDAR_SHEET_ID/edit"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Content calendar"
        },
        "options": {}
      },
      "id": "a8ee11eb-cdb3-42c1-92ec-1bebfacc6b55",
      "name": "Read Content calendar",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        1200,
        300
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "credGSheets00001",
          "name": "Google Sheets (client)"
        }
      },
      "executeOnce": true
    },
    {
      "parameters": {
        "jsCode": "// story -> clicks -> orders -> revenue -> repeat purchases\n// Calendar rows tie everything together: source_id (= utm_campaign), discount_code,\n// post_url / Instagram media, and klaviyo_campaign_ids written by workflows 4 and 5.\nconst { bySource } = $('Attribute orders to content').first().json;\nconst klaviyo = $('Klaviyo: campaign metrics').first().json.data?.attributes?.results || [];\nconst igPosts = $('Instagram: post insights').first().json.data || [];\nconst rows = $input.all().map((i) => i.json);\n\nconst table = {};\nconst story = (id, title) => (table[id] ??= { source_id: id, story: title || id, ig_reach: 0, ig_interactions: 0, email_clicks: 0, orders: 0, revenue: 0, repeat_purchases: 0, new_customers: 0 });\n\nfor (const r of rows) {\n  if (!r.source_id) continue;\n  const s = story(r.source_id, r.source);\n  for (const id of String(r.klaviyo_campaign_ids || '').split(',').map((x) => x.trim()).filter(Boolean)) {\n    const hit = klaviyo.find((k) => k.groupings?.campaign_id === id);\n    if (hit) s.email_clicks += Number(hit.statistics?.clicks || 0);\n  }\n  const post = igPosts.find((p) => p.permalink && p.permalink === r.post_url);\n  if (post) {\n    const m = Object.fromEntries((post.insights?.data || []).map((x) => [x.name, x.values?.[0]?.value || 0]));\n    s.ig_reach += Number(m.reach || 0);\n    s.ig_interactions += Number(m.total_interactions || 0);\n  }\n  if (r.discount_code && bySource['code:' + String(r.discount_code).toUpperCase()]) {\n    bySource[r.source_id] = bySource[r.source_id] || bySource['code:' + String(r.discount_code).toUpperCase()];\n  }\n}\nfor (const [src, o] of Object.entries(bySource)) {\n  if (src.startsWith('code:') && !table[src]) continue;\n  const s = story(src);\n  Object.assign(s, { orders: o.orders, revenue: Math.round(o.revenue * 100) / 100, repeat_purchases: o.repeat_purchases, new_customers: o.new_customers });\n}\n\nconst week = $today.minus({ days: 7 }).toISODate() + ' to ' + $today.minus({ days: 1 }).toISODate();\nreturn Object.values(table)\n  .filter((s) => s.orders || s.email_clicks || s.ig_reach)\n  .sort((a, b) => b.revenue - a.revenue)\n  .map((s) => ({ json: { week, ...s } }));"
      },
      "id": "3f4f4944-915c-41a2-a7c8-57927c93a710",
      "name": "Build results table",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1440,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({\n  model: 'claude-sonnet-5-5',\n  max_tokens: 2000,\n  system: 'You are the marketing analyst for Demo Apparel Co., a small apparel and lifestyle brand. Plain English, no jargon, no hype. Only use the numbers given; never invent figures.',\n  messages: [{ role: 'user', content:\n    'Results for ' + $json.week + ' (one row per story / content source):\\n' +\n    JSON.stringify($('Build results table').all().map(i => i.json), null, 1) +\n    '\\nTotals: ' + JSON.stringify($('Attribute orders to content').first().json.totals) +\n    '\\n\\nWrite exactly 5 short lines summarising what worked and what did not, then \"Next actions:\" with 3 numbered, concrete actions for next week.'\n  }]\n}) }}",
        "options": {
          "timeout": 180000
        }
      },
      "id": "4b0c2550-da82-499a-9329-b19cee63a56e",
      "name": "Summarise with Claude",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        1680,
        190
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "credAnthropic001",
          "name": "Anthropic API"
        }
      },
      "executeOnce": true
    },
    {
      "parameters": {
        "sendTo": "owner@example.com",
        "subject": "=Weekly results · {{ $('Build results table').first().json.week }}",
        "emailType": "text",
        "message": "={{ $json.content.find(b => b.type === 'text').text }}\n\nTop stories by revenue:\n{{ $('Build results table').all().slice(0, 5).map(i => '- ' + i.json.story + ': ' + i.json.orders + ' orders, ' + i.json.revenue + ' revenue, ' + i.json.repeat_purchases + ' repeat').join('\\n') }}\n\nFull table: Results sheet.",
        "options": {
          "appendAttribution": false
        }
      },
      "id": "7728b93a-b2c5-41f1-8cef-38cb3c2d9d21",
      "name": "Email summary to owner",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        1920,
        190
      ],
      "credentials": {
        "gmailOAuth2": {
          "id": "credGmail0000001",
          "name": "Gmail (client)"
        }
      }
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/REPLACE_WITH_RESULTS_SHEET_ID/edit"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Results"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": []
        },
        "options": {}
      },
      "id": "33a83c5e-0026-4457-8f17-aec6f1aa8f92",
      "name": "Append to Results sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        1680,
        410
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "credGSheets00001",
          "name": "Google Sheets (client)"
        }
      }
    }
  ],
  "connections": {
    "Every Monday 08:00": {
      "main": [
        [
          {
            "node": "Shopify: orders last 7 days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Shopify: orders last 7 days": {
      "main": [
        [
          {
            "node": "Attribute orders to content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Attribute orders to content": {
      "main": [
        [
          {
            "node": "Klaviyo: campaign metrics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Klaviyo: campaign metrics": {
      "main": [
        [
          {
            "node": "Instagram: post insights",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Instagram: post insights": {
      "main": [
        [
          {
            "node": "Read Content calendar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Content calendar": {
      "main": [
        [
          {
            "node": "Build results table",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build results table": {
      "main": [
        [
          {
            "node": "Summarise with Claude",
            "type": "main",
            "index": 0
          },
          {
            "node": "Append to Results sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Summarise with Claude": {
      "main": [
        [
          {
            "node": "Email summary to owner",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}
