
{
"name": "Google Ads Daily Digest",
"nodes": [
{
"parameters": {
"jsCode": "// 1. INPUT DATEN LADEN\nconst inventoryRaw = $('Get Inventory').all().map(item => item.json);\nconst logsRaw = $('Get Logs').all().map(item => item.json);\n\n// 2. EINSTELLUNGEN\nconst now = new Date();\nconst msPerHour = 60 * 60 * 1000;\nconst msPerMinute = 60 * 1000;\nconst lookbackWindow = new Date(now.getTime() - (24 * msPerHour)); \nconst bufferMinutes = 30;\n\nconst formatTime = (dateObj) => {\n return dateObj.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });\n};\n\n// ---------------------------------------------------------\n// SCHRITT 1: DIE WIRKLICH LETZTEN ZEITSTEMPEL AUS DEN LOGS HOLEN\n// ---------------------------------------------------------\n// Wir erstellen ein Map, um für jedes Skript den neuesten Log-Eintrag zu finden\nconst realLastRuns = {};\n\nfor (const log of logsRaw) {\n const sName = log['Script Name'] || log['Script_Name'] || 'Unknown';\n const aID = log['Account ID'] || log['Account_ID'] || 'N/A';\n const logTime = new Date(log.Timestamp);\n \n if (isNaN(logTime.getTime())) continue;\n\n const key = `${sName}-${aID}`;\n \n // Wenn wir noch keinen Eintrag haben oder dieser Log-Eintrag neuer ist\n if (!realLastRuns[key] || logTime > realLastRuns[key]) {\n realLastRuns[key] = logTime;\n }\n}\n\n// ---------------------------------------------------------\n// SCHRITT 2: WATCHDOG (Prüfung mit abgeglichenen Daten)\n// ---------------------------------------------------------\nlet overdueScripts = [];\n\nfor (const script of inventoryRaw) {\n const isActive = String(script.Active).toUpperCase() === 'TRUE';\n if (!isActive) continue;\n\n const sName = script.Script_Name;\n const aID = script.Account_ID;\n const key = `${sName}-${aID}`;\n\n // WICHTIG: Wir nehmen den Zeitstempel aus dem Log, falls vorhanden. \n // Ansonsten den aus dem Inventory.\n let lastRunDate = new Date(script.Last_Run_Timestamp);\n if (realLastRuns[key] && realLastRuns[key] > lastRunDate) {\n lastRunDate = realLastRuns[key];\n }\n\n if (isNaN(lastRunDate.getTime())) continue;\n\n const freqHours = parseFloat(script.Expected_Frequency_Hours) || 24;\n const deadlineMs = lastRunDate.getTime() + (freqHours * msPerHour) + (bufferMinutes * msPerMinute);\n const deadline = new Date(deadlineMs);\n\n if (now > deadline) {\n const diffMs = now - deadline;\n overdueScripts.push({\n name: sName,\n account: aID,\n hoursLate: Math.floor(diffMs / msPerHour),\n minutesLate: Math.floor((diffMs % msPerHour) / msPerMinute)\n });\n }\n}\n\n// ---------------------------------------------------------\n// SCHRITT 3: LOGGING SUMMARY (Ergebnisse der letzten 24h)\n// ---------------------------------------------------------\nlet stats = { success: 0, error: 0, optimize: [], critical: [] };\n\nfor (const log of logsRaw) {\n const logTime = new Date(log.Timestamp);\n if (!isNaN(logTime.getTime()) && logTime >= lookbackWindow && logTime <= now) {\n const status = (log.Status || '').toUpperCase().trim();\n if (status === 'SUCCESS') stats.success++;\n else if (status === 'ERROR') stats.error++;\n else if (status === 'OPTIMIZE') stats.optimize.push(log);\n else if (status === 'CRITICAL') stats.critical.push(log);\n }\n}\n\n// ---------------------------------------------------------\n// SCHRITT 4: SLACK MESSAGE\n// ---------------------------------------------------------\nlet msg = `---\\n🌅 *Guten Morgen! Dein Google Ads Daily Digest*\\n_Zeitraum: Letzte 24 Stunden (bis ${formatTime(now)})_\\n\\n`;\n\nmsg += `💀 *Watchdog Alarm (Überfällige Skripte):*\\n`;\nif (overdueScripts.length === 0) {\n msg += `✅ Alle aktiven Skripte laufen nach Plan.\\n`;\n} else {\n overdueScripts.forEach(item => {\n msg += `- [Warnung] *${item.name}* (${item.account}) überfällig seit ${item.hoursLate} Std. ${item.minutesLate} Min.\\n`;\n });\n}\n\nmsg += `\\n📊 *Rückblick (Performance):*\\n`;\nmsg += `- ${stats.success}x SUCCESS\\n`;\nmsg += `- ${stats.error}x ERROR\\n`;\n\nif (stats.critical.length > 0 || stats.optimize.length > 0) {\n msg += `\\n🚀 *Handlung empfohlen (Highlights):*\\n`;\n const printHighlight = (h, prefix) => {\n const sName = h['Script Name'] || h['Script_Name'] || 'Unbekannt';\n const aID = h['Account ID'] || h['Account_ID'] || 'N/A';\n const message = h.Message || 'Keine Nachricht';\n msg += `${prefix} *${sName}* (${aID})\\n 👉 ${message}\\n`;\n };\n stats.critical.forEach(h => printHighlight(h, '🔴 *CRITICAL:*'));\n stats.optimize.forEach(h => printHighlight(h, '💡'));\n}\n\nmsg += `\\n---`;\n\nreturn [{ json: { slackMessage: msg } }];"
},
"id": "5f1b6594-6cf8-4be7-af80-bddb9a60d108",
"name": "Process Daily Digest",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
256,
0
]
},
{
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 7
}
]
}
},
"id": "2ef0fcb2-fe38-47e4-bf87-b018b98c92c9",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.1,
"position": [
-416,
0
]
},
{
"parameters": {
"authentication": "oAuth2",
"select": "user",
"user": {
"__rl": true,
"value": "DEINE_SLACK_USER_ID_HIER",
"mode": "list",
"cachedResultName": "dein.slack.username"
},
"text": "={{ $json.slackMessage }}",
"otherOptions": {
"mrkdwn": true
}
},
"id": "c096c585-6f14-45a0-8f8d-61f379348963",
"name": "Slack",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.1,
"position": [
480,
0
],
"webhookId": "DEINE_SLACK_WEBHOOK_ID_HIER",
"credentials": {
"slackOAuth2Api": {
"id": "DEINE_SLACK_CREDENTIAL_ID_HIER",
"name": "Slack account"
}
},
"notes": "Hier deine Credentials einfügen"
},
{
"parameters": {
"documentId": {
"__rl": true,
"value": "DEINE_SHEET_ID_HIER",
"mode": "list",
"cachedResultName": "Dein n8n Script Logging Sheet",
"cachedResultUrl": "<https://docs.google.com/spreadsheets/d/DEINE_SHEET_ID_HIER/edit?usp=drivesdk>"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Logging",
"cachedResultUrl": "<https://docs.google.com/spreadsheets/d/DEINE_SHEET_ID_HIER/edit#gid=0>"
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
-192,
96
],
"id": "2ed97d7b-b94b-4799-a4e4-77c5279890bd",
"name": "Get Logs",
"credentials": {
"googleSheetsOAuth2Api": {
"id": "DEINE_GSHEETS_CREDENTIAL_ID_HIER",
"name": "Google Sheets account"
}
}
},
{
"parameters": {
"documentId": {
"__rl": true,
"value": "DEINE_SHEET_ID_HIER",
"mode": "list",
"cachedResultName": "Dein n8n Script Logging Sheet",
"cachedResultUrl": "<https://docs.google.com/spreadsheets/d/DEINE_SHEET_ID_HIER/edit?usp=drivesdk>"
},
"sheetName": {
"__rl": true,
"value": 522954319,
"mode": "list",
"cachedResultName": "Inventory",
"cachedResultUrl": "<https://docs.google.com/spreadsheets/d/DEINE_SHEET_ID_HIER/edit#gid=522954319>"
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
-192,
-96
],
"id": "c539dfce-44aa-458a-aad9-850d878ae3e5",
"name": "Get Inventory",
"credentials": {
"googleSheetsOAuth2Api": {
"id": "DEINE_GSHEETS_CREDENTIAL_ID_HIER",
"name": "Google Sheets account"
}
}
},
{
"parameters": {},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [
32,
0
],
"id": "8db820ce-ba99-4c1a-8fa5-5c861b7e15de",
"name": "Merge"
}
],
"pinData": {},
"connections": {
"Schedule Trigger": {
"main": [
[
{
"node": "Get Inventory",
"type": "main",
"index": 0
},
{
"node": "Get Logs",
"type": "main",
"index": 0
}
]
]
},
"Process Daily Digest": {
"main": [
[
{
"node": "Slack",
"type": "main",
"index": 0
}
]
]
},
"Get Logs": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"Get Inventory": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Merge": {
"main": [
[
{
"node": "Process Daily Digest",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"availableInMCP": false
},
"versionId": "PLATZHALTER_VERSION_ID",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "DEINE_N8N_INSTANCE_ID_HIER"
},
"id": "PLATZHALTER_WORKFLOW_ID",
"tags": []
}
Beispiel Tabellen:
Get Inventory

Get Logs:
