We run a handful of social accounts on a schedule and kept finding out too late that one had nothing queued. This workflow checks every morning and only emails when something needs attention.
What it does: it reads every scheduled post in Blotato for the next 21 days, groups them by account, and flags any account whose last scheduled post is less than 3 days away (or that has nothing scheduled). One email lists the flagged accounts with their last post date. On a normal day nothing arrives.
How it’s built: a schedule trigger (daily at 7:05). A Code node, “Set: Your Accounts”, where you list your Blotato account IDs (and page IDs for Facebook pages), MIN_DAYS and LOOK_AHEAD_DAYS. An HTTP Request to Blotato’s posts endpoint with cursor pagination. A Code node, “Check: Days Lined Up”, that matches posts to accounts and works out the days of cover. Then an IF and Gmail.
Things we learned: Facebook pages and LinkedIn company pages sit under a parent account in Blotato, so you match on the page ID too, or every page reads as one account. And keep the email silent on good days. An alert that comes every morning stops getting read.
Workflow JSON below. Read-only, and no credentials are included.
{
“name”: “Get an email before any social account runs out of scheduled posts (Blotato)”,
“nodes”: [
{
“id”: “edd8e937-6a86-4993-a226-226c250b67fd”,
“name”: “Sticky Note”,
“type”: “n8n-nodes-base.stickyNote”,
“typeVersion”: 1,
“position”: [
-60,
-320
],
“parameters”: {
“width”: 620,
“height”: 260,
“content”: “## Never let an account go quiet\nEvery morning this reads everything you have scheduled in Blotato and emails you only when an account has less than 3 days of posts lined up (or nothing at all). No email means every account is covered.\n\nSet up (5 minutes): add your Blotato API credential to Read: Blotato Schedule, list your accounts and your time zone in Set: Your Accounts, connect Gmail and put your address in Email: Running Low.\n\nRead-only: it never changes a post.”
}
},
{
“id”: “80e29006-eea6-43ee-90fd-abd0fd637481”,
“name”: “Every Morning”,
“type”: “n8n-nodes-base.scheduleTrigger”,
“typeVersion”: 1.2,
“position”: [
0,
0
],
“parameters”: {
“rule”: {
“interval”: [
{
“field”: “days”,
“daysInterval”: 1,
“triggerAtHour”: 7,
“triggerAtMinute”: 5
}
]
}
}
},
{
“id”: “8720795a-6e17-49c5-9e20-7ba8e249cbdf”,
“name”: “Set: Your Accounts”,
“type”: “n8n-nodes-base.code”,
“typeVersion”: 2,
“position”: [
220,
0
],
“parameters”: {
“mode”: “runOnceForAllItems”,
“language”: “javaScript”,
“jsCode”: “// Your accounts: one line per Blotato account you want watched.\n// account_id: from Blotato (Settings, Accounts). page_id: only for Facebook pages and LinkedIn company pages.\nconst ACCOUNTS = [\n { label: ‘Instagram’, account_id: ‘00000’, page_id: ‘’ },\n { label: ‘YouTube’, account_id: ‘00000’, page_id: ‘’ },\n { label: ‘Facebook page’, account_id: ‘00000’, page_id: ‘000000000000000’ }\n];\nconst MIN_DAYS = 3; // warn when an account has less than this many days of posts lined up\nconst LOOK_AHEAD_DAYS = 21;\nconst TIME_ZONE = ‘America/New_York’; // times in the email use this zone\nreturn [{ json: { accounts: ACCOUNTS, min_days: MIN_DAYS, look_ahead_days: LOOK_AHEAD_DAYS, time_zone: TIME_ZONE, since: new Date().toISOString(), until: new Date(Date.now() + LOOK_AHEAD_DAYS * 864e5).toISOString() } }];\n”
}
},
{
“id”: “1e4c1dc0-08c0-4604-8da7-6d4783495324”,
“name”: “Read: Blotato Schedule”,
“type”: “n8n-nodes-base.httpRequest”,
“typeVersion”: 4.5,
“position”: [
440,
0
],
“parameters”: {
“method”: “GET”,
“url”: “=https://backend.blotato.com/v2/posts?status=scheduled&limit=250&since={{ encodeURIComponent($json.since) }}&until={{ encodeURIComponent($json.until) }}”,
“authentication”: “predefinedCredentialType”,
“nodeCredentialType”: “blotatoApi”,
“options”: {
“timeout”: 60000,
“pagination”: {
“pagination”: {
“paginationMode”: “updateAParameterInEachRequest”,
“parameters”: {
“parameters”: [
{
“type”: “qs”,
“name”: “cursor”,
“value”: “={{ $response.body.cursor }}”
}
]
},
“paginationCompleteWhen”: “other”,
“completeExpression”: “={{ !$response.body.cursor }}”,
“limitPagesFetched”: true,
“maxRequests”: 10,
“requestInterval”: 1100
}
}
}
},
“executeOnce”: true
},
{
“id”: “e816a2b0-d01d-4801-b1de-1454b5111442”,
“name”: “Check: Days Lined Up”,
“type”: “n8n-nodes-base.code”,
“typeVersion”: 2,
“position”: [
660,
0
],
“parameters”: {
“mode”: “runOnceForAllItems”,
“language”: “javaScript”,
“jsCode”: “// Finds every account whose last scheduled post is less than MIN_DAYS away (or has nothing scheduled).\nconst cfg = $(‘Set: Your Accounts’).first().json;\nconst pages = $input.all().map(i => i.json);\nif (pages.some(p => p.error || !Array.isArray(p.items))) throw new Error(‘Could not read the Blotato schedule: ’ + JSON.stringify(pages[0]).slice(0, 300));\nconst items = pages.flatMap(p => p.items).filter(p => p.state && p.state.type === ‘scheduled’ && p.account);\nconst now = Date.now();\nconst fmt = t => new Date(t).toLocaleString(‘en-US’, { weekday: ‘short’, month: ‘short’, day: ‘numeric’, hour: ‘numeric’, minute: ‘2-digit’, timeZone: cfg.time_zone || ‘UTC’ });\nconst rows = cfg.accounts.map(a => {\n const mine = items.filter(p => String(p.account.id) === String(a.account_id) && String((p.account.subaccount && p.account.subaccount.id) || ‘’) === String(a.page_id || ‘’));\n const last = mine.reduce((m, p) => Math.max(m, Date.parse(p.postTime)), 0);\n const days = last ? (last - now) / 864e5 : 0;\n return { label: a.label, count: mine.length, last: last ? fmt(last) : null, days: Math.round(days * 10) / 10, low: days < cfg.min_days };\n});\nconst low = rows.filter(r => r.low).sort((x, y) => x.days - y.days);\nconst body = low.length\n ? [${low.length} account(s) run out of scheduled posts within ${cfg.min_days} days:, …low.map(r => - ${r.label}: ${r.last ? last post ${r.last} (${r.days} days out, ${r.count} scheduled) : 'nothing scheduled'}), ‘’, ‘All accounts: ’ + rows.map(r => ${r.label} ${r.last ? (r.days >= (cfg.look_ahead_days || 21) - 0.5 ? (cfg.look_ahead_days || 21) + '+d' : r.days + 'd') : 'none'}).join(’; ‘)].join(’\n’)\n : ‘’;\nreturn [{ json: { any_low: low.length > 0, subject: low.length ? Running low on posts: ${low.map(r => r.label).join(', ')} : ‘’, body, rows } }];\n”
}
},
{
“id”: “63d11e9a-049d-4546-848a-fac0eeb51c9d”,
“name”: “If: Any Account Low”,
“type”: “n8n-nodes-base.if”,
“typeVersion”: 2.2,
“position”: [
880,
0
],
“parameters”: {
“conditions”: {
“options”: {
“caseSensitive”: true,
“leftValue”: “”,
“typeValidation”: “loose”
},
“conditions”: [
{
“leftValue”: “={{ $json.any_low }}”,
“operator”: {
“type”: “boolean”,
“operation”: “true”,
“singleValue”: true
}
}
],
“combinator”: “and”
},
“options”: {}
}
},
{
“id”: “07134f9f-fcc1-4c79-b73c-f38a9022d431”,
“name”: “Email: Running Low”,
“type”: “n8n-nodes-base.gmail”,
“typeVersion”: 2.1,
“position”: [
1100,
-80
],
“parameters”: {
“sendTo”: “you@example.com”,
“subject”: “={{ $json.subject }}”,
“emailType”: “text”,
“message”: “={{ $json.body }}”,
“options”: {}
}
}
],
“connections”: {
“Every Morning”: {
“main”: [
[
{
“node”: “Set: Your Accounts”,
“type”: “main”,
“index”: 0
}
]
]
},
“Set: Your Accounts”: {
“main”: [
[
{
“node”: “Read: Blotato Schedule”,
“type”: “main”,
“index”: 0
}
]
]
},
“Read: Blotato Schedule”: {
“main”: [
[
{
“node”: “Check: Days Lined Up”,
“type”: “main”,
“index”: 0
}
]
]
},
“Check: Days Lined Up”: {
“main”: [
[
{
“node”: “If: Any Account Low”,
“type”: “main”,
“index”: 0
}
]
]
},
“If: Any Account Low”: {
“main”: [
[
{
“node”: “Email: Running Low”,
“type”: “main”,
“index”: 0
}
],
]
}
},
“settings”: {
“executionOrder”: “v1”
}
}