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:
echo $TWILIO_ACCOUNT_SID echo $TWILIO_AUTH_TOKENCheck 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:
curl http://localhost:5000/healthVerify 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:
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