AutomationFlowsSlack & Telegram › Custo AWS

Custo AWS

Custo AWS. Uses executeCommand, postgres, telegram. Scheduled trigger; 15 nodes.

Cron / scheduled trigger★★★★☆ complexity15 nodesExecute CommandPostgresTelegram
Slack & Telegram Trigger: Cron / scheduled Nodes: 15 Complexity: ★★★★☆ Added:

This workflow follows the Postgres → Telegram 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
{
  "name": "Custo AWS",
  "nodes": [
    {
      "parameters": {
        "jsCode": "// Calculate date range (yesterday)\nconst today = new Date();\nconst yesterday = new Date(today);\nyesterday.setDate(yesterday.getDate() - 1);\n\nconst startDate = yesterday.toISOString().split('T')[0];\nconst endDate = today.toISOString().split('T')[0];\n\nconsole.log('Date range:', startDate, 'to', endDate);\n\nreturn [{\n  json: {\n    start_date: startDate,\n    end_date: endDate,\n    timestamp: new Date().toISOString()\n  }\n}];"
      },
      "id": "b9da2034-b433-4bfb-ac52-6db4012ed1a7",
      "name": "Calculate Dates",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -400,
        800
      ]
    },
    {
      "parameters": {
        "command": "=aws ce get-cost-and-usage --time-period Start={{ $json.start_date }},End={{ $json.end_date }} --granularity DAILY --metrics UnblendedCost UsageQuantity --group-by Type=DIMENSION,Key=SERVICE Type=DIMENSION,Key=REGION --output json"
      },
      "id": "fa295a65-9162-4e51-adeb-933f249fdaf8",
      "name": "Fetch AWS Costs (CLI)",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        -176,
        800
      ]
    },
    {
      "parameters": {
        "jsCode": "const stdout = $input.first().json.stdout;\nconst awsResponse = JSON.parse(stdout);\n\nconst results = [];\n\nfor (const timeResult of awsResponse.ResultsByTime) {\n  const date = timeResult.TimePeriod.Start;\n  \n  for (const group of timeResult.Groups || []) {\n    const serviceName = group.Keys[0];\n    const region = group.Keys[1] || 'global';\n    const cost = parseFloat(group.Metrics.UnblendedCost.Amount);\n    const usage = parseFloat(group.Metrics.UsageQuantity?.Amount || 0);\n    \n    // Mudei para > 0 ou remova o if completamente\n    if (cost > 0 || usage > 0) {\n      results.push({\n        json: {\n          date: date,\n          service_name: serviceName,\n          region: region,\n          amount: cost,\n          usage_quantity: usage,\n          unit: group.Metrics.UnblendedCost.Unit\n        }\n      });\n    }\n  }\n}\n\nif (results.length === 0) {\n  return [];\n}\n\nreturn results;"
      },
      "id": "b448d2fa-8fa7-46c7-9bb7-dcbe66aa26ae",
      "name": "Parse CLI Output",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        48,
        800
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT \n  service_name,\n  SUM(amount) as total_cost\nFROM daily_costs\nWHERE date = CURRENT_DATE - INTERVAL '1 day'\nGROUP BY service_name\nORDER BY total_cost DESC\nLIMIT 5;",
        "options": {}
      },
      "id": "7b6c21c4-f014-4d4b-9957-d0d2146681ac",
      "name": "Top 5 Services",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.4,
      "position": [
        496,
        800
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT \n  SUM(amount) as mtd_total\nFROM daily_costs\nWHERE date >= DATE_TRUNC('month', CURRENT_DATE);",
        "options": {}
      },
      "id": "8109d16b-bbaf-41c5-b109-6b8c36420021",
      "name": "Month-to-Date Total",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.4,
      "position": [
        496,
        992
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Pegar todos os inputs do Merge\nconst allInputs = $input.all();\n\n// Identificar cada resultado pelos campos que retornam\nlet dailyTotal = { total_cost: 0, service_count: 0 };\nlet topServices = [];\nlet monthlyData = { mtd_total: 0 };\n\n// Processar cada input\nfor (const input of allInputs) {\n  const data = input.json;\n  \n  // Identificar Calculate Daily Total (tem date, total_cost, service_count)\n  if (data.date && data.total_cost !== undefined && data.service_count !== undefined) {\n    dailyTotal = data;\n  }\n  \n  // Identificar Month-to-Date (tem mtd_total)\n  if (data.mtd_total !== undefined) {\n    monthlyData = data;\n  }\n  \n  // Identificar Top 5 Services (tem service_name)\n  if (data.service_name && data.total_cost !== undefined) {\n    topServices.push(data);\n  }\n}\n\n// Pegar configura\u00e7\u00e3o\nconst config = $('Configuration1').item.json;\n\nconst dailyCost = parseFloat(dailyTotal.total_cost || 0);\nconst mtdCost = parseFloat(monthlyData.mtd_total || 0);\nconst budgetLimit = parseFloat(config.budget_limit_monthly);\nconst warningThreshold = parseFloat(config.budget_alert_threshold_warning);\nconst criticalThreshold = parseFloat(config.budget_alert_threshold_critical);\n\nconst budgetPercentage = (mtdCost / budgetLimit) * 100;\n\nlet alertLevel = 'normal';\nlet alertEmoji = '\ud83d\udcb0';\n\nif (budgetPercentage >= criticalThreshold) {\n  alertLevel = 'critical';\n  alertEmoji = '\ud83d\udea8';\n} else if (budgetPercentage >= warningThreshold) {\n  alertLevel = 'warning';\n  alertEmoji = '\u26a0\ufe0f';\n}\n\nlet topServicesText = '';\nif (topServices.length > 0) {\n  topServicesText = topServices.map((svc, idx) => {\n    const cost = parseFloat(svc.total_cost).toFixed(2);\n    return `${idx + 1}. ${svc.service_name}: $${cost}`;\n  }).join('\\n');\n} else {\n  topServicesText = 'No data available';\n}\n\nconst daysInMonth = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate();\nconst dayOfMonth = new Date().getDate();\nconst projectedCost = dayOfMonth > 0 ? (mtdCost / dayOfMonth) * daysInMonth : 0;\n\nreturn [{\n  json: {\n    daily_cost: dailyCost,\n    mtd_cost: mtdCost,\n    budget_limit: budgetLimit,\n    budget_percentage: budgetPercentage,\n    budget_remaining: budgetLimit - mtdCost,\n    projected_monthly_cost: projectedCost,\n    alert_level: alertLevel,\n    alert_emoji: alertEmoji,\n    top_services: topServicesText,\n    service_count: dailyTotal.service_count || 0,\n    date: new Date().toISOString().split('T')[0]\n  }\n}];"
      },
      "id": "88ff903a-f808-41b7-bf5b-a39036fd7e0c",
      "name": "Analyze Budget",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        800,
        640
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 1
          },
          "conditions": [
            {
              "leftValue": "={{ $json.alert_level }}",
              "rightValue": "normal",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "id": "0cf1ab3d-af4d-4037-8eb6-cf1c50fe0934"
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "f3204255-2d66-4521-9ce8-792a40dc30cf",
      "name": "Should Alert?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        976,
        736
      ]
    },
    {
      "parameters": {
        "jsCode": "const data = $json;\n\nlet msg = `${data.alert_emoji} *AWS Cost Report*\\n\\n`;\nmsg += `\ud83d\udcc5 *Daily Summary*\\n`;\nmsg += `Date: ${data.date}\\n`;\nmsg += `Cost: $${data.daily_cost.toFixed(2)}\\n`;\nmsg += `Services: ${data.service_count}\\n\\n`;\n\nmsg += `\ud83d\udcca *Monthly Budget*\\n`;\nmsg += `MTD: $${data.mtd_cost.toFixed(2)}\\n`;\nmsg += `Limit: $${data.budget_limit.toFixed(2)}\\n`;\nmsg += `Used: ${data.budget_percentage.toFixed(1)}%\\n`;\nmsg += `Remaining: $${data.budget_remaining.toFixed(2)}\\n\\n`;\n\nmsg += `\ud83d\udcc8 *Projection*\\n`;\nmsg += `Est. End: $${data.projected_monthly_cost.toFixed(2)}\\n`;\n\nif (data.projected_monthly_cost > data.budget_limit) {\n  const overrun = data.projected_monthly_cost - data.budget_limit;\n  msg += `\u26a0\ufe0f Overrun: $${overrun.toFixed(2)}\\n`;\n}\nmsg += `\\n`;\n\nmsg += `\ud83c\udfc6 *Top 5 Services*\\n`;\nmsg += data.top_services + '\\n\\n';\n\nif (data.alert_level === 'critical') {\n  msg += `\ud83d\udea8 *CRITICAL*: Budget >80%!\\n`;\n} else if (data.alert_level === 'warning') {\n  msg += `\u26a0\ufe0f *WARNING*: Budget >50%\\n`;\n}\n\nmsg += `\\n---\\n\ud83e\udd16 _AWS Cost Management_`;\n\nreturn [{ json: { ...data, telegram_message: msg } }];"
      },
      "id": "21e749c5-3271-49b8-bb03-c22a49068980",
      "name": "Format Message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1184,
        608
      ]
    },
    {
      "parameters": {
        "chatId": "={{ $env.ID_FIN_BOT }}",
        "text": "={{ $json.telegram_message }}",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "id": "10870833-510b-4946-b3fd-2b357d11ce3d",
      "name": "Send Telegram",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1360,
        720
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours"
            }
          ]
        }
      },
      "id": "598aed8f-8406-4db5-8f72-cc47e24d004e",
      "name": "Daily Cost Collection - 8AM1",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1,
      "position": [
        -848,
        800
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "CREATE TABLE IF NOT EXISTS daily_costs (\n  id SERIAL PRIMARY KEY,\n  date DATE NOT NULL,\n  service_name VARCHAR(100) NOT NULL,\n  region VARCHAR(50),\n  amount DECIMAL(10,2) NOT NULL,\n  usage_quantity DECIMAL(15,4),\n  unit VARCHAR(50),\n  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n  UNIQUE(date, service_name, region)\n);\n\nINSERT INTO daily_costs (date, service_name, region, amount, usage_quantity, unit)\nVALUES (\n  '{{ $json.date }}',\n  '{{ $json.service_name }}',\n  '{{ $json.region }}',\n  {{ $json.amount }},\n  {{ $json.usage_quantity }},\n  '{{ $json.unit }}'\n)\nON CONFLICT (date, service_name, region)\nDO UPDATE SET\n  amount = EXCLUDED.amount,\n  usage_quantity = EXCLUDED.usage_quantity,\n  created_at = CURRENT_TIMESTAMP;",
        "options": {}
      },
      "id": "2082894a-3743-4ec5-b3e0-b65412b606e1",
      "name": "Save to PostgreSQL1",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.4,
      "position": [
        272,
        800
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT \n  date,\n  SUM(amount) as total_cost,\n  COUNT(DISTINCT service_name) as service_count\nFROM daily_costs\nWHERE date = CURRENT_DATE - INTERVAL '1 day'\nGROUP BY date;",
        "options": {}
      },
      "id": "67607dcc-3a0e-434d-b868-beec7bd9fb58",
      "name": "Calculate Daily Total1",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.4,
      "position": [
        496,
        640
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "CREATE TABLE IF NOT EXISTS budget_alerts (\n  id SERIAL PRIMARY KEY,\n  date DATE NOT NULL,\n  budget_limit DECIMAL(10,2),\n  mtd_cost DECIMAL(10,2),\n  budget_percentage DECIMAL(5,2),\n  alert_level VARCHAR(20),\n  projected_monthly_cost DECIMAL(10,2),\n  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\n\nINSERT INTO budget_alerts (date, budget_limit, mtd_cost, budget_percentage, alert_level, projected_monthly_cost)\nVALUES ('{{ $json.date }}', {{ $json.budget_limit }}, {{ $json.mtd_cost }}, {{ $json.budget_percentage }}, '{{ $json.alert_level }}', {{ $json.projected_monthly_cost }});",
        "options": {}
      },
      "id": "03ecc107-4c1b-4163-b165-9d8f38f2e569",
      "name": "Save Alert History1",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.4,
      "position": [
        1360,
        880
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "fields": {
          "values": [
            {
              "name": "budget_limit_monthly",
              "stringValue": "10"
            },
            {
              "name": "budget_alert_threshold_warning",
              "stringValue": "50"
            },
            {
              "name": "budget_alert_threshold_critical",
              "stringValue": "80"
            },
            {
              "name": "telegram_chat_id",
              "stringValue": "={{ $env.ID_FIN_BOT }}"
            }
          ]
        },
        "options": {}
      },
      "id": "aa3d2603-2c98-468c-97fe-001d5fdeb3b2",
      "name": "Configuration1",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3,
      "position": [
        -592,
        784
      ]
    },
    {
      "parameters": {
        "numberInputs": 3
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        672,
        784
      ],
      "id": "30629398-a4f6-4623-8a10-6c51ef7c00b6",
      "name": "Merge"
    }
  ],
  "connections": {
    "Calculate Dates": {
      "main": [
        [
          {
            "node": "Fetch AWS Costs (CLI)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch AWS Costs (CLI)": {
      "main": [
        [
          {
            "node": "Parse CLI Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse CLI Output": {
      "main": [
        [
          {
            "node": "Save to PostgreSQL1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Budget": {
      "main": [
        [
          {
            "node": "Should Alert?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Should Alert?": {
      "main": [
        [
          {
            "node": "Format Message",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Format Message": {
      "main": [
        [
          {
            "node": "Send Telegram",
            "type": "main",
            "index": 0
          },
          {
            "node": "Save Alert History1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Daily Cost Collection - 8AM1": {
      "main": [
        [
          {
            "node": "Configuration1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save to PostgreSQL1": {
      "main": [
        [
          {
            "node": "Calculate Daily Total1",
            "type": "main",
            "index": 0
          },
          {
            "node": "Top 5 Services",
            "type": "main",
            "index": 0
          },
          {
            "node": "Month-to-Date Total",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Daily Total1": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configuration1": {
      "main": [
        [
          {
            "node": "Calculate Dates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Month-to-Date Total": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Top 5 Services": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Analyze Budget",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "ef3477e5-5663-4a0a-89a6-42ad2fcdb259",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "FY9kIZOJlGWDb2vi",
  "tags": []
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

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

About this workflow

Custo AWS. Uses executeCommand, postgres, telegram. Scheduled trigger; 15 nodes.

Source: https://github.com/Mfdemenezes/n8n-automations/blob/main/workflows/aws-cost-report/workflow.json — original creator credit. Request a take-down →

More Slack & Telegram workflows → · Browse all categories →

Related workflows

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

Slack & Telegram

03 - Command Handler. Uses executeWorkflowTrigger, telegram, executeCommand, postgres. Event-driven trigger; 53 nodes.

Execute Workflow Trigger, Telegram, Execute Command +2
Slack & Telegram

TelegramQuery. Uses httpRequest, dataTable, postgres, telegram. Scheduled trigger; 26 nodes.

HTTP Request, Data Table, Postgres +1
Slack & Telegram

Any external system triggers a reminder via webhook with a tenant token — the workflow validates the token, fetches the tenant's channel config and message template from PostgreSQL, renders the messag

Postgres, Telegram, Form Trigger +1
Slack & Telegram

Telegram Free Trial Handler. Uses postgres, executeCommand, telegram. Webhook trigger; 21 nodes.

Postgres, Execute Command, Telegram
Slack & Telegram

Telegram Free Trial Handler. Uses postgres, executeCommand, telegram. Webhook trigger; 21 nodes.

Postgres, Execute Command, Telegram