Twilio Skill - Voice Calls, SMS & Two-Way Messaging
Complete Twilio integration for making voice calls, sending SMS messages, and receiving incoming text messages with automatic replies.
Quick Links
- •Outbound SMS:
sms.py- Send text messages - •Outbound Calls:
call.py- Make voice calls with TTS - •Webhook Server:
webhook_server.py- Receive incoming SMS - •SMS Replies:
respond_sms.py- Reply to incoming texts - •Setup Guide: See "Setup" section below
- •Troubleshooting: See end of this document
Setup
1. Prerequisites
- •Python 3.8+
- •Twilio account with phone number and SMS/calling enabled
- •(Optional) ngrok or Tailscale funnel for exposing webhook to internet
2. Install Dependencies
cd ~/clawd/skills/twilio pip install -r requirements.txt
Verify installation:
python -c "from twilio.rest import Client; from flask import Flask; print('✓ All dependencies installed')"
3. Set Environment Variables
Export these before using any scripts:
export TWILIO_ACCOUNT_SID="AC35fce9f5069e4a19358da26286380ca9" export TWILIO_AUTH_TOKEN="a7700999dcff89b738f62c78bd1e33c1" export TWILIO_PHONE_NUMBER="+19152237302" export ELEVENLABS_API_KEY="sk_98316c1321b6263ab8d3fc46b8439c23b9fc076691d85c1a"
Or create a .env file:
# .env TWILIO_ACCOUNT_SID=AC35fce9f5069e4a19358da26286380ca9 TWILIO_AUTH_TOKEN=a7700999dcff89b738f62c78bd1e33c1 TWILIO_PHONE_NUMBER=+19152237302 ELEVENLABS_API_KEY=sk_98316c1321b6263ab8d3fc46b8439c23b9fc076691d85c1a
Then load it:
set -a; source .env; set +a
⚠️ Important: Never commit .env with real credentials. Add to .gitignore:
.env .env.local *.log
4. Verify Credentials
# Check that variables are set echo "Account SID: $TWILIO_ACCOUNT_SID" echo "Phone: $TWILIO_PHONE_NUMBER" # Test with a simple SMS python sms.py --phone "+19152134309" --message "Hello from Twilio skill!"
Features
1. Send SMS Messages
Send text messages to any phone number.
python sms.py --phone "+19152134309" --message "Hello!"
Options:
- •
--phone(required): Recipient in E.164 format (e.g.,+19152134309) - •
--message(required): Message body - •
--json: Output response as JSON
Response:
{
"status": "success",
"message_sid": "SM1234567890abcdef",
"phone": "+19152134309",
"message": "Hello!",
"from_number": "+1 (915) 223-7302",
"segments": 1
}
2. Make Voice Calls
Place outbound calls with text-to-speech.
python call.py --phone "+19152134309" --message "Hello, this is a test call"
Options:
- •
--phone(required): Recipient in E.164 format - •
--message(required): Message to speak - •
--voice(optional): ElevenLabs voice ID (default: Rachel) - •
--json: Output response as JSON
Response:
{
"status": "success",
"call_sid": "CA1234567890abcdef",
"phone": "+19152134309",
"message": "Hello, this is a test call",
"from_number": "+1 (915) 223-7302"
}
3. Receive Incoming SMS (Webhook Server)
Start the webhook server to receive incoming text messages automatically:
python webhook_server.py
This server:
- •✅ Listens for POST requests from Twilio (port 5000 by default)
- •✅ Stores conversation history in JSON
- •✅ Forwards messages to Clawdbot gateway
- •✅ Validates Twilio signatures for security
- •✅ Maintains conversation state per phone number
Server Options:
- •
--host: Bind address (default: 127.0.0.1) - •
--port: Port number (default: 5000) - •
--debug: Enable debug mode - •
--gateway-url: Clawdbot gateway URL (default: http://localhost:18789) - •
--gateway-token: Gateway auth token
Example:
python webhook_server.py --port 5000 --debug
Server Endpoints:
POST /sms - Receive Twilio webhook GET /health - Health check GET /conversations - List all conversations GET /conversations/<phone> - Get specific conversation
4. Send SMS Replies
Reply to incoming messages using respond_sms.py:
# Send a reply python respond_sms.py --to "+19152134309" --message "Thanks for texting!" # View conversation history python respond_sms.py --to "+19152134309" --view # List all active conversations python respond_sms.py --list-conversations
Options:
- •
--to: Phone number (required for sending) - •
--message: Reply message (required for sending) - •
--view: View conversation with this number - •
--list-conversations: Show all active conversations - •
--json: Output as JSON
Example responses:
# Send reply $ python respond_sms.py --to "+19152134309" --message "Got it!" ✓ SMS sent to +19152134309 SID: SM1234567890abcdef Message: Got it! # View conversation $ python respond_sms.py --to "+19152134309" --view Conversation with +19152134309 Started: 2024-02-03T10:30:00 Total messages: 5 Recent messages: [10:31] ← Them: Hey, how are you? [10:32] → You: Doing well! [10:35] ← Them: Great, when are we meeting? [10:36] → You: Tomorrow at 2pm [10:37] ← Them: Perfect! # List conversations $ python respond_sms.py --list-conversations Phone Number Messages Last Message +19152134309 5 2024-02-03 +19154567890 3 2024-02-02 Total: 2 conversations
Two-Way SMS Workflow
Here's how the system handles incoming and outgoing messages:
Incoming SMS from User
↓
[Twilio Cloud]
↓
webhook_server.py (POST /sms)
├→ Validate signature
├→ Store in conversation_db.json
├→ Forward to Clawdbot gateway
└→ Return 200 OK to Twilio
↓
Clawdbot Gateway
├→ Notify user in Telegram/chat
└→ (User crafts reply)
↓
User runs: respond_sms.py --to +1234567890 --message "Reply text"
↓
respond_sms.py
├→ Create Twilio client
├→ Send SMS
├→ Record in conversation_db.json
└→ Return success/error
↓
[Twilio Cloud]
↓
Outgoing SMS to User
Public URL Setup (Required for Webhook)
The webhook server runs locally. To receive SMS from Twilio, it needs a public URL. Choose one:
Option A: ngrok (Simple, Temporary)
Best for testing.
# Install ngrok (one-time) brew install ngrok # macOS # or visit https://ngrok.com/download # Start ngrok tunnel ngrok http 5000 # Output: # Forwarding https://1a2b3c4d5e6f.ngrok.io -> http://localhost:5000
Your public URL is https://1a2b3c4d5e6f.ngrok.io/sms (note: /sms endpoint)
In Twilio Console:
- •Go to Phone Numbers → Active Numbers
- •Click your number
- •Set "A Call Comes In" → "Webhooks" → "Messaging"
- •Paste:
https://1a2b3c4d5e6f.ngrok.io/sms - •Save
Option B: Tailscale Funnel (Persistent)
Best for production use on your local machine.
# Install Tailscale (one-time) brew install tailscale tailscale up # Enable funnel on port 5000 tailscale funnel 5000 # Output: # Access point: https://your-machine.tail12345.ts.net
Your public URL is https://your-machine.tail12345.ts.net/sms
In Twilio Console:
- •Go to Phone Numbers → Active Numbers
- •Click your number
- •Set messaging to:
https://your-machine.tail12345.ts.net/sms - •Save
Option C: Static IP / Port Forwarding (Advanced)
If you have a static public IP and router access:
- •Forward port 5000 to your local machine
- •Use:
https://your-public-ip:5000/sms - •Consider using a domain name + SSL cert for security
Running the System
Development Setup (All-in-One)
Terminal 1: Start webhook server
cd ~/clawd/skills/twilio source .env # Load credentials python webhook_server.py --port 5000 --debug
Terminal 2: Start ngrok tunnel
ngrok http 5000
Terminal 3: Test sending a reply
cd ~/clawd/skills/twilio source .env python respond_sms.py --to "+19152134309" --message "Test reply"
Production Setup (with Tailscale)
Terminal 1: Ensure Tailscale is running
tailscale up tailscale funnel 5000
Terminal 2: Start webhook server
cd ~/clawd/skills/twilio source .env python webhook_server.py --port 5000
That's it! Your server is accessible via https://your-machine.tail12345.ts.net/sms
Conversation History
All conversations are stored in ~/.clawd/twilio_conversations.json
Format:
{
"+19152134309": {
"phone": "+19152134309",
"created_at": "2024-02-03T10:30:00",
"last_message_at": "2024-02-03T11:45:00",
"message_count": 5,
"messages": [
{
"timestamp": "2024-02-03T10:30:00",
"direction": "inbound",
"body": "Hey!",
"sid": "SM1234567890abcdef"
},
{
"timestamp": "2024-02-03T10:31:00",
"direction": "outbound",
"body": "Hi there!",
"sid": "SM1234567890abcdef"
}
]
}
}
View conversation:
python respond_sms.py --to "+19152134309" --view
Backup conversations:
cp ~/.clawd/twilio_conversations.json ~/Desktop/twilio_backup.json
Clear history (careful!):
rm ~/.clawd/twilio_conversations.json
Logs
Server logs are stored in ~/.clawd/twilio_webhook.log
View real-time logs:
tail -f ~/.clawd/twilio_webhook.log
Parse logs:
grep "ERROR" ~/.clawd/twilio_webhook.log grep "SMS sent" ~/.clawd/twilio_webhook.log
Security & Best Practices
✅ Implemented
- •✓ Twilio signature validation on webhook
- •✓ Environment variable credentials (no hardcoded secrets)
- •✓ Request timeout handling
- •✓ Error logging (sensitive data scrubbed)
- •✓ Phone number format validation (E.164)
⚠️ Additional Recommendations
- •
Keep credentials secret:
- •Never share account SID or auth token
- •Rotate tokens regularly
- •Use
.envwith.gitignore
- •
Monitor usage:
- •Set Twilio account alerts for unusual activity
- •Review Logs regularly: https://www.twilio.com/console/logs
- •
Firewall the webhook:
- •If using static IP, restrict to Twilio's IP ranges
- •Use IP filtering on router
- •Enable HTTPS only (ngrok/Tailscale handle this)
- •
Rate limiting:
- •Implement rate limiting for
respond_sms.pyif used by multiple users - •Twilio has API rate limits; be aware of costs
- •Implement rate limiting for
- •
Data protection:
- •Conversation history stored locally (not encrypted)
- •Consider encrypting
~/.clawd/twilio_conversations.jsonif sensitive - •Backup important conversations
Examples
Example 1: Automated Status Check
#!/bin/bash
# Check server health
curl http://localhost:5000/health | jq .
# Output:
# {
# "status": "healthy",
# "timestamp": "2024-02-03T11:00:00",
# "gateway": "http://localhost:18789",
# "conversation_db": "/Users/ericwoodard/.clawd/twilio_conversations.json"
# }
Example 2: Bulk Reply Script
#!/bin/bash # Reply to multiple numbers for phone in "+19152134309" "+19154567890" "+19158901234"; do python respond_sms.py --to "$phone" --message "Hello from bulk script!" sleep 2 # Avoid rate limiting done
Example 3: Integration with Clawdbot
# In a Clawdbot skill
import subprocess
import json
def send_sms(phone, message):
result = subprocess.run([
'python', '/path/to/respond_sms.py',
'--to', phone,
'--message', message,
'--json'
], capture_output=True, text=True)
return json.loads(result.stdout)
# Use it
response = send_sms("+19152134309", "Hello from Python!")
print(response['message_sid'])
Troubleshooting
"Module not found: twilio"
pip install -r requirements.txt
"Authentication failed"
- •
Verify credentials are set:
bashecho $TWILIO_ACCOUNT_SID echo $TWILIO_AUTH_TOKEN
- •
Check credentials in Twilio Console:
- •
Ensure auth token is not expired
"Connection refused" (gateway)
The Clawdbot gateway is not running:
# Check if gateway is running lsof -i :18789 # If not running, start it (in Clawdbot) clawdbot gateway start # Verify clawdbot gateway status
"Invalid Twilio signature"
Webhook validation failed. Either:
- •Request really didn't come from Twilio (security issue)
- •
TWILIO_AUTH_TOKENis wrong in your.env - •You're testing with curl (signatures are per-request)
To disable validation temporarily (testing only):
# In webhook_server.py, change:
if not validate_twilio_signature(request):
return Response("Unauthorized", status=401)
# To:
# Temporarily disabled for testing
# if not validate_twilio_signature(request):
# return Response("Unauthorized", status=401)
"SMS failed to send"
- •Check recipient number format:
+19152134309(E.164) - •Verify Twilio account has SMS enabled
- •Check account balance
- •Review Twilio logs: https://www.twilio.com/console/logs
"No conversations found"
The webhook hasn't received any messages yet.
- •
Test webhook is running:
bashcurl http://localhost:5000/health
- •
Verify ngrok/Tailscale tunnel is active
- •
Check Twilio phone number config points to webhook URL
- •
Try sending a test SMS from your phone
"Webhook server won't start"
Port 5000 is already in use:
# Find what's using port 5000 lsof -i :5000 # Kill the process kill -9 <PID> # Or use different port python webhook_server.py --port 5001
Need help?
- •Check logs:
tail -f ~/.clawd/twilio_webhook.log - •Enable debug:
python webhook_server.py --debug - •Test with curl:
bash
curl -X POST http://localhost:5000/health
- •Review Twilio docs: https://www.twilio.com/docs/sms/quickstart
Costs
- •SMS: $0.0075 per message
- •Voice calls: $0.013 - $0.025 per minute
- •ElevenLabs TTS: $0.15 per 1M characters
Cost control:
- •Keep messages short
- •Avoid unnecessary API calls
- •Monitor usage in Twilio Console
- •Set account alerts for high usage
Phone Number Format
All phone numbers use E.164 format:
+[Country Code][Area Code][Number]
Examples:
- •US:
+1 915 223 7302→+19152237302 - •UK:
+44 20 7122 3467→+442071223467 - •Canada:
+1 416 555 1234→+14165551234
Scripts accept:
- •E.164:
+19152134309✓ - •10-digit US:
9152134309✓ (auto-converted to+19152134309) - •Formatted:
(915) 213-4309✓ (spaces/dashes removed) - •Invalid:
1-915-213-4309✗ (use+1prefix)
Related Skills
- •Universal Voice Agent - Goal-oriented calling with real-time voice
- •Jami Skill - Peer-to-peer voice communication
- •Sentry Mode - Webcam surveillance with voice alerts
License
MIT - Use freely in your projects
Support
Twilio Documentation:
- •SMS: https://www.twilio.com/docs/sms
- •Voice: https://www.twilio.com/docs/voice
- •Webhooks: https://www.twilio.com/docs/usage/webhooks
Twilio Console:
ElevenLabs Documentation:
Last updated: 2024-02-03