AgentSkillsCN

Twilio

Twilio

SKILL.md

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

bash
cd ~/clawd/skills/twilio
pip install -r requirements.txt

Verify installation:

bash
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:

bash
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:

bash
# .env
TWILIO_ACCOUNT_SID=AC35fce9f5069e4a19358da26286380ca9
TWILIO_AUTH_TOKEN=a7700999dcff89b738f62c78bd1e33c1
TWILIO_PHONE_NUMBER=+19152237302
ELEVENLABS_API_KEY=sk_98316c1321b6263ab8d3fc46b8439c23b9fc076691d85c1a

Then load it:

bash
set -a; source .env; set +a

⚠️ Important: Never commit .env with real credentials. Add to .gitignore:

code
.env
.env.local
*.log

4. Verify Credentials

bash
# 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.

bash
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:

json
{
  "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.

bash
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:

json
{
  "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:

bash
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:

bash
python webhook_server.py --port 5000 --debug

Server Endpoints:

code
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:

bash
# 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:

bash
# 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:

code
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.

bash
# 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:

  1. Go to Phone Numbers → Active Numbers
  2. Click your number
  3. Set "A Call Comes In" → "Webhooks" → "Messaging"
  4. Paste: https://1a2b3c4d5e6f.ngrok.io/sms
  5. Save

Option B: Tailscale Funnel (Persistent)

Best for production use on your local machine.

bash
# 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:

  1. Go to Phone Numbers → Active Numbers
  2. Click your number
  3. Set messaging to: https://your-machine.tail12345.ts.net/sms
  4. Save

Option C: Static IP / Port Forwarding (Advanced)

If you have a static public IP and router access:

  1. Forward port 5000 to your local machine
  2. Use: https://your-public-ip:5000/sms
  3. Consider using a domain name + SSL cert for security

Running the System

Development Setup (All-in-One)

Terminal 1: Start webhook server

bash
cd ~/clawd/skills/twilio
source .env  # Load credentials
python webhook_server.py --port 5000 --debug

Terminal 2: Start ngrok tunnel

bash
ngrok http 5000

Terminal 3: Test sending a reply

bash
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

bash
tailscale up
tailscale funnel 5000

Terminal 2: Start webhook server

bash
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:

json
{
  "+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:

bash
python respond_sms.py --to "+19152134309" --view

Backup conversations:

bash
cp ~/.clawd/twilio_conversations.json ~/Desktop/twilio_backup.json

Clear history (careful!):

bash
rm ~/.clawd/twilio_conversations.json

Logs

Server logs are stored in ~/.clawd/twilio_webhook.log

View real-time logs:

bash
tail -f ~/.clawd/twilio_webhook.log

Parse logs:

bash
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

  1. Keep credentials secret:

    • Never share account SID or auth token
    • Rotate tokens regularly
    • Use .env with .gitignore
  2. Monitor usage:

  3. 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)
  4. Rate limiting:

    • Implement rate limiting for respond_sms.py if used by multiple users
    • Twilio has API rate limits; be aware of costs
  5. Data protection:

    • Conversation history stored locally (not encrypted)
    • Consider encrypting ~/.clawd/twilio_conversations.json if sensitive
    • Backup important conversations

Examples

Example 1: Automated Status Check

bash
#!/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

bash
#!/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

python
# 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"

bash
pip install -r requirements.txt

"Authentication failed"

  1. Verify credentials are set:

    bash
    echo $TWILIO_ACCOUNT_SID
    echo $TWILIO_AUTH_TOKEN
    
  2. Check credentials in Twilio Console:

  3. Ensure auth token is not expired

"Connection refused" (gateway)

The Clawdbot gateway is not running:

bash
# 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_TOKEN is wrong in your .env
  • You're testing with curl (signatures are per-request)

To disable validation temporarily (testing only):

bash
# 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"

  1. Check recipient number format: +19152134309 (E.164)
  2. Verify Twilio account has SMS enabled
  3. Check account balance
  4. Review Twilio logs: https://www.twilio.com/console/logs

"No conversations found"

The webhook hasn't received any messages yet.

  1. Test webhook is running:

    bash
    curl http://localhost:5000/health
    
  2. Verify ngrok/Tailscale tunnel is active

  3. Check Twilio phone number config points to webhook URL

  4. Try sending a test SMS from your phone

"Webhook server won't start"

Port 5000 is already in use:

bash
# 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?

  1. Check logs: tail -f ~/.clawd/twilio_webhook.log
  2. Enable debug: python webhook_server.py --debug
  3. Test with curl:
    bash
    curl -X POST http://localhost:5000/health
    
  4. 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:

code
+[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 +1 prefix)

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:

Twilio Console:

ElevenLabs Documentation:

Last updated: 2024-02-03