DEV Community

Alex Kane
Alex Kane

Posted on

n8n for Telecom Companies: 5 Automations That Reduce Downtime and Improve Customer Experience (Free Workflow JSON)

If you work in telecom, you know the drill: outages hit at 2 AM, tickets pile up faster than the team can sort them, and the ops report is still being compiled manually in Excel at 9 PM.

Here are 5 n8n workflows that handle the most common telecom ops pain points automatically — with full JSON you can import and run today.


Why Telecom Teams Choose Self-Hosted n8n

  • Network and customer data never leaves your infrastructure — no traffic through Zapier or Make.com servers
  • Git-versionable JSON — every workflow change is tracked and auditable
  • Docker-native — drops into your existing infra alongside your NMS, CRM, and ticketing stack
  • Free to run — no per-execution pricing when you're processing thousands of alerts per day

Workflow 1: Network Outage Alert & Escalation

Problem: NOC misses outage webhooks. Manual escalation is too slow. By the time management hears about a P1, it's been down 45 minutes.

Trigger: Webhook (SolarWinds, Zabbix, PRTG, or custom monitoring)

What it does:

  • Classifies severity (CRITICAL / HIGH / MEDIUM / LOW) from NMS payload
  • Routes CRITICAL alerts to #noc-critical Slack channel immediately
  • Waits 15 minutes — if outage still unresolved, escalates to #noc-management
  • Non-critical alerts route to #noc-alerts for awareness
{
  "name": "Telecom - Network Outage Alert & Escalation",
  "nodes": [
    {"parameters": {"httpMethod": "POST", "path": "nms-outage", "responseMode": "responseNode"}, "id": "n1", "name": "Webhook - NMS Event", "type": "n8n-nodes-base.webhook", "position": [100, 200]},
    {"parameters": {"jsCode": "const e = $input.first().json;\nconst severity = (e.severity || '').toUpperCase();\nconst sev = ['CRITICAL','HIGH','MEDIUM','LOW'].includes(severity) ? severity : 'UNKNOWN';\nconst affected = e.affected_services || e.node || 'Unknown node';\nconst region = e.region || 'Unknown region';\nreturn [{ json: { ...e, sev, affected, region, ts: new Date().toISOString() } }];"}, "id": "n2", "name": "Code - Classify Severity", "type": "n8n-nodes-base.code", "position": [300, 200]},
    {"parameters": {"conditions": {"string": [{"value1": "={{$json.sev}}", "operation": "equal", "value2": "CRITICAL"}]}}, "id": "n3", "name": "IF - Critical?", "type": "n8n-nodes-base.if", "position": [500, 200]},
    {"parameters": {"channel": "#noc-critical", "text": "🔴 CRITICAL OUTAGE\nNode: {{$json.affected}}\nRegion: {{$json.region}}\nTime: {{$json.ts}}"}, "id": "n4", "name": "Slack - Critical", "type": "n8n-nodes-base.slack", "position": [700, 100]},
    {"parameters": {"amount": 15, "unit": "minutes"}, "id": "n5", "name": "Wait 15 Min", "type": "n8n-nodes-base.wait", "position": [900, 100]},
    {"parameters": {"channel": "#noc-management", "text": "⚠️ UNRESOLVED CRITICAL (15min+)\nNode: {{$json.affected}}\nRegion: {{$json.region}}\nOriginal alert: {{$json.ts}}"}, "id": "n6", "name": "Slack - Escalate", "type": "n8n-nodes-base.slack", "position": [1100, 100]},
    {"parameters": {"channel": "#noc-alerts", "text": "⚡ {{$json.sev}} Alert\nNode: {{$json.affected}}\nRegion: {{$json.region}}\nTime: {{$json.ts}}"}, "id": "n7", "name": "Slack - Alerts", "type": "n8n-nodes-base.slack", "position": [700, 300]}
  ],
  "connections": {
    "Webhook - NMS Event": {"main": [[{"node": "Code - Classify Severity", "type": "main", "index": 0}]]},
    "Code - Classify Severity": {"main": [[{"node": "IF - Critical?", "type": "main", "index": 0}]]},
    "IF - Critical?": {"main": [[{"node": "Slack - Critical", "type": "main", "index": 0}], [{"node": "Slack - Alerts", "type": "main", "index": 0}]]},
    "Slack - Critical": {"main": [[{"node": "Wait 15 Min", "type": "main", "index": 0}]]},
    "Wait 15 Min": {"main": [[{"node": "Slack - Escalate", "type": "main", "index": 0}]]}
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 2: Customer Service Ticket Auto-Triage

Problem: CSR team manually routes 80+ tickets/day to the right team. It's 30+ minutes of wasted time — and mistakes happen.

Trigger: Webhook (Zendesk, ServiceNow, Freshdesk, or your ticketing system)

What it does:

  • Keyword-classifies each ticket (outage / billing / performance / hardware / general)
  • Routes to the correct Slack channel instantly
  • Customers get faster resolution without manual sorting
{
  "name": "Telecom - CS Ticket Auto-Triage",
  "nodes": [
    {"parameters": {"httpMethod": "POST", "path": "telecom-ticket"}, "id": "t1", "name": "Webhook - New Ticket", "type": "n8n-nodes-base.webhook", "position": [100, 200]},
    {"parameters": {"jsCode": "const t = $input.first().json;\nconst text = ((t.subject || '') + ' ' + (t.description || '')).toLowerCase();\nlet category = 'general';\nif (/outage|no service|down|not working|dead zone/.test(text)) category = 'outage';\nelse if (/bill|invoice|charge|payment|refund|overcharge/.test(text)) category = 'billing';\nelse if (/slow|speed|latency|packet|ping|buffering/.test(text)) category = 'performance';\nelse if (/setup|install|device|modem|router|hardware|box/.test(text)) category = 'hardware';\nreturn [{ json: { ...t, category, ts: new Date().toISOString() } }];"}, "id": "t2", "name": "Code - Classify", "type": "n8n-nodes-base.code", "position": [300, 200]},
    {"parameters": {"rules": {"rules": [{"value2": "outage"}, {"value2": "billing"}, {"value2": "performance"}, {"value2": "hardware"}]}, "fallbackOutput": "extra"}, "id": "t3", "name": "Switch - Route", "type": "n8n-nodes-base.switch", "position": [500, 200]},
    {"parameters": {"channel": "#cs-outages", "text": "🔴 Outage ticket | {{$json.customer_name}} | #{{$json.id}}\n{{$json.subject}}"}, "id": "t4", "name": "Slack #cs-outages", "type": "n8n-nodes-base.slack", "position": [700, 50]},
    {"parameters": {"channel": "#cs-billing", "text": "💳 Billing ticket | {{$json.customer_name}} | #{{$json.id}}\n{{$json.subject}}"}, "id": "t5", "name": "Slack #cs-billing", "type": "n8n-nodes-base.slack", "position": [700, 150]},
    {"parameters": {"channel": "#cs-performance", "text": "📶 Performance ticket | {{$json.customer_name}} | #{{$json.id}}\n{{$json.subject}}"}, "id": "t6", "name": "Slack #cs-perf", "type": "n8n-nodes-base.slack", "position": [700, 250]},
    {"parameters": {"channel": "#cs-hardware", "text": "🔧 Hardware ticket | {{$json.customer_name}} | #{{$json.id}}\n{{$json.subject}}"}, "id": "t7", "name": "Slack #cs-hardware", "type": "n8n-nodes-base.slack", "position": [700, 350]}
  ],
  "connections": {
    "Webhook - New Ticket": {"main": [[{"node": "Code - Classify", "type": "main", "index": 0}]]},
    "Code - Classify": {"main": [[{"node": "Switch - Route", "type": "main", "index": 0}]]},
    "Switch - Route": {"main": [[{"node": "Slack #cs-outages", "type": "main", "index": 0}],[{"node": "Slack #cs-billing", "type": "main", "index": 0}],[{"node": "Slack #cs-perf", "type": "main", "index": 0}],[{"node": "Slack #cs-hardware", "type": "main", "index": 0}]]}
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 3: SLA Breach Monitor

Problem: Tickets breach SLA silently. Manager finds out at the daily standup — too late to fix CSAT.

Trigger: Schedule every 15 minutes

What it does:

  • Reads open tickets from Google Sheets (or your ticketing system API)
  • Calculates time open vs SLA threshold by priority (P1: 1h, P2: 4h, P3: 24h, P4: 72h)
  • Fires Slack alert for each breached ticket with overage in minutes
{
  "name": "Telecom - SLA Breach Monitor",
  "nodes": [
    {"parameters": {"rule": {"interval": [{"field": "minutes", "minutesInterval": 15}]}}, "id": "s1", "name": "Schedule - Every 15 Min", "type": "n8n-nodes-base.scheduleTrigger", "position": [100, 200]},
    {"parameters": {"documentId": "YOUR_SHEET_ID", "sheetName": "ActiveTickets"}, "id": "s2", "name": "Sheets - Active Tickets", "type": "n8n-nodes-base.googleSheets", "position": [300, 200]},
    {"parameters": {"jsCode": "const now = Date.now();\nconst SLA = { P1: 60, P2: 240, P3: 1440, P4: 4320 };\nconst breached = [];\nfor (const t of $input.all()) {\n  const d = t.json;\n  const sla = SLA[d.priority] || 1440;\n  const open_min = (now - new Date(d.created_at).getTime()) / 60000;\n  const overage = Math.round(open_min - sla);\n  if (overage > 0) breached.push({ ...d, sla_minutes: sla, open_minutes: Math.round(open_min), overage_minutes: overage });\n}\nif (!breached.length) return [{ json: { has_breaches: false } }];\nreturn breached.map(b => ({ json: { ...b, has_breaches: true } }));"}, "id": "s3", "name": "Code - Find Breaches", "type": "n8n-nodes-base.code", "position": [500, 200]},
    {"parameters": {"conditions": {"boolean": [{"value1": "={{$json.has_breaches}}", "value2": true}]}}, "id": "s4", "name": "IF - Has Breaches", "type": "n8n-nodes-base.if", "position": [700, 200]},
    {"parameters": {"channel": "#cs-sla-alerts", "text": "⏰ SLA BREACH — Ticket #{{$json.id}} ({{$json.priority}})\nCustomer: {{$json.customer_name}}\nOpen: {{$json.open_minutes}} min | SLA: {{$json.sla_minutes}} min | Overage: {{$json.overage_minutes}} min\nAgent: {{$json.assigned_to}}"}, "id": "s5", "name": "Slack - SLA Alert", "type": "n8n-nodes-base.slack", "position": [900, 100]}
  ],
  "connections": {
    "Schedule - Every 15 Min": {"main": [[{"node": "Sheets - Active Tickets", "type": "main", "index": 0}]]},
    "Sheets - Active Tickets": {"main": [[{"node": "Code - Find Breaches", "type": "main", "index": 0}]]},
    "Code - Find Breaches": {"main": [[{"node": "IF - Has Breaches", "type": "main", "index": 0}]]},
    "IF - Has Breaches": {"main": [[{"node": "Slack - SLA Alert", "type": "main", "index": 0}]]}
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 4: Daily Telecom Ops Report

Problem: Head of ops assembles the daily KPI email manually from 3 spreadsheets. Takes 30+ minutes every evening.

Trigger: Weekdays at 6 PM

What it does:

  • Pulls today's ticket and ops data from Sheets
  • Calculates: total tickets, resolution rate, SLA compliance %, outage count
  • Sends a formatted HTML email to the ops manager automatically
{
  "name": "Telecom - Daily Ops Report",
  "nodes": [
    {"parameters": {"rule": {"interval": [{"field": "cronExpression", "expression": "0 18 * * 1-5"}]}}, "id": "r1", "name": "Schedule - 6PM Weekdays", "type": "n8n-nodes-base.scheduleTrigger", "position": [100, 200]},
    {"parameters": {"documentId": "YOUR_SHEET_ID", "sheetName": "DailyOps"}, "id": "r2", "name": "Sheets - Ops Data", "type": "n8n-nodes-base.googleSheets", "position": [300, 200]},
    {"parameters": {"jsCode": "const rows = $input.all().map(r => r.json);\nconst today = new Date().toISOString().slice(0,10);\nconst t = rows.filter(r => r.date === today);\nconst total = t.length;\nconst resolved = t.filter(r => r.status === 'resolved').length;\nconst breached = t.filter(r => r.sla_breached === 'true').length;\nconst outages = t.filter(r => r.category === 'outage').length;\nconst resRate = total ? Math.round(resolved/total*100) : 0;\nconst slaRate = total ? Math.round((total-breached)/total*100) : 100;\nconst html = `<h2>Telecom Daily Ops — ${today}</h2><table border='1' cellpadding='6'><tr><th>Metric</th><th>Today</th></tr><tr><td>Total Tickets</td><td>${total}</td></tr><tr><td>Resolved</td><td>${resolved} (${resRate}%)</td></tr><tr><td>SLA Compliance</td><td>${slaRate}%</td></tr><tr><td>Outage Tickets</td><td>${outages}</td></tr></table>`;\nreturn [{ json: { html, total, resolved, slaRate, outages, today } }];"}, "id": "r3", "name": "Code - Build Report", "type": "n8n-nodes-base.code", "position": [500, 200]},
    {"parameters": {"fromEmail": "ops@yourcompany.com", "toEmail": "headofops@yourcompany.com", "subject": "Telecom Ops Report — {{$json.today}}", "emailType": "html", "message": "={{$json.html}}"}, "id": "r4", "name": "Gmail - Send Report", "type": "n8n-nodes-base.gmail", "position": [700, 200]}
  ],
  "connections": {
    "Schedule - 6PM Weekdays": {"main": [[{"node": "Sheets - Ops Data", "type": "main", "index": 0}]]},
    "Sheets - Ops Data": {"main": [[{"node": "Code - Build Report", "type": "main", "index": 0}]]},
    "Code - Build Report": {"main": [[{"node": "Gmail - Send Report", "type": "main", "index": 0}]]}
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 5: Equipment Maintenance Scheduler

Problem: Field maintenance gets missed because reminders live in someone's Outlook calendar, not a tracked system.

Trigger: Daily at 7 AM

What it does:

  • Reads equipment inventory from Sheets (name, location, last maintenance date, interval in days)
  • Calculates next maintenance due date
  • Flags anything due within 7 days
  • Emails the assigned technician + posts to #field-ops Slack
{
  "name": "Telecom - Equipment Maintenance Scheduler",
  "nodes": [
    {"parameters": {"rule": {"interval": [{"field": "cronExpression", "expression": "0 7 * * *"}]}}, "id": "m1", "name": "Schedule - 7AM Daily", "type": "n8n-nodes-base.scheduleTrigger", "position": [100, 200]},
    {"parameters": {"documentId": "YOUR_SHEET_ID", "sheetName": "Equipment"}, "id": "m2", "name": "Sheets - Equipment", "type": "n8n-nodes-base.googleSheets", "position": [300, 200]},
    {"parameters": {"jsCode": "const now = Date.now();\nconst due = [];\nfor (const item of $input.all()) {\n  const d = item.json;\n  if (!d.last_maintenance || !d.interval_days) continue;\n  const next = new Date(d.last_maintenance).getTime() + parseInt(d.interval_days)*86400000;\n  const daysUntil = Math.round((next - now) / 86400000);\n  if (daysUntil <= 7) due.push({ ...d, daysUntil, maintenance_due: new Date(next).toISOString().slice(0,10) });\n}\nif (!due.length) return [{ json: { nothing_due: true } }];\nreturn due.map(d => ({ json: d }));"}, "id": "m3", "name": "Code - Find Due", "type": "n8n-nodes-base.code", "position": [500, 200]},
    {"parameters": {"conditions": {"boolean": [{"value1": "={{$json.nothing_due}}", "value2": true}]}}, "id": "m4", "name": "IF - Nothing Due", "type": "n8n-nodes-base.if", "position": [700, 200]},
    {"parameters": {"fromEmail": "ops@yourcompany.com", "toEmail": "={{$json.technician_email}}", "subject": "Maintenance Due: {{$json.equipment_name}} ({{$json.maintenance_due}})", "emailType": "text", "message": "Hi,\n\nMaintenance is due within {{$json.daysUntil}} days:\n\nEquipment: {{$json.equipment_name}}\nLocation: {{$json.location}}\nDue: {{$json.maintenance_due}}\nLast maintained: {{$json.last_maintenance}}\n\nPlease schedule in field ops calendar.\n\nOps Automation"}, "id": "m5", "name": "Gmail - Notify Tech", "type": "n8n-nodes-base.gmail", "position": [900, 100]},
    {"parameters": {"channel": "#field-ops", "text": "🔧 Maintenance due in {{$json.daysUntil}} days\n{{$json.equipment_name}} at {{$json.location}}\nDue: {{$json.maintenance_due}} | Tech: {{$json.technician_email}}"}, "id": "m6", "name": "Slack #field-ops", "type": "n8n-nodes-base.slack", "position": [900, 300]}
  ],
  "connections": {
    "Schedule - 7AM Daily": {"main": [[{"node": "Sheets - Equipment", "type": "main", "index": 0}]]},
    "Sheets - Equipment": {"main": [[{"node": "Code - Find Due", "type": "main", "index": 0}]]},
    "Code - Find Due": {"main": [[{"node": "IF - Nothing Due", "type": "main", "index": 0}]]},
    "IF - Nothing Due": {"main": [[{"node": "Gmail - Notify Tech", "type": "main", "index": 0}],[]]}
  }
}
Enter fullscreen mode Exit fullscreen mode

n8n vs Zapier vs Make.com for Telecom

n8n (self-hosted) Zapier Make.com
NMS/SCADA integration Full HTTP + SSH + custom Limited Limited
Network data leaves org Never Yes Yes
Per-execution cost $0 $0.01+ $0.001+
Webhook throughput Unlimited Throttled Throttled
Audit log / git versioning JSON workflows No No
Self-hosting option Yes (required for OT) No No

For most telecom environments — especially those with OT/IT separation requirements — self-hosted n8n is the only viable option. Your NMS alerts, customer records, and network topology data never touch a third-party server.


Get the pre-built templates

These 5 workflows plus 10+ others (email auto-responder, invoice generator, lead capture to CRM, AI customer support bot, daily report generator) are available at FlowKit on Gumroad — import-ready JSON, works with any n8n instance.

Start with the free Email Auto-Responder to get familiar with the workflow format, then grab the bundle for full automation coverage.

Top comments (0)