{ "name": "LGM — Weekly campaign recap with week-over-week AI highlights", "nodes": [ { "parameters": { "rule": { "interval": [ { "field": "weeks", "triggerAtDay": [1], "triggerAtHour": 8 } ] } }, "id": "schedule", "name": "Every Monday 8am", "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1.2, "position": [0, 260] }, { "parameters": { "resource": "campaign", "operation": "getAll", "returnAll": true, "status": "RUNNING" }, "id": "campaigns", "name": "Get running campaigns", "type": "@lagrowthmachine/n8n-nodes-lagrowthmachine.laGrowthMachine", "typeVersion": 1, "position": [240, 260], "credentials": { "laGrowthMachineApi": { "id": "REPLACE_ME", "name": "La Growth Machine account" } } }, { "parameters": { "resource": "campaign", "operation": "getStats", "campaignId": "={{ $json.id }}" }, "id": "stats", "name": "Get campaign stats", "type": "@lagrowthmachine/n8n-nodes-lagrowthmachine.laGrowthMachine", "typeVersion": 1, "position": [480, 260], "credentials": { "laGrowthMachineApi": { "id": "REPLACE_ME", "name": "La Growth Machine account" } } }, { "parameters": { "jsControls": "runOnceForAllItems", "language": "javaScript", "jsCode": "// Map campaign id -> name from the list step\nconst runs = $('Get running campaigns').all().map((x) => x.json);\nconst nameById = {};\nruns.forEach((c) => { nameById[c.id] = c.name; });\n\n// This week's snapshot\nconst weekOf = $now.startOf('week').toFormat('yyyy-MM-dd');\nconst snapshot = $input.all().map((x) => {\n const s = x.json;\n const r = s.replies || {};\n return {\n id: s.campaignId,\n name: nameById[s.campaignId] || s.campaignId,\n audienceSize: s.audienceSize || 0,\n contacted: r.contacted || 0,\n replied: r.replied || 0,\n replyRate: r.totalReplyPercent || 0,\n };\n});\n\n// Historize in n8n's local DB (workflow static data) and compare to last week\nconst sd = $getWorkflowStaticData('global');\nconst history = sd.history || [];\nconst prev = history.length ? history[history.length - 1] : null;\n\nconst deltas = snapshot.map((c) => {\n const p = prev && prev.campaigns.find((x) => x.id === c.id);\n return {\n name: c.name,\n contacted: c.contacted,\n replied: c.replied,\n replyRate: c.replyRate,\n dContacted: p ? c.contacted - p.contacted : null,\n dReplied: p ? c.replied - p.replied : null,\n dReplyRate: p ? Number((c.replyRate - p.replyRate).toFixed(1)) : null,\n };\n});\n\nhistory.push({ weekOf, campaigns: snapshot });\nsd.history = history.slice(-26); // keep ~6 months\n\nreturn [{ json: { weekOf, hasPrevious: Boolean(prev), previousWeekOf: prev ? prev.weekOf : null, campaignCount: snapshot.length, deltas } }];" }, "id": "compute", "name": "Historize + compute deltas", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [720, 260] }, { "parameters": { "modelId": { "__rl": true, "value": "gpt-4o", "mode": "list" }, "messages": { "values": [ { "role": "system", "content": "You are a RevOps analyst. Write a short weekly Slack update on active outreach campaigns. Lead with the biggest movers in reply rate and replies (up AND down). Use Slack markdown, bullet points, max ~8 lines. If there is no previous week, just summarise the current state." }, { "role": "user", "content": "=Week of {{ $json.weekOf }} (previous: {{ $json.previousWeekOf }}). {{ $json.campaignCount }} active campaigns.\nWeek-over-week deltas (dReplyRate in points, null = new/no history):\n{{ JSON.stringify($json.deltas) }}" } ] }, "options": {} }, "id": "ai", "name": "Highlight big changes (AI)", "type": "@n8n/n8n-nodes-langchain.openAi", "typeVersion": 1.8, "position": [960, 260], "credentials": { "openAiApi": { "id": "REPLACE_ME", "name": "OpenAI account" } } }, { "parameters": { "resource": "message", "operation": "post", "select": "channel", "channelId": { "__rl": true, "value": "REPLACE_CHANNEL", "mode": "id" }, "text": "=📊 *Weekly campaign recap — week of {{ $('Historize + compute deltas').item.json.weekOf }}*\n\n{{ $json.message.content }}", "otherOptions": {} }, "id": "slack", "name": "Post to Slack", "type": "n8n-nodes-base.slack", "typeVersion": 2.2, "position": [1200, 260], "credentials": { "slackApi": { "id": "REPLACE_ME", "name": "Slack account" } } }, { "parameters": { "content": "## Weekly recap + AI highlights\n\n1. Pull **running** campaigns (status filtered by the LGM node itself — no extra filter needed).\n2. Fetch each one's stats.\n3. **Historize + compute deltas** stores this week's numbers in n8n's local DB (workflow static data) and computes the week-over-week change per campaign.\n4. The **AI** node turns the deltas into a short digest that leads with the biggest movers.\n5. Posted to **Slack**.\n\n**Configure:** LGM + OpenAI + Slack credentials, the channel, the schedule.\n\n**Notes**\n- History lives in the workflow's static data. Prefer an auditable trail? Swap in *Google Sheets → Append* + *Get rows* instead.\n- Many running campaigns = many stats calls; mind the 50 req / 10 s rate limit.", "height": 360, "width": 460 }, "id": "note", "name": "Setup", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [0, -160] } ], "connections": { "Every Monday 8am": { "main": [[{ "node": "Get running campaigns", "type": "main", "index": 0 }]] }, "Get running campaigns": { "main": [[{ "node": "Get campaign stats", "type": "main", "index": 0 }]] }, "Get campaign stats": { "main": [[{ "node": "Historize + compute deltas", "type": "main", "index": 0 }]] }, "Historize + compute deltas": { "main": [[{ "node": "Highlight big changes (AI)", "type": "main", "index": 0 }]] }, "Highlight big changes (AI)": { "main": [[{ "node": "Post to Slack", "type": "main", "index": 0 }]] } }, "settings": { "executionOrder": "v1" }, "meta": { "templateCredsSetupCompleted": false } }