AutomationFlowsAI & RAG › Generate Financial Reports with AI Insights, Budget Analysis & Smart Alerts

Generate Financial Reports with AI Insights, Budget Analysis & Smart Alerts

ByJitesh Dugar @jiteshdugar on n8n.io

Transform month-end reporting from manual drudgery to automated intelligence - automatically pull P&L data from accounting systems, detect financial anomalies, generate AI-powered insights, calculate health scores, and distribute professional reports with conditional CFO review…

Cron / scheduled trigger★★★★☆ complexityAI-powered25 nodesHTTP RequestAgentOpenAI ChatGmailN8N Nodes HtmlcsstopdfGoogle Drive
AI & RAG Trigger: Cron / scheduled Nodes: 25 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #10677 — we link there as the canonical source.

This workflow follows the Agent → Gmail 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 →

Download .json
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "75e590c0-8032-41b7-b40b-434381e835f4",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3184,
        -944
      ],
      "parameters": {
        "width": 592,
        "height": 812,
        "content": "## How it works\n\nThis workflow automates month-end financial reporting with AI-powered insights. It runs automatically on the 1st of each month, pulls P&L data from your accounting system for current and previous periods, performs variance analysis, calculates a financial health score (0-100), and identifies anomalies (>20% revenue swings, >15% expense growth, >25% budget variances). AI generates an executive summary with top concerns and actionable recommendations. Critical reports (health <50 or 3+ anomalies) route through CFO approval. The system creates a comprehensive PDF with color-coded metrics, archives it to Drive, emails stakeholders, and sends context-aware Slack alerts.\n\n## Setup steps\n\n1. **Connect accounting system API** - Update URLs in both \"Fetch P&L\" nodes (QuickBooks/Xero/NetSuite)\n2. **Add authentication** - Configure API credentials for accounting system\n3. **Add OpenAI API key** - For AI financial insights (GPT-4 recommended)\n4. **Set thresholds** - Adjust anomaly detection limits in \"Analyze Financial Data\" if needed\n5. **Add HTML to PDF API** - Get key at htmlcsstoimage.com\n6. **Connect Google Drive** - Authenticate for report archival\n7. **Connect Gmail** - Authenticate for stakeholder emails and CFO approvals\n8. **Add Slack webhook** - Get URL from Slack for team notifications\n9. **Configure stakeholders** - Edit recipient list in \"Prepare Report Data\" node\n10. **Test manually first** - Use \"Execute Workflow\" before enabling monthly schedule\n\n\ud83d\udca1 **Required:** Accounting system API access with P&L data endpoints"
      },
      "typeVersion": 1
    },
    {
      "id": "ba296fdd-51e1-4c90-8ede-35ad61194683",
      "name": "Schedule Monthly",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -2144,
        16
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 1 * *"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "3d60a7b7-ab0d-4a3b-981e-61a06a9d0c51",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2192,
        -240
      ],
      "parameters": {
        "color": 7,
        "width": 772,
        "height": 480,
        "content": "## Scheduling & Data Collection\n\nRuns automatically 1st of month at 9 AM. Calculates previous month date range, pulls P&L data from accounting system for current and comparison periods, then merges datasets for analysis."
      },
      "typeVersion": 1
    },
    {
      "id": "4fadddfd-26a8-4abb-bb78-2f8f50e68889",
      "name": "Calculate Period",
      "type": "n8n-nodes-base.code",
      "position": [
        -1984,
        16
      ],
      "parameters": {
        "jsCode": "// Calculate reporting period (previous month)\nconst now = new Date();\nconst lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);\nconst startDate = new Date(lastMonth.getFullYear(), lastMonth.getMonth(), 1);\nconst endDate = new Date(lastMonth.getFullYear(), lastMonth.getMonth() + 1, 0);\n\n// Previous period for comparison\nconst prevStartDate = new Date(lastMonth.getFullYear(), lastMonth.getMonth() - 1, 1);\nconst prevEndDate = new Date(lastMonth.getFullYear(), lastMonth.getMonth(), 0);\n\nconst formatDate = (date) => date.toISOString().split('T')[0];\n\nreturn {\n  json: {\n    reportPeriod: {\n      startDate: formatDate(startDate),\n      endDate: formatDate(endDate),\n      month: lastMonth.toLocaleString('en-US', { month: 'long' }),\n      year: lastMonth.getFullYear()\n    },\n    previousPeriod: {\n      startDate: formatDate(prevStartDate),\n      endDate: formatDate(prevEndDate)\n    },\n    reportDate: new Date().toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "d1295137-29b7-4dc3-b287-29c1ccf1663a",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1360,
        -176
      ],
      "parameters": {
        "color": 7,
        "width": 772,
        "height": 560,
        "content": "## Financial Analysis & AI\n\nCalculates growth rates, identifies anomalies (revenue swings, expense increases, budget variances), computes 0-100 health score. AI generates executive summary with top concerns and actionable recommendations based on financial patterns."
      },
      "typeVersion": 1
    },
    {
      "id": "162844f9-0294-4706-ae80-b84509e3daec",
      "name": "Fetch Current P&L",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1760,
        -64
      ],
      "parameters": {
        "url": "https://api.youraccounting.com/reports/pl",
        "options": {},
        "sendQuery": true,
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "queryParameters": {
          "parameters": [
            {
              "name": "start_date",
              "value": "={{ $json.reportPeriod.startDate }}"
            },
            {
              "name": "end_date",
              "value": "={{ $json.reportPeriod.endDate }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "a0a5ca76-2740-4d96-889f-86d0f22aebe0",
      "name": "Fetch Previous P&L",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1760,
        112
      ],
      "parameters": {
        "url": "https://api.youraccounting.com/reports/pl",
        "options": {},
        "sendQuery": true,
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "queryParameters": {
          "parameters": [
            {
              "name": "start_date",
              "value": "={{ $json.previousPeriod.startDate }}"
            },
            {
              "name": "end_date",
              "value": "={{ $json.previousPeriod.endDate }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "348b6eea-74da-4384-a125-4d4ce6da20ab",
      "name": "Merge Data",
      "type": "n8n-nodes-base.merge",
      "position": [
        -1552,
        16
      ],
      "parameters": {
        "mode": "combine",
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "809d6f3f-469b-49ef-b664-6e99f4bf955f",
      "name": "Analyze Financial Data",
      "type": "n8n-nodes-base.code",
      "position": [
        -1328,
        16
      ],
      "parameters": {
        "jsCode": "// Process and analyze financial data\nconst item = $input.first().json;\n\n// Extract current period data\nconst current = item[0];\nconst previous = item[1];\n\n// Calculate key metrics\nconst revenue = parseFloat(current.revenue) || 0;\nconst expenses = parseFloat(current.expenses) || 0;\nconst netIncome = revenue - expenses;\n\nconst prevRevenue = parseFloat(previous.revenue) || 0;\nconst prevExpenses = parseFloat(previous.expenses) || 0;\nconst prevNetIncome = prevRevenue - prevExpenses;\n\n// Growth calculations\nconst revenueGrowth = prevRevenue > 0 ? ((revenue - prevRevenue) / prevRevenue * 100) : 0;\nconst expenseGrowth = prevExpenses > 0 ? ((expenses - prevExpenses) / prevExpenses * 100) : 0;\nconst incomeGrowth = prevNetIncome !== 0 ? ((netIncome - prevNetIncome) / Math.abs(prevNetIncome) * 100) : 0;\n\n// Process expense categories\nconst categories = (current.categories || []).map(cat => {\n  const actual = parseFloat(cat.actual) || 0;\n  const budget = parseFloat(cat.budget) || 0;\n  const variance = actual - budget;\n  const variancePercent = budget > 0 ? (variance / budget * 100) : 0;\n  \n  return {\n    name: cat.name,\n    actual,\n    budget,\n    variance,\n    variancePercent,\n    isOverBudget: variance > 0\n  };\n});\n\n// Identify anomalies\nconst anomalies = [];\n\nif (Math.abs(revenueGrowth) > 20) {\n  anomalies.push({\n    type: 'revenue',\n    severity: 'high',\n    message: `Revenue ${revenueGrowth > 0 ? 'increased' : 'decreased'} by ${Math.abs(revenueGrowth).toFixed(1)}% (>20% threshold)`\n  });\n}\n\nif (expenseGrowth > 15) {\n  anomalies.push({\n    type: 'expense',\n    severity: 'medium',\n    message: `Expenses increased by ${expenseGrowth.toFixed(1)}% (>15% threshold)`\n  });\n}\n\ncategories.forEach(cat => {\n  if (Math.abs(cat.variancePercent) > 25) {\n    anomalies.push({\n      type: 'budget_variance',\n      severity: 'medium',\n      message: `${cat.name}: ${Math.abs(cat.variancePercent).toFixed(1)}% ${cat.isOverBudget ? 'over' : 'under'} budget`\n    });\n  }\n});\n\n// Calculate health score (0-100)\nlet healthScore = 50;\nif (netIncome > 0) healthScore += 20;\nif (revenueGrowth > 0) healthScore += 15;\nif (expenseGrowth < 5) healthScore += 10;\nif (anomalies.length === 0) healthScore += 5;\n\nconst healthStatus = healthScore >= 80 ? 'Excellent' : \n                     healthScore >= 60 ? 'Good' : \n                     healthScore >= 40 ? 'Fair' : 'Concerning';\n\nreturn {\n  json: {\n    reportPeriod: item[0].reportPeriod || { startDate: '', endDate: '' },\n    revenue,\n    expenses,\n    netIncome,\n    previousPeriod: {\n      revenue: prevRevenue,\n      expenses: prevExpenses,\n      netIncome: prevNetIncome\n    },\n    revenueGrowth,\n    expenseGrowth,\n    incomeGrowth,\n    grossMargin: revenue > 0 ? ((revenue - expenses) / revenue * 100) : 0,\n    categories,\n    anomalies,\n    anomalyCount: anomalies.length,\n    healthScore,\n    healthStatus,\n    requiresReview: anomalies.length > 2 || healthScore < 50\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "c4fc1a69-1c4a-4d5e-a08d-062e1ef92f7e",
      "name": "Prepare AI Context",
      "type": "n8n-nodes-base.set",
      "position": [
        -1136,
        16
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "ai_context",
              "name": "aiContext",
              "type": "string",
              "value": "=Financial Performance Analysis:\n\nPeriod: {{ $json.reportPeriod.startDate }} to {{ $json.reportPeriod.endDate }}\n\nKey Metrics:\n- Revenue: ${{ $json.revenue }}\n- Expenses: ${{ $json.expenses }}\n- Net Income: ${{ $json.netIncome }}\n- Gross Margin: {{ $json.grossMargin }}%\n\nGrowth vs Previous Period:\n- Revenue Growth: {{ $json.revenueGrowth }}%\n- Expense Growth: {{ $json.expenseGrowth }}%\n- Income Growth: {{ $json.incomeGrowth }}%\n\nAnomalies Detected:\n{{ $json.anomalies.map(a => `- ${a.message}`).join('\\n') }}\n\nBudget Variances:\n{{ $json.categories.filter(c => Math.abs(c.variancePercent) > 10).map(c => `- ${c.name}: ${c.variancePercent.toFixed(1)}% ${c.isOverBudget ? 'over' : 'under'} budget`).join('\\n') }}\n\nHealth Score: {{ $json.healthScore }}/100 ({{ $json.healthStatus }})\n\nProvide:\n1. Executive summary (2-3 sentences)\n2. Top 3 insights or concerns\n3. 2-3 actionable recommendations\n\nRespond in JSON format:\n{\n  \"executiveSummary\": \"...\",\n  \"insights\": [\"...\", \"...\", \"...\"],\n  \"recommendations\": [\"...\", \"...\"]\n}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "d0cc42c4-d749-4788-88ab-bbd54aa5c6de",
      "name": "AI Financial Insights",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -992,
        16
      ],
      "parameters": {
        "text": "={{ $json.aiContext }}",
        "agent": "conversationalAgent",
        "options": {
          "systemMessage": "You are a CFO advisor analyzing financial reports. Provide concise, actionable insights. Always respond with valid JSON only."
        },
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "8280b6ba-dfdf-4616-9cad-7ef4c55b3480",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -1008,
        224
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4-turbo"
        },
        "options": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "52e07a1f-6391-4ba8-8947-482091a2de85",
      "name": "Prepare Report Data",
      "type": "n8n-nodes-base.code",
      "position": [
        -720,
        16
      ],
      "parameters": {
        "jsCode": "// Parse AI insights and prepare report data\nconst item = $input.first().json;\nconst analysisData = $('Analyze Financial Data').first().json;\n\n// Parse AI response\nlet aiInsights;\ntry {\n  const aiResponse = item.output || item.text || '{}';\n  const cleanResponse = aiResponse.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n  aiInsights = JSON.parse(cleanResponse);\n} catch (e) {\n  aiInsights = {\n    executiveSummary: 'Financial analysis complete. Review detailed metrics below.',\n    insights: ['Data successfully processed'],\n    recommendations: ['Continue monitoring key metrics']\n  };\n}\n\n// Company defaults\nconst companyData = {\n  companyName: 'Media Jade',\n  companyLogo: '',\n  reportTitle: 'Monthly Financial Report',\n  fiscalYear: new Date().getFullYear(),\n  preparedBy: 'Finance Department',\n  preparedByEmail: 'user@example.com',\n  currency: 'USD',\n  currencySymbol: '$',\n  stakeholders: [\n    { name: 'CEO', email: 'user@example.com' },\n    { name: 'CFO', email: 'user@example.com' }\n  ]\n};\n\n// Generate report ID\nconst date = new Date();\nconst reportId = `FIN-${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}-${Math.floor(Math.random() * 1000)}`;\n\n// Format dates\nconst formatDate = (dateStr) => {\n  return new Date(dateStr).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });\n};\n\nconst reportPeriod = analysisData.reportPeriod;\n\nreturn {\n  json: {\n    ...companyData,\n    ...analysisData,\n    aiInsights,\n    reportId,\n    reportDate: formatDate(new Date().toISOString()),\n    reportPeriod: {\n      ...reportPeriod,\n      startDateFormatted: formatDate(reportPeriod.startDate),\n      endDateFormatted: formatDate(reportPeriod.endDate)\n    }\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "16a34652-a2ad-4825-9e3d-922a2ba2f34b",
      "name": "Requires CFO Review?",
      "type": "n8n-nodes-base.if",
      "position": [
        -480,
        -96
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "anomaly_check",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.requiresReview }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "2e0787d2-c462-4cfa-83cb-cfca657a1770",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -240,
        112
      ],
      "parameters": {
        "color": 7,
        "width": 600,
        "height": 456,
        "content": "## Report Generation & Distribution\n\nCreates comprehensive PDF with health score banner, AI insights, anomaly alerts, P&L comparison, and budget variance analysis. Archives to Drive, then emails stakeholders (CEO, CFO, Board)."
      },
      "typeVersion": 1
    },
    {
      "id": "cabdec02-e1e2-41c6-b049-69b24cc3bc77",
      "name": "Request CFO Review",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -272,
        -96
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 2.1
    },
    {
      "id": "77a0a0a1-c49e-4541-b5aa-e8ea9e4a02fb",
      "name": "Generate Report HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        -208,
        256
      ],
      "parameters": {
        "jsCode": "// Generate comprehensive financial report HTML\nconst item = $input.first().json;\n\nconst formatCurrency = (amount) => {\n  return `${item.currencySymbol}${Math.abs(amount).toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2})}`;\n};\n\nconst formatPercent = (value) => {\n  return `${value >= 0 ? '+' : ''}${value.toFixed(1)}%`;\n};\n\nconst getVarianceClass = (variance) => {\n  if (variance > 0) return 'negative';\n  if (variance < 0) return 'positive';\n  return 'neutral';\n};\n\nconst getHealthColor = (score) => {\n  if (score >= 80) return 'success';\n  if (score >= 60) return 'warning';\n  return 'danger';\n};\n\nconst html = `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>${item.reportTitle} - ${item.reportId}</title>\n    <style>\n        @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');\n        * { margin: 0; padding: 0; box-sizing: border-box; }\n        body { font-family: 'Inter', sans-serif; line-height: 1.6; color: #1f2937; background: #fff; }\n        .container { max-width: 1000px; margin: 0 auto; padding: 40px; }\n        .header { text-align: center; margin-bottom: 40px; padding-bottom: 25px; border-bottom: 4px solid #3b82f6; }\n        .report-title { color: #1e40af; font-size: 32px; font-weight: 700; margin-bottom: 10px; }\n        .report-subtitle { color: #6b7280; font-size: 16px; margin-bottom: 8px; }\n        .report-period { color: #3b82f6; font-size: 18px; font-weight: 600; }\n        \n        .health-banner { padding: 25px; border-radius: 12px; margin-bottom: 30px; text-align: center; }\n        .health-banner.success { background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%); border: 3px solid #10b981; }\n        .health-banner.warning { background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); border: 3px solid #f59e0b; }\n        .health-banner.danger { background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%); border: 3px solid #ef4444; }\n        .health-score { font-size: 48px; font-weight: 700; margin-bottom: 10px; }\n        .health-status { font-size: 20px; font-weight: 600; }\n        \n        .metrics-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-bottom: 40px; }\n        .metric-card { background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%); padding: 25px; border-radius: 12px; border: 2px solid #93c5fd; text-align: center; }\n        .metric-card.success { background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%); border-color: #86efac; }\n        .metric-card.warning { background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); border-color: #fcd34d; }\n        .metric-card.danger { background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%); border-color: #fca5a5; }\n        .metric-label { font-size: 13px; color: #6b7280; font-weight: 600; text-transform: uppercase; margin-bottom: 10px; }\n        .metric-value { font-size: 32px; font-weight: 700; color: #1e40af; margin-bottom: 8px; }\n        .metric-change { font-size: 14px; font-weight: 600; }\n        .positive { color: #16a34a; }\n        .negative { color: #dc2626; }\n        .neutral { color: #6b7280; }\n        \n        .ai-insights { background: linear-gradient(135deg, #faf5ff 0%, #f3e8ff 100%); padding: 30px; border-radius: 12px; border-left: 6px solid #a855f7; margin-bottom: 40px; }\n        .ai-insights h2 { color: #7e22ce; font-size: 22px; font-weight: 700; margin-bottom: 20px; }\n        .ai-insights .summary { color: #6b21a8; font-size: 16px; line-height: 1.8; margin-bottom: 20px; padding: 15px; background: rgba(255,255,255,0.6); border-radius: 8px; }\n        .insight-section { margin-bottom: 20px; }\n        .insight-section h3 { color: #7e22ce; font-size: 16px; font-weight: 700; margin-bottom: 12px; }\n        .insight-section ul { padding-left: 25px; }\n        .insight-section li { color: #6b21a8; font-size: 14px; margin-bottom: 8px; line-height: 1.6; }\n        \n        .anomaly-section { background: #fef2f2; padding: 25px; border-radius: 12px; border-left: 6px solid #dc2626; margin-bottom: 40px; }\n        .anomaly-section h2 { color: #991b1b; font-size: 20px; font-weight: 700; margin-bottom: 15px; }\n        .anomaly-item { background: white; padding: 15px; border-radius: 8px; margin-bottom: 10px; border-left: 4px solid #ef4444; }\n        .anomaly-item .severity { display: inline-block; padding: 4px 12px; border-radius: 12px; font-size: 11px; font-weight: 700; color: white; margin-bottom: 8px; }\n        .anomaly-item .severity.high { background: #dc2626; }\n        .anomaly-item .severity.medium { background: #f59e0b; }\n        .anomaly-item .message { color: #991b1b; font-size: 14px; }\n        \n        .section { margin-bottom: 40px; }\n        .section-title { color: #1e40af; font-size: 22px; font-weight: 700; margin-bottom: 20px; padding-bottom: 12px; border-bottom: 3px solid #dbeafe; }\n        \n        .financial-table { width: 100%; border-collapse: separate; border-spacing: 0; margin-bottom: 30px; box-shadow: 0 4px 6px rgba(0,0,0,0.07); border-radius: 12px; overflow: hidden; }\n        .financial-table thead { background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); }\n        .financial-table th { padding: 16px; text-align: left; font-weight: 600; font-size: 13px; color: white; text-transform: uppercase; }\n        .financial-table tbody tr { background: white; }\n        .financial-table tbody tr:nth-child(even) { background: #f9fafb; }\n        .financial-table td { padding: 14px 16px; border-bottom: 1px solid #e5e7eb; font-size: 14px; color: #374151; }\n        .financial-table tbody tr:last-child td { border-bottom: none; }\n        .text-right { text-align: right; }\n        .text-bold { font-weight: 700; }\n        .total-row { background: #eff6ff !important; font-weight: 700; font-size: 16px; }\n        \n        .footer { margin-top: 50px; padding-top: 25px; border-top: 3px solid #e5e7eb; text-align: center; color: #6b7280; font-size: 13px; }\n        @media print { body { print-color-adjust: exact; -webkit-print-color-adjust: exact; } }\n    </style>\n</head>\n<body>\n    <div class=\"container\">\n        <div class=\"header\">\n            <h1 class=\"report-title\">${item.reportTitle}</h1>\n            <p class=\"report-subtitle\">${item.companyName}</p>\n            <p class=\"report-period\">${item.reportPeriod.startDateFormatted} - ${item.reportPeriod.endDateFormatted}</p>\n        </div>\n\n        <div class=\"health-banner ${getHealthColor(item.healthScore)}\">\n            <div class=\"health-score\">${item.healthScore}/100</div>\n            <div class=\"health-status\">Financial Health: ${item.healthStatus}</div>\n        </div>\n\n        <div class=\"metrics-grid\">\n            <div class=\"metric-card ${item.revenue >= item.previousPeriod.revenue ? 'success' : 'warning'}\">\n                <div class=\"metric-label\">Total Revenue</div>\n                <div class=\"metric-value\">${formatCurrency(item.revenue)}</div>\n                <div class=\"metric-change ${item.revenueGrowth >= 0 ? 'positive' : 'negative'}\">${formatPercent(item.revenueGrowth)} vs last month</div>\n            </div>\n            <div class=\"metric-card ${item.expenses <= item.previousPeriod.expenses ? 'success' : 'warning'}\">\n                <div class=\"metric-label\">Total Expenses</div>\n                <div class=\"metric-value\">${formatCurrency(item.expenses)}</div>\n                <div class=\"metric-change ${item.expenseGrowth <= 0 ? 'positive' : 'negative'}\">${formatPercent(item.expenseGrowth)} vs last month</div>\n            </div>\n            <div class=\"metric-card ${item.netIncome >= 0 ? 'success' : 'danger'}\">\n                <div class=\"metric-label\">Net Income</div>\n                <div class=\"metric-value\">${formatCurrency(item.netIncome)}</div>\n                <div class=\"metric-change ${item.incomeGrowth >= 0 ? 'positive' : 'negative'}\">${formatPercent(item.incomeGrowth)} vs last month</div>\n            </div>\n        </div>\n\n        ${item.aiInsights ? `\n        <div class=\"ai-insights\">\n            <h2>\ud83e\udd16 AI-Powered Executive Insights</h2>\n            <div class=\"summary\">${item.aiInsights.executiveSummary}</div>\n            \n            ${item.aiInsights.insights && item.aiInsights.insights.length > 0 ? `\n            <div class=\"insight-section\">\n                <h3>Key Insights</h3>\n                <ul>\n                    ${item.aiInsights.insights.map(insight => `<li>${insight}</li>`).join('')}\n                </ul>\n            </div>\n            ` : ''}\n            \n            ${item.aiInsights.recommendations && item.aiInsights.recommendations.length > 0 ? `\n            <div class=\"insight-section\">\n                <h3>Recommended Actions</h3>\n                <ul>\n                    ${item.aiInsights.recommendations.map(rec => `<li>${rec}</li>`).join('')}\n                </ul>\n            </div>\n            ` : ''}\n        </div>\n        ` : ''}\n\n        ${item.anomalies && item.anomalies.length > 0 ? `\n        <div class=\"anomaly-section\">\n            <h2>\u26a0\ufe0f Anomalies & Alerts (${item.anomalyCount})</h2>\n            ${item.anomalies.map(anomaly => `\n            <div class=\"anomaly-item\">\n                <span class=\"severity ${anomaly.severity}\">${anomaly.severity.toUpperCase()}</span>\n                <div class=\"message\">${anomaly.message}</div>\n            </div>\n            `).join('')}\n        </div>\n        ` : ''}\n\n        <div class=\"section\">\n            <h2 class=\"section-title\">Profit & Loss Statement</h2>\n            <table class=\"financial-table\">\n                <thead>\n                    <tr>\n                        <th>Item</th>\n                        <th class=\"text-right\">Current Period</th>\n                        <th class=\"text-right\">Previous Period</th>\n                        <th class=\"text-right\">Change</th>\n                    </tr>\n                </thead>\n                <tbody>\n                    <tr>\n                        <td class=\"text-bold\">Revenue</td>\n                        <td class=\"text-right text-bold\">${formatCurrency(item.revenue)}</td>\n                        <td class=\"text-right\">${formatCurrency(item.previousPeriod.revenue)}</td>\n                        <td class=\"text-right ${item.revenueGrowth >= 0 ? 'positive' : 'negative'}\">${formatPercent(item.revenueGrowth)}</td>\n                    </tr>\n                    <tr>\n                        <td class=\"text-bold\">Expenses</td>\n                        <td class=\"text-right text-bold\">${formatCurrency(item.expenses)}</td>\n                        <td class=\"text-right\">${formatCurrency(item.previousPeriod.expenses)}</td>\n                        <td class=\"text-right ${item.expenseGrowth <= 0 ? 'positive' : 'negative'}\">${formatPercent(item.expenseGrowth)}</td>\n                    </tr>\n                    <tr class=\"total-row\">\n                        <td class=\"text-bold\">Net Income</td>\n                        <td class=\"text-right text-bold\">${formatCurrency(item.netIncome)}</td>\n                        <td class=\"text-right\">${formatCurrency(item.previousPeriod.netIncome)}</td>\n                        <td class=\"text-right ${item.incomeGrowth >= 0 ? 'positive' : 'negative'}\">${formatPercent(item.incomeGrowth)}</td>\n                    </tr>\n                </tbody>\n            </table>\n        </div>\n\n        ${item.categories && item.categories.length > 0 ? `\n        <div class=\"section\">\n            <h2 class=\"section-title\">Budget vs Actual Analysis</h2>\n            <table class=\"financial-table\">\n                <thead>\n                    <tr>\n                        <th>Category</th>\n                        <th class=\"text-right\">Budget</th>\n                        <th class=\"text-right\">Actual</th>\n                        <th class=\"text-right\">Variance</th>\n                        <th class=\"text-right\">Variance %</th>\n                    </tr>\n                </thead>\n                <tbody>\n                    ${item.categories.map(cat => `\n                    <tr>\n                        <td>${cat.name}</td>\n                        <td class=\"text-right\">${formatCurrency(cat.budget)}</td>\n                        <td class=\"text-right\">${formatCurrency(cat.actual)}</td>\n                        <td class=\"text-right ${getVarianceClass(cat.variance)}\">${formatCurrency(cat.variance)}</td>\n                        <td class=\"text-right ${getVarianceClass(cat.variance)}\">${formatPercent(cat.variancePercent)}</td>\n                    </tr>\n                    `).join('')}\n                </tbody>\n            </table>\n        </div>\n        ` : ''}\n\n        <div class=\"footer\">\n            <p><strong>Report ID:</strong> ${item.reportId} | <strong>Generated:</strong> ${item.reportDate}</p>\n            <p><strong>Prepared by:</strong> ${item.preparedBy} (${item.preparedByEmail})</p>\n            <p style=\"margin-top: 15px;\">This report is confidential and intended for internal use only.</p>\n        </div>\n    </div>\n</body>\n</html>\n`;\n\nreturn {\n  json: {\n    html,\n    fileName: `Financial_Report_${item.reportId}_${item.reportPeriod.startDate.replace(/[^0-9]/g, '')}.pdf`,\n    ...item\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "69ba81c6-33ea-43d9-9730-ce61524a51cf",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -528,
        -304
      ],
      "parameters": {
        "color": 7,
        "width": 408,
        "height": 388,
        "content": "## Approval Routing\n\nRoutes critical reports (health <50 or 3+ anomalies) through CFO email approval before distribution. Normal reports auto-proceed to generation, ensuring executive oversight of concerning financials."
      },
      "typeVersion": 1
    },
    {
      "id": "bebab518-94f2-4a1c-b3b2-279974176934",
      "name": "HTML to PDF",
      "type": "n8n-nodes-htmlcsstopdf.htmlcsstopdf",
      "position": [
        -32,
        256
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "eaea604b-a54f-45c5-9b63-002f71d2a6ed",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        416,
        -144
      ],
      "parameters": {
        "color": 7,
        "width": 476,
        "height": 552,
        "content": "## Context-Aware Alerts\n\nRoutes Slack notifications based on health score. Critical alerts (health <60) get urgent format with immediate attention flag. Normal reports get standard summary notification with key metrics."
      },
      "typeVersion": 1
    },
    {
      "id": "d7b63343-be96-4c39-aa56-b3839fb6bfa7",
      "name": "Save to Google Drive",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        176,
        256
      ],
      "parameters": {
        "name": "={{ $('Generate Report HTML').item.json.fileName }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "list",
          "value": "root"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "0d310ef0-aaf4-4d5a-b2b8-4af8a23f4d5e",
      "name": "Send to Stakeholders",
      "type": "n8n-nodes-base.gmail",
      "position": [
        192,
        416
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 2.1
    },
    {
      "id": "5d705049-c3a6-4e0b-ada2-9a35354962d5",
      "name": "Health Critical?",
      "type": "n8n-nodes-base.if",
      "position": [
        448,
        128
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "health_check",
              "operator": {
                "type": "number",
                "operation": "lt"
              },
              "leftValue": "={{ $json.healthScore }}",
              "rightValue": "60"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "44edaca0-752e-4a5f-8367-b14d83ed806e",
      "name": "Alert - Critical",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        656,
        16
      ],
      "parameters": {
        "url": "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK",
        "options": {},
        "jsonBody": "={\n  \"text\": \"\u26a0\ufe0f CRITICAL: Financial Report Alert\",\n  \"blocks\": [\n    {\n      \"type\": \"section\",\n      \"text\": {\n        \"type\": \"mrkdwn\",\n        \"text\": \"*\u26a0\ufe0f CRITICAL Financial Alert*\\n\\n*Report:* {{ $('Generate Report HTML').item.json.reportId }}\\n*Period:* {{ $('Generate Report HTML').item.json.reportPeriod.startDateFormatted }} - {{ $('Generate Report HTML').item.json.reportPeriod.endDateFormatted }}\\n\\n*Health Score:* {{ $('Generate Report HTML').item.json.healthScore }}/100 ({{ $('Generate Report HTML').item.json.healthStatus }})\\n\\n*Key Metrics:*\\n\u2022 Revenue: {{ $('Generate Report HTML').item.json.currencySymbol }}{{ $('Generate Report HTML').item.json.revenue }} ({{ $('Generate Report HTML').item.json.revenueGrowth }}%)\\n\u2022 Net Income: {{ $('Generate Report HTML').item.json.currencySymbol }}{{ $('Generate Report HTML').item.json.netIncome }} ({{ $('Generate Report HTML').item.json.incomeGrowth }}%)\\n\\n*Anomalies Detected:* {{ $('Generate Report HTML').item.json.anomalyCount }}\\n\\n<{{ $('Save to Google Drive').item.json.webViewLink }}|View Full Report>\\n\\n\ud83d\udea8 *Immediate attention required*\"\n      }\n    }\n  ]\n}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.1
    },
    {
      "id": "81b5729a-368f-49cf-9e17-8bcd5999b309",
      "name": "Notify - Standard",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        656,
        224
      ],
      "parameters": {
        "url": "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK",
        "options": {},
        "jsonBody": "={\n  \"text\": \"\ud83d\udcca Monthly Financial Report Published\",\n  \"blocks\": [\n    {\n      \"type\": \"section\",\n      \"text\": {\n        \"type\": \"mrkdwn\",\n        \"text\": \"*\ud83d\udcca Financial Report Published*\\n\\n*Report:* {{ $('Generate Report HTML').item.json.reportId }}\\n*Period:* {{ $('Generate Report HTML').item.json.reportPeriod.startDateFormatted }} - {{ $('Generate Report HTML').item.json.reportPeriod.endDateFormatted }}\\n\\n*Health Score:* {{ $('Generate Report HTML').item.json.healthScore }}/100 ({{ $('Generate Report HTML').item.json.healthStatus }})\\n\\n*Financial Summary:*\\n\u2022 Revenue: {{ $('Generate Report HTML').item.json.currencySymbol }}{{ $('Generate Report HTML').item.json.revenue }} ({{ $('Generate Report HTML').item.json.revenueGrowth }}% MoM)\\n\u2022 Expenses: {{ $('Generate Report HTML').item.json.currencySymbol }}{{ $('Generate Report HTML').item.json.expenses }} ({{ $('Generate Report HTML').item.json.expenseGrowth }}% MoM)\\n\u2022 Net Income: {{ $('Generate Report HTML').item.json.currencySymbol }}{{ $('Generate Report HTML').item.json.netIncome }} ({{ $('Generate Report HTML').item.json.incomeGrowth }}% MoM)\\n\\n<{{ $('Save to Google Drive').item.json.webViewLink }}|View Report>\"\n      }\n    }\n  ]\n}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.1
    }
  ],
  "connections": {
    "Merge Data": {
      "main": [
        [
          {
            "node": "Analyze Financial Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTML to PDF": {
      "main": [
        [
          {
            "node": "Save to Google Drive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Period": {
      "main": [
        [
          {
            "node": "Fetch Current P&L",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Previous P&L",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Health Critical?": {
      "main": [
        [
          {
            "node": "Alert - Critical",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Notify - Standard",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Monthly": {
      "main": [
        [
          {
            "node": "Calculate Period",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Current P&L": {
      "main": [
        [
          {
            "node": "Merge Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Financial Insights",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Previous P&L": {
      "main": [
        [
          {
            "node": "Merge Data",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Prepare AI Context": {
      "main": [
        [
          {
            "node": "AI Financial Insights",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request CFO Review": {
      "main": [
        [
          {
            "node": "Generate Report HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Report Data": {
      "main": [
        [
          {
            "node": "Requires CFO Review?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Report HTML": {
      "main": [
        [
          {
            "node": "HTML to PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Requires CFO Review?": {
      "main": [
        [
          {
            "node": "Request CFO Review",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Generate Report HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save to Google Drive": {
      "main": [
        [
          {
            "node": "Send to Stakeholders",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send to Stakeholders": {
      "main": [
        [
          {
            "node": "Health Critical?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Financial Insights": {
      "main": [
        [
          {
            "node": "Prepare Report Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Financial Data": {
      "main": [
        [
          {
            "node": "Prepare AI Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Transform month-end reporting from manual drudgery to automated intelligence - automatically pull P&L data from accounting systems, detect financial anomalies, generate AI-powered insights, calculate health scores, and distribute professional reports with conditional CFO review…

Source: https://n8n.io/workflows/10677/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

This n8n automation workflow automates the creation, scripting, production, and posting of YouTube videos. It leverages AI (OpenAI), image generation (PIAPI), video rendering (Shotstack), and platform

Agent, OpenAI Chat, Airtable Tool +7
AI & RAG

The Multi-Model Agency Content Engine is a high-performance editorial system designed for agencies. It solves the "blank page" problem by alternating between real-world social proof and strategic expe

Google Sheets, Gmail, Google Drive +6
AI & RAG

This workflow automates the creation, rendering, approval, and posting of TikTok-style POV (Point of View) videos to Instagram, with cross-posting to Facebook and YouTube. It eliminates manual video p

OpenAI Chat, Output Parser Item List, HTTP Request +10
AI & RAG

This workflow automatically generates professional equity research reports for selected companies using financial data, market news, and AI analysis. It is designed for analysts, founders, and finance

Google Sheets, HTTP Request, OpenAI Chat +3
AI & RAG

Consolidates daily revenue from Stripe, PayPal, Shopify, and bank feeds into a single system. The workflow automatically normalizes data across payment sources, uses AI to categorize income transactio

Stripe, PayPal, Shopify +6