AutomationFlowsEmail & Gmail › Text Back Missed Calls and Notify Business Owners with Twilio and Telegram

Text Back Missed Calls and Notify Business Owners with Twilio and Telegram

ByDmytro @dmytro on n8n.io

This workflow uses Twilio webhooks to forward inbound calls, detect missed calls, and automatically send a tailored SMS text-back to the caller, while also notifying the business owner via Telegram. Receives an inbound call event from Twilio via a webhook. Responds with TwiML…

Webhook trigger★★★★☆ complexity14 nodesTwilioTelegram
Email & Gmail Trigger: Webhook Nodes: 14 Complexity: ★★★★☆ Added:

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

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": "Text back missed calls with Twilio for HVAC, plumbing and home service businesses",
  "tags": [],
  "nodes": [
    {
      "id": "2a5a3e82-2948-4068-823c-5363f4f542ff",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -768,
        -128
      ],
      "parameters": {
        "width": 480,
        "height": 864,
        "content": "## Text back missed calls with Twilio for HVAC, plumbing and home service businesses\n\n### How it works\n\nThis workflow helps home service businesses automatically respond to missed phone calls. It first answers Twilio's inbound call webhook with TwiML to forward the call, then listens for Twilio status callbacks to determine whether the call was missed. When a missed call is detected, it deduplicates repeat callers, builds a text-back message, sends the customer an SMS, and notifies the owner in Telegram.\n\n### Setup steps\n\n- Configure the Twilio incoming call webhook to point to the Incoming Call webhook URL.\n- Replace YOUR-N8N-HOST in the TwiML with your n8n domain so Twilio posts the dial outcome back to the Call Status webhook.\n- Set up Twilio credentials for sending SMS and confirm the sending phone number is SMS-enabled.\n- Set up Telegram bot credentials and the owner's chat ID for notifications.\n- Edit the TwiML forwarding destination so calls route to the correct business phone number.\n- Review the code nodes for the repeat-caller suppression window and message content.\n\n### Customization\n\nCustomize the text-back copy, business name, owner notification format, and deduplication time window in the code nodes."
      },
      "typeVersion": 1
    },
    {
      "id": "cfa244b4-c668-43e9-8717-4e8f57242c87",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -208,
        -112
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 320,
        "content": "## Forward incoming calls\n\nReceives Twilio's incoming call webhook and immediately returns TwiML to forward or dial the business phone line."
      },
      "typeVersion": 1
    },
    {
      "id": "135e4bb9-9478-436e-bcff-e905f218d38e",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -208,
        240
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 304,
        "content": "## Receive call status\n\nHandles Twilio call status callbacks, acknowledges the webhook, and checks whether the call outcome qualifies as missed."
      },
      "typeVersion": 1
    },
    {
      "id": "2b8e4e8e-1501-4f6c-89fc-81e7c04a4472",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        480,
        16
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 320,
        "content": "## Prepare missed-call text\n\nSuppresses repeat callers within the configured window, then builds the customer text-back message and owner notification payload."
      },
      "typeVersion": 1
    },
    {
      "id": "c27338a6-b4ff-42dd-aefa-2d89a62cc8ed",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        928,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 576,
        "content": "## Send follow-up alerts\n\nSends the SMS text-back to the missed caller through Twilio and notifies the business owner through Telegram."
      },
      "typeVersion": 1
    },
    {
      "id": "webhook-incoming-call",
      "name": "When Incoming Call Received",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -160,
        40
      ],
      "parameters": {
        "path": "incoming-call",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "respond-twiml-dial",
      "name": "Forward Call With TwiML",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        60,
        40
      ],
      "parameters": {
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "text/xml"
              }
            ]
          }
        },
        "respondWith": "text",
        "responseBody": "=<Response><Dial timeout=\"20\" callerId=\"{{ $json.body.To }}\" action=\"https://YOUR-N8N-HOST/webhook/call-status\" method=\"POST\"><Number>+10000000000</Number></Dial></Response>"
      },
      "typeVersion": 1.1
    },
    {
      "id": "webhook-call-status",
      "name": "When Call Status Updated",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -160,
        384
      ],
      "parameters": {
        "path": "call-status",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "respond-ack",
      "name": "Acknowledge Call Status",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        64,
        384
      ],
      "parameters": {
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "text/xml"
              }
            ]
          }
        },
        "respondWith": "text",
        "responseBody": "<Response/>"
      },
      "typeVersion": 1.1
    },
    {
      "id": "if-call-missed",
      "name": "If Call Was Missed",
      "type": "n8n-nodes-base.if",
      "position": [
        288,
        384
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "call-was-missed",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ ['no-answer', 'busy', 'failed', 'canceled'].includes($json.body.DialCallStatus) }}",
              "rightValue": ""
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.2
    },
    {
      "id": "code-dedupe",
      "name": "Filter Repeat Callers",
      "type": "n8n-nodes-base.code",
      "position": [
        528,
        176
      ],
      "parameters": {
        "jsCode": "// Only text a caller once per window, even if they redial several times.\nconst store = $getWorkflowStaticData('global');\nstore.recentCallers = store.recentCallers || {};\n\nconst WINDOW_MS = 2 * 60 * 60 * 1000; // 2 hours\nconst now = Date.now();\n\n// Forget anyone outside the window so the store cannot grow forever.\nfor (const [number, seenAt] of Object.entries(store.recentCallers)) {\n  if (now - seenAt > WINDOW_MS) delete store.recentCallers[number];\n}\n\nconst fresh = [];\nfor (const item of $input.all()) {\n  const from = item.json.body?.From;\n  if (!from) continue;\n  if (store.recentCallers[from]) continue;\n  store.recentCallers[from] = now;\n  fresh.push(item);\n}\n\nreturn fresh;"
      },
      "typeVersion": 2
    },
    {
      "id": "code-build-message",
      "name": "Create Text Message Content",
      "type": "n8n-nodes-base.code",
      "position": [
        752,
        176
      ],
      "parameters": {
        "jsCode": "// ---------- EDIT THIS BLOCK ----------\nconst BUSINESS_NAME = 'Your Company';\nconst TIMEZONE = 'America/Chicago';\nconst OPEN_HOUR = 8;   // 24h clock\nconst CLOSE_HOUR = 17; // 24h clock\nconst WORKDAYS = [1, 2, 3, 4, 5]; // 0 = Sunday\n// -------------------------------------\n\nconst local = new Date(new Date().toLocaleString('en-US', { timeZone: TIMEZONE }));\nconst isOpen =\n  WORKDAYS.includes(local.getDay()) &&\n  local.getHours() >= OPEN_HOUR &&\n  local.getHours() < CLOSE_HOUR;\n\nreturn $input.all().map((item) => {\n  const body = item.json.body || {};\n  const fromNumber = body.From;\n  const businessNumber = body.To;\n\n  const smsBody = isOpen\n    ? `Sorry we missed your call at ${BUSINESS_NAME} - we are on a job right now. Reply with your name, address and what is going on, and we will call you straight back. Reply STOP to opt out.`\n    : `Thanks for calling ${BUSINESS_NAME}. We are closed right now and open again at ${OPEN_HOUR}:00. Reply with your name, address and the issue and you will be first in line in the morning. Reply STOP to opt out.`;\n\n  const ownerAlert = `Missed call from ${fromNumber}\\nStatus: ${body.DialCallStatus}\\nHours: ${isOpen ? 'open' : 'after hours'}\\nText-back sent.`;\n\n  return { json: { fromNumber, businessNumber, smsBody, ownerAlert, isOpen } };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "twilio-send-sms",
      "name": "Send SMS Text-Back",
      "type": "n8n-nodes-base.twilio",
      "position": [
        976,
        80
      ],
      "parameters": {
        "to": "={{ $json.fromNumber }}",
        "from": "={{ $json.businessNumber }}",
        "message": "={{ $json.smsBody }}",
        "options": {},
        "resource": "sms",
        "operation": "send",
        "toWhatsapp": false
      },
      "typeVersion": 1
    },
    {
      "id": "telegram-notify-owner",
      "name": "Alert Business Owner via Telegram",
      "type": "n8n-nodes-base.telegram",
      "position": [
        976,
        304
      ],
      "parameters": {
        "text": "={{ $json.ownerAlert }}",
        "chatId": "REPLACE_WITH_YOUR_CHAT_ID",
        "resource": "message",
        "operation": "sendMessage",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "typeVersion": 1.2
    }
  ],
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "If Call Was Missed": {
      "main": [
        [
          {
            "node": "Filter Repeat Callers",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Filter Repeat Callers": {
      "main": [
        [
          {
            "node": "Create Text Message Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Acknowledge Call Status": {
      "main": [
        [
          {
            "node": "If Call Was Missed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Call Status Updated": {
      "main": [
        [
          {
            "node": "Acknowledge Call Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Text Message Content": {
      "main": [
        [
          {
            "node": "Send SMS Text-Back",
            "type": "main",
            "index": 0
          },
          {
            "node": "Alert Business Owner via Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Incoming Call Received": {
      "main": [
        [
          {
            "node": "Forward Call With TwiML",
            "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

This workflow uses Twilio webhooks to forward inbound calls, detect missed calls, and automatically send a tailored SMS text-back to the caller, while also notifying the business owner via Telegram. Receives an inbound call event from Twilio via a webhook. Responds with TwiML…

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

More Email & Gmail workflows → · Browse all categories →

Related workflows

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

Email & Gmail

Advanced AI Powered Document Parsing & Text Extraction with Llama Parse. Uses gmail, gmailTrigger, httpRequest, googleSheets. Webhook trigger; 54 nodes.

Gmail, Gmail Trigger, HTTP Request +3
Email & Gmail

Workflow1-2-3. Uses httpRequest, gmail, telegram, postgres. Webhook trigger; 34 nodes.

HTTP Request, Gmail, Telegram +2
Email & Gmail

Automate legal appointment booking end-to-end. Clients request a slot via webhook, Google Calendar checks availability, Stripe collects payment, and a confirmed calendar event is created automatically

Google Calendar, Gmail, HTTP Request +2
Email & Gmail

Automate clinic appointment booking end to end. Patients submit a request via webhook, the workflow generates a unique Booking ID, routes online consultations to Google Meet and offline ones to the cl

Google Calendar, Gmail, Twilio
Email & Gmail

A complete email campaign automation system featuring dual-mode access control (Demo/Pro), usage tracking, and professional email delivery. Perfect for SaaS products, marketing agencies, or anyone bui

Google Sheets, SendGrid, Telegram