{
  "name": "Missed Call Text Back",
  "nodes": [
    {
      "parameters": {
        "content": "## Missed call text back\n\nEvery missed call gets an instant SMS so the lead doesn't ring the next business on Google.\n\n**Setup**\n1. Connect Twilio and Google Sheets. Put your Twilio number where it says YOUR_TWILIO_NUMBER.\n2. Make a sheet with a **Contacts** tab: phone, name, tags, last_missed_call, opted_out.\n3. In Twilio, point your number's voice webhook at a TwiML Bin that dials your mobile with `action` = this workflow's **Call Ended** URL (see the card on the website).\n4. Point the number's messaging webhook at the **Incoming SMS** URL so STOP replies are honoured.\n5. Edit the SMS text and your business hours in **Check Quiet Hours**.",
        "height": 420,
        "width": 440
      },
      "id": "dc965ae9-581c-4bdb-a04f-5e195b68c84f",
      "name": "Note 0",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -460,
        -200
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "missed-call",
        "options": {},
        "responseMode": "responseNode"
      },
      "id": "86e5722d-3628-4a59-a124-2696870f4c70",
      "name": "Call Ended",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ],
      "webhookId": "8515e93c-22cb-48cc-b7c0-f92d8bc553f9"
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "<Response><Hangup/></Response>",
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "text/xml"
              }
            ]
          }
        }
      },
      "id": "2d1c7964-7f18-431e-8691-8ecff03e7fe6",
      "name": "Hang Up",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        220,
        -140
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "84f0d886-32da-4986-abfb-ea4fbf707ea5",
              "leftValue": "={{ $json.body.DialCallStatus }}",
              "operator": {
                "type": "string",
                "operation": "regex"
              },
              "rightValue": "^(no-answer|busy|failed|canceled)$"
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "18736468-0da7-4e26-8381-49bb2e36ffe7",
      "name": "Missed?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        220,
        60
      ]
    },
    {
      "parameters": {
        "jsCode": "// Normalise an Australian or international number to E.164 (+61...). Change DEFAULT_CC for your country.\nconst DEFAULT_CC = '61';\nfunction e164(raw) {\n  let d = String(raw || '').replace(/[^\\d+]/g, '');\n  if (d.startsWith('+')) return d;\n  if (d.startsWith('00')) return '+' + d.slice(2);\n  if (d.startsWith('0')) return '+' + DEFAULT_CC + d.slice(1);\n  return d ? '+' + d : '';\n}\nreturn [{ json: { phone: e164($json.body.From), calledAt: new Date().toISOString() } }];",
        "mode": "runOnceForEachItem"
      },
      "id": "cc59abcc-39f0-4aef-9ead-2db81994f710",
      "name": "Normalise Caller",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        440,
        40
      ]
    },
    {
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Contacts"
        },
        "filtersUI": {
          "values": [
            {
              "lookupColumn": "phone",
              "lookupValue": "={{ $json.phone }}"
            }
          ]
        },
        "options": {}
      },
      "id": "a27e14ce-f0b9-4632-a7ba-25724dc86be2",
      "name": "Find Contact",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        660,
        40
      ],
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "2b747e50-548d-41df-a4ff-b0e9a7071363",
              "leftValue": "={{ $json.opted_out }}",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "rightValue": "yes"
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "1ef2790a-cd26-4bf4-8970-560911c1a6f6",
      "name": "Not Opted Out?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        880,
        40
      ]
    },
    {
      "parameters": {
        "jsCode": "// Texts outside business hours wait until the morning. Edit to suit.\nconst tz = 'Australia/Sydney', open = 8, close = 20;\nconst hour = Number(new Date().toLocaleString('en-AU', { timeZone: tz, hour: 'numeric', hour12: false }));\nconst phone = $('Normalise Caller').item.json.phone;\nconst name = $json.name ? ' ' + String($json.name).split(' ')[0] : '';\nreturn [{ json: {\n  phone,\n  quiet: hour < open || hour >= close,\n  message: `Hi${name}, sorry we missed your call! This is YOUR BUSINESS NAME. How can we help? Reply here or we'll call you back shortly. Reply STOP to opt out.`,\n} }];",
        "mode": "runOnceForEachItem"
      },
      "id": "ff7139dd-1d0a-4b94-8339-afb2cdc0c447",
      "name": "Check Quiet Hours",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1100,
        20
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "72a77555-5670-457b-a101-16658a6857c7",
              "leftValue": "={{ $json.quiet }}",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "rightValue": ""
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "08d902c8-ecc0-4292-ab38-991dab0e3f4b",
      "name": "Quiet Hours?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1320,
        20
      ]
    },
    {
      "parameters": {
        "amount": 10,
        "unit": "hours"
      },
      "id": "72262a3a-386b-4e56-99f2-7af2d7e4e62a",
      "name": "Wait Until Morning",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        1540,
        -80
      ]
    },
    {
      "parameters": {
        "from": "YOUR_TWILIO_NUMBER",
        "to": "={{ $('Check Quiet Hours').item.json.phone }}",
        "message": "={{ $('Check Quiet Hours').item.json.message }}",
        "options": {}
      },
      "id": "785e156c-9e48-4c3d-99d0-25661e22c480",
      "name": "Send Text",
      "type": "n8n-nodes-base.twilio",
      "typeVersion": 1,
      "position": [
        1760,
        20
      ]
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Contacts"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "phone": "={{ $('Check Quiet Hours').item.json.phone }}",
            "tags": "missed-call",
            "last_missed_call": "={{ $now.toISO() }}"
          },
          "matchingColumns": [
            "phone"
          ],
          "schema": [
            {
              "id": "phone",
              "displayName": "phone",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "tags",
              "displayName": "tags",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "last_missed_call",
              "displayName": "last_missed_call",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {}
      },
      "id": "ebd54cab-1c08-4f79-84fd-fa04cd787fb1",
      "name": "Log on Contact",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1980,
        20
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "missed-call-sms",
        "options": {},
        "responseMode": "responseNode"
      },
      "id": "16e60f37-b39b-4bba-bd67-711c10682586",
      "name": "Incoming SMS",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        420
      ],
      "webhookId": "e756d9e0-6d10-450e-86fa-d85ec34bd3dc"
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "<Response></Response>",
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "text/xml"
              }
            ]
          }
        }
      },
      "id": "2c597587-3905-4d64-82c0-d3796f6a7f60",
      "name": "Empty Reply",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        220,
        320
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "0e6f7959-e098-4406-993a-694aa060a974",
              "leftValue": "={{ $json.body.Body.trim() }}",
              "operator": {
                "type": "string",
                "operation": "regex"
              },
              "rightValue": "/^(stop|stopall|unsubscribe|cancel|end|quit)$/i"
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "ba095930-478b-4eb0-a9cf-e1598f5e3666",
      "name": "Is STOP?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        220,
        500
      ]
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Contacts"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "phone": "={{ $json.body.From }}",
            "opted_out": "yes"
          },
          "matchingColumns": [
            "phone"
          ],
          "schema": [
            {
              "id": "phone",
              "displayName": "phone",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "opted_out",
              "displayName": "opted_out",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {}
      },
      "id": "3d588006-6760-4cda-97a2-597fdd4b24d7",
      "name": "Mark Opted Out",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        440,
        480
      ]
    }
  ],
  "connections": {
    "Call Ended": {
      "main": [
        [
          {
            "node": "Hang Up",
            "type": "main",
            "index": 0
          },
          {
            "node": "Missed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Missed?": {
      "main": [
        [
          {
            "node": "Normalise Caller",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalise Caller": {
      "main": [
        [
          {
            "node": "Find Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Contact": {
      "main": [
        [
          {
            "node": "Not Opted Out?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Not Opted Out?": {
      "main": [
        [
          {
            "node": "Check Quiet Hours",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Quiet Hours": {
      "main": [
        [
          {
            "node": "Quiet Hours?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Quiet Hours?": {
      "main": [
        [
          {
            "node": "Wait Until Morning",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Until Morning": {
      "main": [
        [
          {
            "node": "Send Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Text": {
      "main": [
        [
          {
            "node": "Log on Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Incoming SMS": {
      "main": [
        [
          {
            "node": "Empty Reply",
            "type": "main",
            "index": 0
          },
          {
            "node": "Is STOP?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is STOP?": {
      "main": [
        [
          {
            "node": "Mark Opted Out",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "active": false
}