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-criticalSlack channel immediately - Waits 15 minutes — if outage still unresolved, escalates to
#noc-management - Non-critical alerts route to
#noc-alertsfor 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}]]}
}
}
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}]]}
}
}
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}]]}
}
}
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}]]}
}
}
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-opsSlack
{
"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}],[]]}
}
}
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)