{
  "name": "Missed-call text-back (The Automation Memo template)",
  "nodes": [
    {
      "parameters": {
        "content": "# MISSED-CALL TEXT-BACK (template from The Automation Memo)\n\nA call comes in on your Twilio number, nobody picks up, the caller gets a text within seconds, and every call and reply lands in one table.\n\nSETUP (10 minutes)\n1. Twilio: buy or use a trial number. Phone Numbers -> your number -> Voice: webhook POST <your n8n url>/webhook/missed-call. Messaging: webhook POST <your n8n url>/webhook/missed-call-sms.\n2. n8n: add a Twilio credential (Account SID + Auth Token) and pick it on the 'Text the caller back' node.\n3. Logging: the two Postgres nodes write to a table named lead_log (columns: source, phone, message, received_at, responded_at, response_channel, notes). Point them at your own Postgres, or delete them and log somewhere else.\n4. Edit the greeting in 'Answer' and the text in 'Text the caller back'. Keep the text under 160 characters.\n\nHONESTY: the text says it was sent automatically. Keep that line. US numbers need A2P 10DLC registration on a paid Twilio account before texting strangers; trial accounts text verified numbers only.\n\nFree, from theautomationmemo.com. Content on that account is AI-assisted, and we say so.",
        "color": 5,
        "width": 720,
        "height": 560
      },
      "name": "Read me first",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -400,
        -300
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "missed-call",
        "responseMode": "responseNode",
        "options": {}
      },
      "name": "Call comes in (Twilio voice)",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const b = $input.first().json.body || {};\nconst caller = String(b.From || b.Caller || '').trim();\nconst ours = String(b.To || b.Called || '').trim();\nreturn [{ json: { caller, ours, call_sid: String(b.CallSid || ''), received_at: new Date().toISOString(), city: String(b.FromCity || ''), state: String(b.FromState || '') } }];"
      },
      "name": "Normalize Call",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        0
      ]
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Response><Say voice=\"Polly.Matthew\">Thanks for calling. We are on a job and cannot pick up right now. A text is on its way to this number. Reply there and a real person will get back to you.</Say><Hangup/></Response>",
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "text/xml"
              }
            ]
          }
        }
      },
      "name": "Answer: greeting, then hang up",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        480,
        0
      ]
    },
    {
      "parameters": {
        "resource": "sms",
        "operation": "send",
        "from": "={{ $('Normalize Call').first().json.ours }}",
        "to": "={{ $('Normalize Call').first().json.caller }}",
        "message": "Sorry we missed your call. This text went out automatically the moment you hung up. Reply here with what you need and a real person will get right back to you.",
        "options": {}
      },
      "name": "Text the caller back",
      "type": "n8n-nodes-base.twilio",
      "typeVersion": 1,
      "position": [
        720,
        0
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "=insert into lead_log (source, phone, message, received_at, responded_at, response_channel, notes)\nvalues ('missed-call', '{{ $('Normalize Call').first().json.caller }}', 'missed call, text-back sent', '{{ $('Normalize Call').first().json.received_at }}'::timestamptz, now(), 'sms', 'call {{ $('Normalize Call').first().json.call_sid }} from {{ $('Normalize Call').first().json.city }} {{ $('Normalize Call').first().json.state }}; sms {{ $json.sid || '' }}')\nreturning id",
        "options": {}
      },
      "name": "Log call + text time",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        960,
        0
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "missed-call-sms",
        "responseMode": "responseNode",
        "options": {}
      },
      "name": "Reply comes in (Twilio SMS)",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        0,
        400
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const b = $input.first().json.body || {};\nreturn [{ json: { caller: String(b.From || '').trim(), ours: String(b.To || '').trim(), text: String(b.Body || '').trim().slice(0, 1000), message_sid: String(b.MessageSid || '') } }];"
      },
      "name": "Normalize Reply",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        400
      ]
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Response></Response>",
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "text/xml"
              }
            ]
          }
        }
      },
      "name": "Acknowledge (no auto-reply)",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        480,
        400
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "=insert into lead_log (source, phone, message, received_at, response_channel, notes)\nvalues ('missed-call-reply', '{{ $('Normalize Reply').first().json.caller }}', '{{ $('Normalize Reply').first().json.text.replace(/'/g, \"''\") }}', now(), 'sms', 'reply to text-back; sms {{ $('Normalize Reply').first().json.message_sid }}; human follow-up owed')\nreturning id",
        "options": {}
      },
      "name": "Log the reply",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        720,
        400
      ]
    }
  ],
  "connections": {
    "Call comes in (Twilio voice)": {
      "main": [
        [
          {
            "node": "Normalize Call",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Call": {
      "main": [
        [
          {
            "node": "Answer: greeting, then hang up",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Answer: greeting, then hang up": {
      "main": [
        [
          {
            "node": "Text the caller back",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Text the caller back": {
      "main": [
        [
          {
            "node": "Log call + text time",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reply comes in (Twilio SMS)": {
      "main": [
        [
          {
            "node": "Normalize Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Reply": {
      "main": [
        [
          {
            "node": "Acknowledge (no auto-reply)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Acknowledge (no auto-reply)": {
      "main": [
        [
          {
            "node": "Log the reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": false
  }
}