This workflow corresponds to n8n.io template #17606 — we link there as the canonical source.
This workflow follows the Gmail → HTTP Request recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "7a15ac141a64e4c5",
"name": "Flag Meta Audience Network wasted spend by placement with Claude AI",
"tags": [],
"nodes": [
{
"id": "807edce162f21a3a",
"name": "Run manually",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-528,
128
],
"parameters": {},
"typeVersion": 1
},
{
"id": "cbb9283fc790854d",
"name": "Set config: results & AN thresholds",
"type": "n8n-nodes-base.code",
"position": [
-272,
128
],
"parameters": {
"jsCode": "// Audience Network leak detector - tune here.\n// RESULT_ACTIONS = every event that counts as a conversion (leads, registrations,\n// purchases); the workflow sums whichever are present, so it fits lead-gen and ecommerce.\nreturn [{ json: {\n DATE_PRESET: 'last_30d',\n SPEND_FLOOR: 50, // ignore Audience Network slices spending less than this\n CPA_MULTIPLE: 1.5, // flag AN when its cost-per-result is this many times the non-AN benchmark\n MIN_BENCH_RESULTS: 3, // need at least this many non-AN results before trusting the benchmark\n RESULT_ACTIONS: [\n 'offsite_complete_registration_add_meta_leads', 'lead', 'leadgen_grouped',\n 'onsite_conversion.lead_grouped', 'omni_complete_registration', 'complete_registration',\n 'omni_purchase', 'offsite_conversion.fb_pixel_purchase', 'purchase',\n ],\n} }];"
},
"typeVersion": 2
},
{
"id": "3201be4fb23de1df",
"name": "Fetch placement insights (Meta)",
"type": "n8n-nodes-base.httpRequest",
"position": [
80,
128
],
"parameters": {
"url": "=https://graph.facebook.com/{{ $env.META_API_VERSION || 'v21.0' }}/{{ $env.META_AD_ACCOUNT_ID }}/insights",
"options": {},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "access_token",
"value": "={{ $env.META_ACCESS_TOKEN }}"
},
{
"name": "level",
"value": "account"
},
{
"name": "date_preset",
"value": "={{ $('Set config: results & AN thresholds').first().json.DATE_PRESET }}"
},
{
"name": "breakdowns",
"value": "publisher_platform,platform_position"
},
{
"name": "fields",
"value": "spend,impressions,clicks,actions"
},
{
"name": "limit",
"value": "500"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "79c709a262eec7ef",
"name": "Flag Audience Network leaks",
"type": "n8n-nodes-base.code",
"position": [
576,
128
],
"parameters": {
"jsCode": "// Separate Audience Network slices from the rest, compute cost-per-result for each,\n// and flag AN spend that underperforms the non-AN benchmark or converts nothing.\nconst cfg = $('Set config: results & AN thresholds').first().json;\nconst RES = new Set(cfg.RESULT_ACTIONS);\nconst rows = $json.data || [];\nconst resultsOf = (actions) => Array.isArray(actions)\n ? actions.filter((a) => RES.has(a.action_type)).reduce((n, a) => n + (Number(a.value) || 0), 0)\n : 0;\n\nlet anSpend = 0, anResults = 0, restSpend = 0, restResults = 0;\nconst anSlices = [];\nfor (const r of rows) {\n const spend = Number(r.spend) || 0;\n const results = resultsOf(r.actions);\n if (r.publisher_platform === 'audience_network') {\n anSpend += spend; anResults += results;\n anSlices.push({\n placement: `${r.publisher_platform} / ${r.platform_position}`,\n spend: +spend.toFixed(2),\n results,\n cpa: results > 0 ? +(spend / results).toFixed(2) : null,\n });\n } else { restSpend += spend; restResults += results; }\n}\nconst benchCPA = (restResults >= cfg.MIN_BENCH_RESULTS && restResults > 0)\n ? +(restSpend / restResults).toFixed(2) : null;\n\nconst leaks = [];\nfor (const s of anSlices) {\n if (s.spend < cfg.SPEND_FLOOR) continue;\n const zeroResults = s.results === 0;\n const overBench = benchCPA != null && s.cpa != null && s.cpa >= benchCPA * cfg.CPA_MULTIPLE;\n if (!zeroResults && !overBench) continue;\n const recoverable = zeroResults ? s.spend : (s.spend - benchCPA * s.results);\n leaks.push({\n ...s,\n benchmark_cpa: benchCPA,\n reason: zeroResults ? 'spend with zero results' : `CPA $${s.cpa} vs benchmark $${benchCPA}`,\n dollars_recoverable: Math.max(0, +recoverable.toFixed(2)),\n });\n}\nleaks.sort((a, b) => b.dollars_recoverable - a.dollars_recoverable);\n\nreturn [{ json: {\n date_preset: cfg.DATE_PRESET,\n audience_network_totals: {\n spend: +anSpend.toFixed(2), results: anResults,\n cpa: anResults > 0 ? +(anSpend / anResults).toFixed(2) : null,\n },\n benchmark_cpa: benchCPA,\n leak_count: leaks.length,\n total_recoverable: +leaks.reduce((n, l) => n + l.dollars_recoverable, 0).toFixed(2),\n leaks,\n} }];"
},
"typeVersion": 2
},
{
"id": "90bf8bcccfa057f5",
"name": "Build AI action prompt",
"type": "n8n-nodes-base.code",
"position": [
816,
128
],
"parameters": {
"jsCode": "// Ask Claude to turn the Audience Network leak list into a prioritized action plan.\nconst d = $json;\nconst system = 'You are a senior paid-social analyst. You are given Audience Network (AN) delivery slices from a Meta ad account, judged against the account non-AN cost-per-result benchmark. For each slice give one concrete action: EXCLUDE Audience Network, KEEP (with why), or INVESTIGATE. Rank by dollars recoverable. Be terse. Return ONLY valid JSON, no prose, no markdown fences.';\nconst user = 'Non-AN benchmark CPA: $' + d.benchmark_cpa + '. Total recoverable: $' + d.total_recoverable + '.\\n\\nAudience Network leaks:\\n' + JSON.stringify(d.leaks) + '\\n\\nReturn JSON exactly: {\"summary\":\"one sentence\",\"ranked\":[{\"placement\":\"\",\"recoverable\":0,\"action\":\"exclude|keep|investigate\",\"why\":\"\"}]}';\nconst body = { model: 'claude-haiku-4-5', max_tokens: 3000, system, messages: [{ role: 'user', content: user }] };\nreturn [{ json: { body, _ctx: d } }];"
},
"typeVersion": 2
},
{
"id": "6a8c62f5eb81735e",
"name": "Rank Audience Network leaks with Claude AI",
"type": "n8n-nodes-base.httpRequest",
"position": [
1248,
128
],
"parameters": {
"url": "https://api.anthropic.com/v1/messages",
"method": "POST",
"options": {},
"jsonBody": "={{ $json.body }}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "x-api-key",
"value": "={{ $env.ANTHROPIC_API_KEY }}"
},
{
"name": "anthropic-version",
"value": "2023-06-01"
},
{
"name": "content-type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "de0499f28f833e1b",
"name": "Sticky Note - Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1600,
-304
],
"parameters": {
"width": 900,
"height": 664,
"content": "## Meta Audience Network Leak Detector\n\nFinds where the Meta Audience Network placement is quietly wasting your ad budget, and gives you an AI-ranked action plan. Works for lead-gen and ecommerce.\n\n### How it works\n- Pulls last-30-day account insights broken down by placement from the Meta Marketing API.\n- Separates Audience Network spend from your other placements and computes a non-AN cost-per-result benchmark.\n- Flags Audience Network slices spending over a floor with zero results, or a cost-per-result far above benchmark, and scores the dollars recoverable.\n- Claude ranks the leaks and recommends exclude, keep, or investigate.\n\n### Setup\n1. Add to your environment: META_ACCESS_TOKEN, META_AD_ACCOUNT_ID, META_API_VERSION, ANTHROPIC_API_KEY.\n2. Run with the manual trigger, or attach a Schedule trigger for a daily check.\n\n### Customization\nIn the Config node, set RESULT_ACTIONS to the events you optimize for, and tune SPEND_FLOOR and CPA_MULTIPLE. Swap the report node for a Slack or email node to auto-deliver.\n\nBuilt by **nocode.expert** - done-for-you automation & tracking. https://nocode.expert"
},
"typeVersion": 1
},
{
"id": "8eaaaaf5515fb2dc",
"name": "Sticky Note - Section 1",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
-16
],
"parameters": {
"color": 7,
"width": 424,
"height": 380,
"content": "## 1. Fetch & benchmark\nPull account insights split by placement; separate Audience Network from the rest."
},
"typeVersion": 1
},
{
"id": "455ba21d1d5a133d",
"name": "Sticky Note - Section 2",
"type": "n8n-nodes-base.stickyNote",
"position": [
480,
-16
],
"parameters": {
"color": 7,
"width": 520,
"height": 380,
"content": "## 2. Detect & rank\nFlag Audience Network slices below benchmark, then Claude ranks the fixes."
},
"typeVersion": 1
},
{
"id": "66f3aaaaacfa2d6c",
"name": "Sticky Note - Section 3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1152,
-16
],
"parameters": {
"color": 7,
"width": 760,
"height": 380,
"content": "## 3. Report\nPrint the leak report (swap for Slack/email)."
},
"typeVersion": 1
},
{
"id": "0cb0a6f7-28c6-4e08-8f97-b59b944ef5ac",
"name": "Send a message",
"type": "n8n-nodes-base.gmail",
"position": [
1696,
128
],
"parameters": {
"options": {}
},
"typeVersion": 2.2
},
{
"id": "af50137c660c5404",
"name": "Compile Audience Network leak report",
"type": "n8n-nodes-base.code",
"position": [
1488,
128
],
"parameters": {
"jsCode": "// Print a readable Audience Network leak report. Swap this node for Slack or email to auto-deliver.\nconst ctx = $('Build AI action prompt').first().json._ctx || {};\nfunction aiJson(fallback) {\n const t = ($json.content && $json.content[0] && $json.content[0].text) || '';\n let s = String(t).trim().replace(/^```(?:json)?/i, '').replace(/```$/, '').trim();\n try { return JSON.parse(s); } catch (e) {}\n const m = s.match(/\\{[\\s\\S]*\\}/);\n if (m) { try { return JSON.parse(m[0]); } catch (e) {} }\n return fallback;\n}\nconst ai = aiJson({ summary: 'AI response could not be parsed.', ranked: [] });\n\nconst lines = [];\nlines.push('AUDIENCE NETWORK LEAK REPORT (' + (ctx.date_preset || '') + ')');\nlines.push('Non-AN benchmark CPA: $' + ctx.benchmark_cpa);\nlines.push('Leaks: ' + (ctx.leak_count || 0) + ' | Recoverable: $' + (ctx.total_recoverable || 0));\nlines.push('');\nlines.push(ai.summary || '');\nlines.push('');\nfor (const r of (ai.ranked || [])) {\n lines.push('- [' + String(r.action || '').toUpperCase() + '] ' + r.placement + ' ($' + r.recoverable + ')');\n if (r.why) lines.push(' ' + r.why);\n}\nreturn [{ json: { report: lines.join('\\n'), leaks: ctx.leaks || [], ai } }];"
},
"typeVersion": 2
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "405d6eab-0f82-4bef-8b7a-752ae12ed219",
"nodeGroups": [],
"connections": {
"Run manually": {
"main": [
[
{
"node": "Set config: results & AN thresholds",
"type": "main",
"index": 0
}
]
]
},
"Build AI action prompt": {
"main": [
[
{
"node": "Rank Audience Network leaks with Claude AI",
"type": "main",
"index": 0
}
]
]
},
"Flag Audience Network leaks": {
"main": [
[
{
"node": "Build AI action prompt",
"type": "main",
"index": 0
}
]
]
},
"Fetch placement insights (Meta)": {
"main": [
[
{
"node": "Flag Audience Network leaks",
"type": "main",
"index": 0
}
]
]
},
"Set config: results & AN thresholds": {
"main": [
[
{
"node": "Fetch placement insights (Meta)",
"type": "main",
"index": 0
}
]
]
},
"Compile Audience Network leak report": {
"main": [
[
{
"node": "Send a message",
"type": "main",
"index": 0
}
]
]
},
"Rank Audience Network leaks with Claude AI": {
"main": [
[
{
"node": "Compile Audience Network leak report",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow pulls Meta Ads placement insights, flags underperforming Audience Network slices based on spend and CPA thresholds, uses Anthropic Claude to rank recommended actions, and compiles a plain-text leak report that is sent via Gmail. Runs manually to start the analysis.…
Source: https://n8n.io/workflows/17606/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Product Marketing Pack Generator (UAE). Uses formTrigger, httpRequest, googleDrive, gmail. Event-driven trigger; 24 nodes.
This sophisticated n8n automation is a powerful lead generation and outreach tool designed to transform YouTube channel research into actionable marketing opportunities. By intelligently connecting mu
This template enables fully automated lead enrichment using Surfe’s bulk API. Simply drop a Google Spreadsheet into your Google Drive, and n8n will handle everything — from reading the leads, enrichin
org-ai Dept Marketing. Uses executeWorkflowTrigger, httpRequest, gmail. Event-driven trigger; 9 nodes.
My workflow 11. Uses manualTrigger, splitOut, httpRequest, splitInBatches. Event-driven trigger; 14 nodes.