Awesome n8n templates: 280+ free workflows, organised by use case
A curated, continuously updated GitHub collection of free n8n templates spanning marketing, sales, content and operations automation.
The Automation Wing
An importable n8n flow that reads Meta account insights daily, compares yesterday against the trailing week and posts to Slack when spend swings beyond your threshold.
A budget doubling overnight is a Tuesday-morning problem if you catch it Tuesday morning, and a five-figure invoice if you catch it in the monthly report. The account itself already knows: yesterday's spend against the trailing week is a plain statistical comparison, no dashboard required. This flow runs that comparison every day and posts to Slack only when the number is genuinely unusual.
It reads two calls against the Graph API's /act_<id>/insights edge, one for yesterday and one for the trailing seven days broken out by day, works out how far yesterday sits from the weekly average, and fires a Slack alert when the swing clears your threshold. It is read-only against the ad account: no budget, campaign or ad set is touched.
ads_read, and the numeric ad account ID (the act_ prefix is added in the URL, keep it out of the config value).Authorization: Bearer {token}) on both HTTP Request nodes rather than pasting it into the URL.SIGMA, a standard-deviation trigger (2 is a reasonable start on accounts with steady daily spend), and PCT, a simple percentage-change backstop for accounts too new to have a stable trailing average.{
"name": "Meta Spend Anomaly to Slack",
"nodes": [
{
"parameters": {
"rule": { "interval": [ { "field": "days", "triggerAtHour": 8 } ] }
},
"id": "1",
"name": "Every Morning",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [220, 300]
},
{
"parameters": {
"values": {
"string": [
{ "name": "AD_ACCOUNT_ID", "value": "REPLACE_WITH_ACCOUNT_ID" },
{ "name": "SLACK_CHANNEL", "value": "#paid-social-alerts" }
],
"number": [
{ "name": "SIGMA", "value": 2 },
{ "name": "PCT", "value": 40 }
]
}
},
"id": "2",
"name": "Config",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [440, 300]
},
{
"parameters": {
"url": "=https://graph.facebook.com/v21.0/act_{{$json.AD_ACCOUNT_ID}}/insights",
"sendQuery": true,
"queryParameters": {
"parameters": [
{ "name": "level", "value": "account" },
{ "name": "date_preset", "value": "yesterday" },
{ "name": "fields", "value": "spend,impressions,cpm,ctr" }
]
},
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"options": {}
},
"id": "3",
"name": "Get Yesterday",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [660, 220]
},
{
"parameters": {
"url": "=https://graph.facebook.com/v21.0/act_{{$json.AD_ACCOUNT_ID}}/insights",
"sendQuery": true,
"queryParameters": {
"parameters": [
{ "name": "level", "value": "account" },
{ "name": "date_preset", "value": "last_7d" },
{ "name": "time_increment", "value": "1" },
{ "name": "fields", "value": "spend,impressions,cpm,ctr" }
]
},
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"options": {}
},
"id": "4",
"name": "Get Trailing 7 Days",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [660, 380]
},
{
"parameters": {
"jsCode": "const cfg = $('Config').first().json;\nconst yesterday = parseFloat(($('Get Yesterday').first().json.data || [{}])[0].spend || 0);\nconst days = ($('Get Trailing 7 Days').first().json.data || []).map(d => parseFloat(d.spend || 0));\nconst mean = days.length ? days.reduce((a, b) => a + b, 0) / days.length : 0;\nconst variance = days.length ? days.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / days.length : 0;\nconst stddev = Math.sqrt(variance);\nconst z = stddev > 0 ? (yesterday - mean) / stddev : 0;\nconst pctChange = mean > 0 ? ((yesterday - mean) / mean) * 100 : 0;\nconst fire = Math.abs(z) > cfg.SIGMA || Math.abs(pctChange) > cfg.PCT;\nreturn [{ json: { yesterday, mean, stddev, z, pctChange, fire } }];"
},
"id": "5",
"name": "Compute Deviation",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [900, 300]
},
{
"parameters": {
"conditions": {
"options": { "caseSensitive": true, "typeValidation": "strict" },
"conditions": [
{
"leftValue": "={{$json.fire}}",
"rightValue": true,
"operator": { "type": "boolean", "operation": "true", "singleValue": true }
}
],
"combinator": "and"
}
},
"id": "6",
"name": "Beyond Threshold?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [1120, 300]
},
{
"parameters": {
"channel": "={{$('Config').first().json.SLACK_CHANNEL}}",
"text": "=Spend anomaly on the account: yesterday {{$json.yesterday.toFixed(2)}} vs 7-day average {{$json.mean.toFixed(2)}} (z {{$json.z.toFixed(2)}}, {{$json.pctChange.toFixed(1)}}% change)",
"otherOptions": {}
},
"id": "7",
"name": "Post to Slack",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.2,
"position": [1340, 220],
"executeOnce": true
}
],
"connections": {
"Every Morning": { "main": [[{ "node": "Config", "type": "main", "index": 0 }]] },
"Config": { "main": [[{ "node": "Get Yesterday", "type": "main", "index": 0 }, { "node": "Get Trailing 7 Days", "type": "main", "index": 0 }]] },
"Get Yesterday": { "main": [[{ "node": "Compute Deviation", "type": "main", "index": 0 }]] },
"Get Trailing 7 Days": { "main": [[{ "node": "Compute Deviation", "type": "main", "index": 0 }]] },
"Compute Deviation": { "main": [[{ "node": "Beyond Threshold?", "type": "main", "index": 0 }]] },
"Beyond Threshold?": { "main": [[{ "node": "Post to Slack", "type": "main", "index": 0 }], []] }
},
"active": false,
"settings": { "executionOrder": "v1" }
}
AD_ACCOUNT_ID and SLACK_CHANNEL, and adjust SIGMA and PCT if the defaults do not suit the account's normal spend variance.The z-score answers "how unusual is this compared with the account's own recent behaviour", while the percentage change is a plain-English backstop for a newer account with too few days to have a meaningful standard deviation. Either one clearing its threshold fires the alert, so an account that is normally flat will trigger on a smaller absolute jump than a volatile one, which is the point: the comparison is relative to the account, not a fixed pound figure. "Post to Slack" runs once per execution regardless of how the underlying data is shaped, so a single anomaly produces a single message, never a flood.
One honest limitation: date_preset=yesterday and last_7d both use the account's reporting time zone and Meta's own attribution windows, so a very late-attributing conversion event can nudge the figures for a day or two after the fact. That is fine for a spend anomaly check, since spend itself is not subject to attribution lag, but do not repurpose this same flow for a ROAS or conversion anomaly without accounting for it.
More Like This
A curated, continuously updated GitHub collection of free n8n templates spanning marketing, sales, content and operations automation.
An importable n8n flow that watches your sitemap, spots new or changed URLs and pings IndexNow so Bing and Yandex hear about a page the moment it ships.
A weekly n8n workflow that pulls the last 28 days of Search Console clicks against the 28 before, writes the biggest query winners and losers to a Google Sheet, and emails the top movers.