{
  "name": "Outbound sales calls from Google Sheets using Retell AI voice agent",
  "nodes": [
    {
      "id": "3033d295-aca5-4e11-8014-5a5529272046",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        496,
        912
      ],
      "parameters": {
        "color": 4,
        "height": 432,
        "content": "## Make Phone Call\n**⚙️ Setup:**\n**Create a free Retell account**  [Retell AI – $10 FREE credits](https://dashboard.retellai.com/?ref=retell-n8n) \n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "08e73ac6-8da3-4902-9d95-eeef09c82461",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1136,
        912
      ],
      "parameters": {
        "color": 5,
        "width": 288,
        "height": 432,
        "content": "## When a lead arrives\n"
      },
      "typeVersion": 1
    },
    {
      "id": "df3b824e-aaad-48b9-b814-a246f82178ea",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -832,
        912
      ],
      "parameters": {
        "color": 6,
        "width": 576,
        "height": 432,
        "content": "## Santise the phone number\nPhone numbers are messy; For the call to work, we need to strip away all spaces, dashes, plus signs, and brackets so all we have left are numbers. Next, we're filtering out any numbers we've already called for a bit of damage control.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "75df0ca6-fb3d-4252-ba5a-dd59c49e501f",
      "name": "Make phone call",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        560,
        1056
      ],
      "parameters": {
        "url": "https://api.retellai.com/v2/create-phone-call",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "authentication": "genericCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "from_number",
              "value": "{replace with your Retell AI phone number}"
            },
            {
              "name": "to_number",
              "value": "={{ $json['Phone Number'] }}"
            },
            {
              "name": "agent_id",
              "value": "{replace with your Retell AI agent ID}"
            }
          ]
        },
        "genericAuthType": "httpBearerAuth"
      },
      "typeVersion": 4.2
    },
    {
      "id": "fb28e354-faa1-4e4e-9962-474551fd7a04",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        752,
        912
      ],
      "parameters": {
        "color": 4,
        "width": 720,
        "height": 432,
        "content": "## Wait for the call to finish\nThis poll's Retell AI until the call has finished. For a more elegant approach, you can use [a webhook](https://docs.retellai.com/features/webhook-overview) but this approach is simpler / beginner-friendly.\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "77f2dd86-1d67-4cb8-8524-c998cbb3ca6c",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -240,
        912
      ],
      "parameters": {
        "color": 6,
        "width": 720,
        "height": 432,
        "content": "## Check it's daytime (in their timezone)\nWe don't want to call leads at 2am, so this step checks the lead's timezone (based on their phone number) and only calls within the 8am-5pm window."
      },
      "typeVersion": 1
    },
    {
      "id": "cf181a33-7778-41ba-b395-b75e1df6b796",
      "name": "Get lead's timezone",
      "type": "n8n-nodes-base.code",
      "position": [
        -160,
        1072
      ],
      "parameters": {
        "jsCode": "const rawPhone = $input.first().json['Phone Number'];\nconst phoneNumber = String(rawPhone || '');\nconst cleaned = phoneNumber.replace(/\\D/g, '');\n\n// Country code to timezone mapping\nconst timezoneMap = {\n  '44': 'Europe/London',\n  '1': 'America/New_York',\n  '61': 'Australia/Sydney',\n  '33': 'Europe/Paris',\n  '49': 'Europe/Berlin',\n  '34': 'Europe/Madrid',\n  '39': 'Europe/Rome',\n  '31': 'Europe/Amsterdam',\n  '353': 'Europe/Dublin',\n  '91': 'Asia/Kolkata',\n  '971': 'Asia/Dubai',\n  '65': 'Asia/Singapore',\n  '81': 'Asia/Tokyo',\n};\n\n// Find matching country code (check longer codes first)\nlet timezone = 'Europe/London';\nlet countryCode = '44';\n\nconst sortedCodes = Object.keys(timezoneMap).sort((a, b) => b.length - a.length);\nfor (const code of sortedCodes) {\n  if (cleaned.startsWith(code)) {\n    timezone = timezoneMap[code];\n    countryCode = code;\n    break;\n  }\n}\n\n// Get current time in the lead's timezone using Intl API\nconst now = new Date();\n\nconst formatter = new Intl.DateTimeFormat('en-GB', {\n  timeZone: timezone,\n  hour: 'numeric',\n  minute: 'numeric',\n  weekday: 'long',\n  hour12: false\n});\n\nconst parts = formatter.formatToParts(now);\nconst localHour = parseInt(parts.find(p => p.type === 'hour').value, 10);\nconst localMinute = parseInt(parts.find(p => p.type === 'minute').value, 10);\nconst weekdayName = parts.find(p => p.type === 'weekday').value;\n\nconst weekdayMap = {\n  'Monday': 1,\n  'Tuesday': 2,\n  'Wednesday': 3,\n  'Thursday': 4,\n  'Friday': 5,\n  'Saturday': 6,\n  'Sunday': 7\n};\nconst dayOfWeek = weekdayMap[weekdayName];\n\nreturn {\n  json: {\n    ...$input.first().json,\n    timezone,\n    countryCode,\n    localHour,\n    localMinute,\n    dayOfWeek,\n    localTimeFormatted: `${weekdayName} ${String(localHour).padStart(2, '0')}:${String(localMinute).padStart(2, '0')}`\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "7e839392-632c-49d9-bd68-521cc0994a7d",
      "name": "Cleanse phone numbers",
      "type": "n8n-nodes-base.code",
      "position": [
        -736,
        1072
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nconst updatedItems = items.map((item) => {\n  item.json[\"Phone Number\"] = String(item.json[\"Phone Number\"]).replace(/\\D/g, \"\");\n  return item;\n});\nreturn updatedItems;"
      },
      "typeVersion": 2
    },
    {
      "id": "2a8f1a88-5638-400e-a535-db4ba9d5ff13",
      "name": "New lead",
      "type": "n8n-nodes-base.googleSheetsTrigger",
      "position": [
        -1056,
        1072
      ],
      "parameters": {
        "event": "rowAdded",
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultUrl": "",
          "cachedResultName": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": ""
        }
      },
      "credentials": {
        "googleSheetsTriggerOAuth2Api": {
          "id": "Oca0ZSrR8OUM5wUG",
          "name": "Google Sheets Trigger account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "67dc5f93-74ea-45ea-b698-629268a9d7cf",
      "name": "Is it between 8am-5pm for them?",
      "type": "n8n-nodes-base.if",
      "position": [
        96,
        1072
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "ba0908e3-7234-4e40-b232-37111a59ff11",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.localHour }}",
              "rightValue": 8
            },
            {
              "id": "5085c2ab-cf18-41d9-989e-1f25579a89b8",
              "operator": {
                "type": "number",
                "operation": "lte"
              },
              "leftValue": "={{ $json.localHour }}",
              "rightValue": 17
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "1b1ce178-dca3-4b6c-be73-56de95f1fe46",
      "name": "Wait",
      "type": "n8n-nodes-base.wait",
      "position": [
        304,
        1152
      ],
      "webhookId": "5174cf45-39dc-4366-876a-765d75f76112",
      "parameters": {
        "unit": "hours",
        "amount": 2
      },
      "typeVersion": 1.1
    },
    {
      "id": "5de17c52-cbc4-4786-8fa4-2570eea19ae7",
      "name": "Wait 1 minute",
      "type": "n8n-nodes-base.wait",
      "position": [
        848,
        1056
      ],
      "webhookId": "5251c753-4f59-46a9-b5ac-e98c1df12641",
      "parameters": {
        "unit": "minutes",
        "amount": 1
      },
      "typeVersion": 1.1
    },
    {
      "id": "b97b854d-9950-473d-b5d4-5d81a35a245c",
      "name": "Call has finished",
      "type": "n8n-nodes-base.if",
      "position": [
        1232,
        1056
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond1",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{$json.body.call_status}}",
              "rightValue": "ended"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "29c2d2f2-80f0-4b65-846d-faee8ccb9ac2",
      "name": "Get Call Status",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1040,
        1056
      ],
      "parameters": {
        "url": "={{`https://api.retellai.com/v2/get-call/${$node['Make phone call'].json.call_id}`}}\n",
        "options": {
          "response": {
            "response": {
              "fullResponse": true
            }
          }
        },
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {}
          ]
        }
      },
      "credentials": {
        "httpBearerAuth": {
          "id": "16ke929vPR8H6MKl",
          "name": "Bearer Auth account 4"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "c97bd9e5-b038-4c36-826e-b069d21f73b1",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1488,
        912
      ],
      "parameters": {
        "color": 7,
        "width": 512,
        "height": 432,
        "content": "## Update Google Sheet\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "23763512-2dee-4728-8b6d-0df2fee84e71",
      "name": "Filter numbers we've already called",
      "type": "n8n-nodes-base.filter",
      "position": [
        -432,
        1072
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "81d18bd8-2b72-4605-9503-45c6cd0de270",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json['Phone Number'] }}",
              "rightValue": 0
            },
            {
              "id": "2f60a482-7bac-441a-b8de-35e62238d874",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.Status }}",
              "rightValue": "Not Called"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "dd47c895-1915-4227-b163-c991ff4cd29d",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1792,
        640
      ],
      "parameters": {
        "width": 592,
        "height": 1040,
        "content": "## 📞 Automated Outbound Lead Caller\n\nAutomatically call new leads from a Google Sheet using [Retell AI](https://www.retellai.com/?via=intellagents), with built-in timezone awareness to ensure you're only calling during business hours.\n\n### What it does\n1. **Triggers** when a new row is added to your Google Sheet\n2. **Cleans** the phone number (removes spaces, dashes, brackets)\n3. **Checks timezone** based on country code—only calls between 8am-5pm local time\n4. **Makes the call** via Retell AI using your configured voice agent\n5. **Polls for completion** then updates the sheet with \"Called\" status, a call summary, sentiment, transcript & more.\n\n### ⚙️ Setup\n\n**1. Retell AI Account**\n[Create a free account and get $10 in credits](https://www.retellai.com/?via=intellagents\n) (~73 mins of calls)\n\n\n**2. Add Your Credentials**\n- Retell AI API key → Bearer Auth credential\n- Google Sheets OAuth\n\n\n**3. Configure the \"Make phone call\" Node**\n- `from_number`: Your Retell phone number (with country code)\n- `to_number`: Change to `{{ $json['Phone Number'] }}`\n- `agent_id`: Your Retell agent ID\n\n\n**4. Connect Your Google Sheet**\nColumns needed: `Phone Number`, `Name`, `Status`\nSet new leads to Status = \"Not called\"\n\n### 📺 Video Walkthrough\n[COMING SOON]\n\n### 💡 Customisation Ideas\n- Swap the trigger for a webhook, form, or CRM\n- Adjust calling hours in the IF node (currently 8am-5pm)\n- Add more country codes to the timezone mapping\n- Send results to Slack, CRM, or email\n\n---\nBuilt by Marcus Taylor\n📺 @intellagents | 🌐 [voiceai.guide](https://voiceai.guide)"
      },
      "typeVersion": 1
    },
    {
      "id": "47bd5d82-dcec-4bc4-8209-25fadaf45254",
      "name": "Update Google Sheets with call information",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1696,
        1040
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": "gid=0"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": ""
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "sfbnXOcEAdJB07hN",
          "name": "Google Sheets account"
        }
      },
      "typeVersion": 4.7
    }
  ],
  "active": true,
  "pinData": {},
  "settings": {
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "connections": {
    "Wait": {
      "main": [
        [
          {
            "node": "Get lead's timezone",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "New lead": {
      "main": [
        [
          {
            "node": "Cleanse phone numbers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 1 minute": {
      "main": [
        [
          {
            "node": "Get Call Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Call Status": {
      "main": [
        [
          {
            "node": "Call has finished",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Make phone call": {
      "main": [
        [
          {
            "node": "Wait 1 minute",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call has finished": {
      "main": [
        [
          {
            "node": "Update Google Sheets with call information",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait 1 minute",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get lead's timezone": {
      "main": [
        [
          {
            "node": "Is it between 8am-5pm for them?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cleanse phone numbers": {
      "main": [
        [
          {
            "node": "Filter numbers we've already called",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is it between 8am-5pm for them?": {
      "main": [
        [
          {
            "node": "Make phone call",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter numbers we've already called": {
      "main": [
        [
          {
            "node": "Get lead's timezone",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}