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 →
{
"name": "Schulung: Daten-Pipeline \u2192 E-Mail Report",
"nodes": [
{
"parameters": {},
"id": "trigger-1",
"name": "1. Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
200,
300
],
"notes": "SCHULUNG: Manueller Start fuer Demo-Zwecke. In Produktion durch Schedule Trigger (z.B. jeden 5. Werktag) oder Webhook ersetzen."
},
{
"parameters": {
"jsCode": "// ============================================================\n// SCHULUNG: Beispieldaten generieren\n// In Produktion wird dieser Node durch echte Datenquelle ersetzt\n// ============================================================\n\nconst guv_data = [\n { Kostenart: 'Umsatz Inland', Plan: 5200000, Ist: 5450000, Typ: 'Revenue' },\n { Kostenart: 'Umsatz Export', Plan: 3800000, Ist: 3620000, Typ: 'Revenue' },\n { Kostenart: 'Umsatz Gesamt', Plan: 9000000, Ist: 9070000, Typ: 'Revenue' },\n { Kostenart: 'Umsatz Marge %', Plan: 0.42, Ist: 0.41, Typ: 'Percentage' },\n { Kostenart: 'Materialkosten', Plan: 2700000, Ist: 2850000, Typ: 'Cost' },\n { Kostenart: 'Personalkosten', Plan: 3100000, Ist: 3050000, Typ: 'Cost' },\n { Kostenart: 'Sonstige Kosten', Plan: 800000, Ist: 920000, Typ: 'Cost' },\n { Kostenart: 'Kosten Marge %', Plan: 0.73, Ist: 0.75, Typ: 'Percentage' },\n { Kostenart: 'Abschreibungen', Plan: 450000, Ist: 440000, Typ: 'Cost' },\n { Kostenart: 'EBIT', Plan: 1950000, Ist: 1810000, Typ: 'Result' }\n];\n\nreturn guv_data.map(row => ({ json: row }));"
},
"id": "demo-data-1",
"name": "2. Demo-Daten erzeugen",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
420,
300
],
"notes": "SCHULUNG: Erzeugt Beispiel-GuV-Daten. Zeigt typische D365-Exportstruktur inkl. Prozentzeilen (die spaeter gefiltert werden muessen). Dieser Node entfaellt in Produktion."
},
{
"parameters": {
"jsCode": "// ============================================================\n// SCHULUNG: Datentransformation & Bereinigung\n// Kernlogik einer Controlling-Pipeline\n// ============================================================\n\nconst items = $input.all();\n\n// SCHRITT 1: Prozentzeilen ausfiltern\nconst filtered = items.filter(item => item.json.Typ !== 'Percentage');\n\n// SCHRITT 2: Abweichung berechnen\nconst enriched = filtered.map(item => {\n const d = item.json;\n let abweichung;\n \n // WICHTIG: Vorzeichenkonvention!\n // Revenue: Ist > Plan = positiv (gut)\n // Kosten: Ist > Plan = negativ (schlecht)\n if (d.Typ === 'Revenue' || d.Typ === 'Result') {\n abweichung = d.Ist - d.Plan;\n } else {\n abweichung = -(d.Ist - d.Plan);\n }\n \n const abweichung_pct = d.Plan !== 0 ? (abweichung / Math.abs(d.Plan)) * 100 : 0;\n \n let ampel;\n if (abweichung_pct >= 2) ampel = 'gruen';\n else if (abweichung_pct >= -2) ampel = 'gelb';\n else ampel = 'rot';\n \n return {\n json: {\n ...d,\n Abweichung_EUR: abweichung,\n Abweichung_Pct: Math.round(abweichung_pct * 10) / 10,\n Ampel: ampel\n }\n };\n});\n\nreturn enriched;"
},
"id": "transform-1",
"name": "3. Daten transformieren",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
660,
300
],
"notes": "SCHULUNG: Zentrale Transformationslogik.\n1. Prozentzeilen rausfiltern (typischer D365-Fallstrick)\n2. Abweichung berechnen mit korrekter Vorzeichenkonvention\n3. Ampelbewertung (Rot/Gelb/Gruen) hinzufuegen"
},
{
"parameters": {
"jsCode": "// ============================================================\n// SCHULUNG: HTML-Report generieren\n// Management-tauglicher E-Mail-Report\n// ============================================================\n\nconst items = $input.all();\nconst now = new Date();\nconst dateStr = now.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });\n\nconst ampelColor = { 'gruen': '#27ae60', 'gelb': '#f39c12', 'rot': '#e74c3c' };\n\nfunction fmtEUR(val) {\n return new Intl.NumberFormat('de-DE', { maximumFractionDigits: 0 }).format(val);\n}\nfunction fmtPct(val) {\n return val.toFixed(1).replace('.', ',');\n}\n\nlet tableRows = '';\nfor (const item of items) {\n const d = item.json;\n const color = ampelColor[d.Ampel] || '#95a5a6';\n const sign = d.Abweichung_EUR >= 0 ? '+' : '';\n tableRows += `<tr>\n <td style=\"padding:8px 12px;border-bottom:1px solid #eee;font-weight:${d.Typ==='Result'?'bold':'normal'}\">${d.Kostenart}</td>\n <td style=\"padding:8px 12px;border-bottom:1px solid #eee;text-align:right\">${fmtEUR(d.Plan)}</td>\n <td style=\"padding:8px 12px;border-bottom:1px solid #eee;text-align:right\">${fmtEUR(d.Ist)}</td>\n <td style=\"padding:8px 12px;border-bottom:1px solid #eee;text-align:right;color:${d.Abweichung_EUR>=0?'#27ae60':'#e74c3c'}\">${sign}${fmtEUR(d.Abweichung_EUR)}</td>\n <td style=\"padding:8px 12px;border-bottom:1px solid #eee;text-align:right\">${sign}${fmtPct(d.Abweichung_Pct)} %</td>\n <td style=\"padding:8px 12px;border-bottom:1px solid #eee;text-align:center\"><span style=\"display:inline-block;width:16px;height:16px;border-radius:50%;background:${color}\"></span></td>\n </tr>`;\n}\n\nconst ebit = items.find(i => i.json.Kostenart === 'EBIT');\nconst ebitAbw = ebit ? ebit.json.Abweichung_EUR : 0;\nconst ebitAmpel = ebit ? ebit.json.Ampel : 'gelb';\n\nconst html = `<!DOCTYPE html><html><head><meta charset=\"utf-8\"></head>\n<body style=\"font-family:'Segoe UI',Arial,sans-serif;color:#2c3e50;max-width:800px;margin:0 auto;padding:20px;\">\n <div style=\"background:linear-gradient(135deg,#2c3e50,#3498db);color:white;padding:24px 32px;border-radius:8px 8px 0 0;\">\n <h1 style=\"margin:0;font-size:22px;\">Monatlicher Controlling-Report</h1>\n <p style=\"margin:4px 0 0 0;opacity:0.85;font-size:14px;\">Automatisch erstellt am ${dateStr} | Unternehmen A</p>\n </div>\n <div style=\"background:#f8f9fa;padding:20px 32px;border-left:1px solid #e0e0e0;border-right:1px solid #e0e0e0;\">\n <h2 style=\"font-size:16px;margin:0 0 12px 0;\">Executive Summary</h2>\n <ul style=\"margin:0;padding-left:20px;line-height:1.8;\">\n <li>Umsatz leicht ueber Plan (+0,8%) \u2014 Inland kompensiert Exportrueckgang</li>\n <li>Materialkosten ueber Budget (+150 kEUR) \u2014 Preiseffekt Halbleiter</li>\n <li>Personalkosten unter Plan (-50 kEUR) \u2014 offene Stelle BU Engineering</li>\n <li><strong>EBIT ${fmtEUR(Math.abs(ebitAbw))} EUR ${ebitAbw>=0?'ueber':'unter'} Plan</strong> \u2014 Ampel: <span style=\"color:${ampelColor[ebitAmpel]};font-weight:bold;\">${ebitAmpel.toUpperCase()}</span></li>\n <li>Handlungsbedarf: Materialkostenentwicklung im naechsten RF adressieren</li>\n </ul>\n </div>\n <div style=\"padding:20px 32px;border:1px solid #e0e0e0;border-top:none;\">\n <h2 style=\"font-size:16px;margin:0 0 12px 0;\">Plan-Ist-Vergleich (kumuliert YTD)</h2>\n <table style=\"width:100%;border-collapse:collapse;font-size:14px;\">\n <thead><tr style=\"background:#2c3e50;color:white;\">\n <th style=\"padding:10px 12px;text-align:left;\">Position</th>\n <th style=\"padding:10px 12px;text-align:right;\">Plan (EUR)</th>\n <th style=\"padding:10px 12px;text-align:right;\">Ist (EUR)</th>\n <th style=\"padding:10px 12px;text-align:right;\">Abw. (EUR)</th>\n <th style=\"padding:10px 12px;text-align:right;\">Abw. (%)</th>\n <th style=\"padding:10px 12px;text-align:center;\">Status</th>\n </tr></thead>\n <tbody>${tableRows}</tbody>\n </table>\n </div>\n <div style=\"background:#f8f9fa;padding:16px 32px;border:1px solid #e0e0e0;border-top:none;border-radius:0 0 8px 8px;font-size:12px;color:#7f8c8d;\">\n <p style=\"margin:0;\">Automatisch generiert via n8n | Datenquelle: D365 (simuliert) | Schulungsbeispiel</p>\n </div>\n</body></html>`;\n\nreturn [{ json: { html_report: html, subject: `Controlling-Report ${dateStr} | Unternehmen A`, date: dateStr } }];"
},
"id": "report-gen-1",
"name": "4. HTML-Report generieren",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
900,
300
],
"notes": "SCHULUNG: Erzeugt Management-tauglichen HTML-Report mit Executive Summary, Plan-Ist-Tabelle und Ampelbewertung."
},
{
"parameters": {
"fromEmail": "controlling@unternehmen-a.de",
"toEmail": "={{ $json.toEmail || 'cfo@unternehmen-a.de' }}",
"subject": "={{ $json.subject }}",
"emailType": "html",
"html": "={{ $json.html_report }}",
"options": {}
},
"id": "email-send-1",
"name": "5. Report per E-Mail senden",
"type": "n8n-nodes-base.emailSend",
"typeVersion": 2.1,
"position": [
1140,
300
],
"notes": "SCHULUNG: Versendet HTML-Report per SMTP. Voraussetzung: SMTP-Credentials einrichten. Alternativen: Gmail, Outlook, SendGrid Node.",
"credentials": {}
}
],
"connections": {
"1. Manual Trigger": {
"main": [
[
{
"node": "2. Demo-Daten erzeugen",
"type": "main",
"index": 0
}
]
]
},
"2. Demo-Daten erzeugen": {
"main": [
[
{
"node": "3. Daten transformieren",
"type": "main",
"index": 0
}
]
]
},
"3. Daten transformieren": {
"main": [
[
{
"node": "4. HTML-Report generieren",
"type": "main",
"index": 0
}
]
]
},
"4. HTML-Report generieren": {
"main": [
[
{
"node": "5. Report per E-Mail senden",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"tags": [
{
"name": "Schulung"
},
{
"name": "Controlling"
},
{
"name": "Daten-Pipeline"
}
],
"meta": {
"templateCredsSetupCompleted": true
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Schulung: Daten-Pipeline → E-Mail Report. Uses emailSend. Event-driven trigger; 5 nodes.
Source: https://github.com/Michael-Braun72/Control4Insights_Code/blob/4cb3d18042a738e0c76f6324cb822dc3dafb2547/output/n8n_workflows/Schulung_Daten_Pipeline_Workflow.json — 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.
SwitchSubTask. Uses graphql, n8n-nodes-switch-nine-thousand, executeWorkflowTrigger, emailSend. Event-driven trigger; 30 nodes.
This workflow automatically handles errors in your n8n workflows by: Detecting when an error occurs and capturing the error details Sending an email notification with the error message and affected no
Perfect for disaster recovery or migrating between environments, this workflow automatically identifies your most recent FTP backup and provides a manual restore capability that intelligently excludes
Perfect for disaster recovery or migrating between environments, this workflow automatically identifies your most recent backup and provides a manual restore capability that intelligently excludes the
Find A New Book Recommendations. Uses manualTrigger, httpRequest, emailSend. Event-driven trigger; 13 nodes.