better-call-claude
v2.1.1
Published
Bi-directional communication for Claude Code via Voice Calls, SMS, and WhatsApp. Cross-channel context sharing between voice and WhatsApp.
Maintainers
Readme
Better Call Claude
📞 "Hey Claude, refactor the auth module and call me when you're done or need a decision."
[20 minutes later, phone rings]
🤖 "I've finished the refactor but found a security issue. Should I fix it now or create a ticket?"
💬 Or via SMS/WhatsApp:
You: "Claude, how's the deployment going?"
Claude: "Deployment complete. 3 services updated, all health checks passing."
Features
- 📱 Inbound calls - Call Claude Code from your phone to start tasks
- 📲 Outbound calls - Claude calls you when done, stuck, or needs decisions
- 💬 SMS messaging - Send and receive text messages with Claude
- 📱 WhatsApp - Full WhatsApp Business integration
- 🔄 Cross-channel context - Start on voice, continue on WhatsApp seamlessly
- 🔗 Persistent sessions - Claude stays alive listening for WhatsApp messages
- 🔒 Secure transports - Choose ngrok (easy) or Tailscale (enterprise-grade)
- 🗣️ Natural conversations - Multi-turn interactions across all channels
- 🔧 Tool composable - Claude can use other tools while communicating
- ⚡ Auto-webhook updates - Twilio webhooks auto-update when ngrok URL changes
- ⌚ Works anywhere - Phone, smartwatch, or any device
⚠️ Testing Status
| Channel | Provider | Status | |---------|----------|--------| | Voice | Twilio | ✅ Tested & Working | | WhatsApp | Twilio Sandbox | ✅ Tested & Working | | SMS | Twilio | ⏳ Pending A2P 10DLC verification | | Voice | Telnyx | 🔬 Not yet tested | | SMS | Telnyx | 🔬 Not yet tested | | WhatsApp | Telnyx | 🔬 Not yet tested |
Contributions welcome for testing other provider/channel combinations!
Quick Start
1. Get Required Accounts
| Service | Purpose | Cost | |---------|---------|------| | Telnyx or Twilio | Phone calls | ~$1/mo + usage | | OpenAI | Speech-to-text & text-to-speech | ~$0.03/min | | ngrok OR Tailscale | Webhook tunneling | Free tier available |
2. Set Up Phone Provider
- Create account at portal.telnyx.com and verify identity
- Buy a phone number (~$1/month)
- Create a Voice API application:
- Set webhook URL to your tunnel URL +
/webhook/telnyx/inbound - Set API version to v2
- Set webhook URL to your tunnel URL +
- Enable SMS on your phone number:
- Go to Messaging and create a Messaging Profile
- Assign your phone number to the profile
- Set SMS webhook URL to your tunnel URL +
/webhook/telnyx/sms
- Enable WhatsApp (optional):
- Go to WhatsApp in portal
- Complete WhatsApp Business verification
- Set webhook URL to your tunnel URL +
/webhook/telnyx/whatsapp
- Verify your phone number for outbound calls
- Get your Connection ID (or Messaging Profile ID) and API Key
- Create account at twilio.com/console
- Buy a phone number
- Configure webhooks for your number:
- Voice webhook: your tunnel URL +
/webhook/twilio/inbound - SMS webhook: your tunnel URL +
/webhook/twilio/sms
- Voice webhook: your tunnel URL +
- Enable WhatsApp (optional):
- Go to WhatsApp Senders
- Complete WhatsApp Business setup
- Set webhook URL to your tunnel URL +
/webhook/twilio/whatsapp
- Get your Account SID and Auth Token
3. Choose Your Transport
Pros: Instant setup, works everywhere, no network configuration
- Sign up at ngrok.com
- Copy your auth token from the dashboard
- Set
BETTERCALLCLAUDE_TRANSPORT=ngrokin your config
Pros: No public URLs, works behind firewalls, stable addresses, enterprise-grade security
Requirements: Tailscale installed on both your computer AND your phone provider must support private webhooks (or use Tailscale Funnel)
- Install Tailscale on your machine
- Enable Tailscale Funnel for public webhook access:
tailscale funnel 3333 - Or use a relay server on your Tailscale network
4. Install Better Call Claude
# Quick start with bunx (recommended)
bunx better-call-claude
# Or install globally
bun install -g better-call-claude
better-call-claude5. Add to Claude Code
Add to ~/.claude/settings.json:
{
"mcpServers": {
"better-call-claude": {
"command": "bunx",
"args": ["better-call-claude"],
"env": {
"BETTERCALLCLAUDE_PHONE_PROVIDER": "telnyx",
"BETTERCALLCLAUDE_PHONE_ACCOUNT_SID": "your-connection-id",
"BETTERCALLCLAUDE_PHONE_AUTH_TOKEN": "your-api-key",
"BETTERCALLCLAUDE_PHONE_NUMBER": "+15551234567",
"BETTERCALLCLAUDE_USER_PHONE_NUMBER": "+15559876543",
"BETTERCALLCLAUDE_OPENAI_API_KEY": "sk-...",
"BETTERCALLCLAUDE_TRANSPORT": "ngrok",
"BETTERCALLCLAUDE_NGROK_AUTHTOKEN": "your-ngrok-token"
}
}
}
}Restart Claude Code. Done!
Environment Variables
Required Variables
| Variable | Description |
|----------|-------------|
| BETTERCALLCLAUDE_PHONE_PROVIDER | telnyx or twilio |
| BETTERCALLCLAUDE_PHONE_ACCOUNT_SID | Provider account/connection ID |
| BETTERCALLCLAUDE_PHONE_AUTH_TOKEN | Provider API key/auth token |
| BETTERCALLCLAUDE_PHONE_NUMBER | Your Telnyx/Twilio phone number (E.164) |
| BETTERCALLCLAUDE_WHATSAPP_NUMBER | WhatsApp number if different (e.g., Twilio Sandbox) |
| BETTERCALLCLAUDE_USER_PHONE_NUMBER | Your personal phone number |
| BETTERCALLCLAUDE_OPENAI_API_KEY | OpenAI API key for TTS/STT |
| BETTERCALLCLAUDE_TRANSPORT | ngrok or tailscale |
Transport-Specific Variables
For ngrok:
| Variable | Default | Description |
|----------|---------|-------------|
| BETTERCALLCLAUDE_NGROK_AUTHTOKEN | - | ngrok auth token (required) |
| BETTERCALLCLAUDE_NGROK_DOMAIN | - | Custom domain (paid feature) |
For Tailscale:
| Variable | Default | Description |
|----------|---------|-------------|
| BETTERCALLCLAUDE_TAILSCALE_HOSTNAME | auto | Your Tailscale hostname |
| BETTERCALLCLAUDE_TAILSCALE_USE_FUNNEL | false | Use Tailscale Funnel for public access |
| BETTERCALLCLAUDE_TAILSCALE_FUNNEL_PORT | 443 | Funnel port |
Optional Variables
| Variable | Default | Description |
|----------|---------|-------------|
| BETTERCALLCLAUDE_TTS_VOICE | onyx | OpenAI voice (alloy, echo, fable, onyx, nova, shimmer) |
| BETTERCALLCLAUDE_PORT | 3333 | Local HTTP server port |
| BETTERCALLCLAUDE_TRANSCRIPT_TIMEOUT_MS | 180000 | Speech timeout (3 min) |
| BETTERCALLCLAUDE_STT_SILENCE_DURATION_MS | 800 | End-of-speech detection |
Usage
Voice Calls
You → Claude (Inbound Calls)
Call your Telnyx/Twilio phone number from your personal phone:
📱 "Hey Claude, I need you to write unit tests for the payment module. Call me when you're done."
Claude will acknowledge and start working. When done, it calls you back.
Claude → You (Outbound Calls)
Claude can initiate calls when it needs your input:
🤖 "I found 3 different approaches for the caching layer. Want me to explain them so you can choose?"
Voice Commands During Calls
- "Hang up" or "Goodbye" - End the call
- "Hold on" - Claude waits for you to continue
- "Go ahead" - Claude continues with the task
- "Cancel that" - Abort current action
SMS Messaging
You → Claude (Inbound SMS)
Text your Telnyx/Twilio number:
💬 "Hey Claude, what's the status of the deployment?"
Claude will respond via SMS:
🤖 "Deployment is 80% complete. Running integration tests now. ETA: 5 minutes."
Claude → You (Outbound SMS)
Claude can send you text updates:
🤖 "Build failed on line 42 of auth.ts. Reply with 'fix' to auto-fix or 'skip' to continue."
You → Claude (Inbound WhatsApp)
Send a WhatsApp message to your business number:
💬 "Show me the error logs from the last hour"
Claude responds in WhatsApp:
🤖 "Found 3 errors:\n1. Connection timeout at 14:32\n2. Auth failure at 14:45\n3. Rate limit at 15:01\n\nWant me to investigate any of these?"
Claude → You (Outbound WhatsApp)
Claude can send rich WhatsApp messages:
🤖 "Code review complete! Found 2 issues:\n• Line 23: Unused variable\n• Line 67: Missing error handling\n\nReply 'fix' to auto-fix or 'details' for more info."
Cross-Channel Context
Start a task on voice and seamlessly continue on WhatsApp - Claude remembers everything.
Example Flow
Call Claude:
📞 "Hey Claude, run the todo app in dev mode and let's continue on WhatsApp"
Claude starts the app and enters WhatsApp listening mode:
🤖 "Todo app running on port 5173. Send me WhatsApp messages for more instructions."
Send WhatsApp message:
💬 "Expose it via localtunnel and add the URL to allowed hosts"
Claude responds via WhatsApp:
🤖 "Done! Localtunnel URL: https://xyz.loca.lt - I've added it to vite.config.ts allowedHosts"
Continue the conversation:
💬 "What's the public IP so I can access it remotely?"
🤖 "Your public IP is 203.0.113.42. Access the app at https://xyz.loca.lt"
Key phrases to trigger WhatsApp listening:
- "Continue on WhatsApp"
- "Let's talk on WhatsApp"
- "Listen for my WhatsApp messages"
WhatsApp Sandbox (Twilio)
For testing, you can use Twilio's WhatsApp Sandbox instead of a full WhatsApp Business account.
- Go to Twilio Console > Messaging > WhatsApp Sandbox
- Send the join code to the sandbox number (+1 415 523 8886)
- Set the webhook URL to
{your-ngrok-url}/webhook/twilio/whatsapp - Add to your config:
"BETTERCALLCLAUDE_WHATSAPP_NUMBER": "+14155238886"
Note: Sandbox requires re-joining every 72 hours.
How It Works
┌─────────────────┐ ┌──────────────────────────────────┐
│ Your Phone │────────>│ Phone Provider │
│ 📞 Voice │<────────│ (Telnyx/Twilio) │
│ 💬 SMS │ │ │
│ 📱 WhatsApp │ │ • Voice API │
└─────────────────┘ │ • Messaging API │
│ • WhatsApp Business API │
└──────────────┬───────────────────┘
│ webhooks
▼
┌──────────────────────────────────┐
│ Transport Layer │
│ (ngrok / Tailscale Funnel) │
└──────────────┬───────────────────┘
│
▼
┌─────────────────┐ ┌──────────────────────────────────┐
│ Claude Code │◄──────► │ Better Call Claude MCP Server │
│ (your IDE) │ stdio │ (local, port 3333) │
└─────────────────┘ │ │
│ • Voice handling │
│ • SMS handling │
│ • WhatsApp handling │
│ • Conversation management │
└──────────────┬───────────────────┘
│
▼
┌──────────────────────────────────┐
│ OpenAI API │
│ (Whisper STT + TTS) │
│ (Voice calls only) │
└──────────────────────────────────┘Communication Flows
Voice:
- Inbound: You call → Provider → Webhook → MCP Server → Claude Code
- Outbound: Claude Code → MCP Server → Provider → Your phone rings
- Speech: Your voice → Whisper STT → Text → Claude → TTS → Audio playback
SMS:
- Inbound: You text → Provider → Webhook → MCP Server → Claude Code
- Outbound: Claude Code → MCP Server → Provider API → SMS delivered
WhatsApp:
- Inbound: You message → Provider → Webhook → MCP Server → Claude Code
- Outbound: Claude Code → MCP Server → Provider API → WhatsApp delivered
MCP Tools
Voice Tools
receive_inbound_call
Accept and process an incoming call from the user.
initiate_call
Start a phone call to the user.
const { callId, response } = await initiate_call({
message: "Hey! I finished the refactor. What should I work on next?"
});continue_call
Continue an active call with follow-up messages.
const response = await continue_call({
call_id: callId,
message: "Got it. Should I also add the caching layer?"
});speak_to_user
Speak without waiting for a response (for acknowledgments).
await speak_to_user({
call_id: callId,
message: "Let me search for that. One moment..."
});end_call
End an active call.
await end_call({
call_id: callId,
message: "Perfect, I'll get started. Talk soon!"
});get_call_status
Check status of current or recent calls.
const status = await get_call_status({ call_id: callId });
// { state: "active", duration: 45, transcript: [...] }Messaging Tools
receive_inbound_message
Check for incoming SMS or WhatsApp messages.
const result = await receive_inbound_message({
channel: "any", // "sms", "whatsapp", or "any"
timeout_ms: 5000 // How long to wait
});
// { success: true, channel: "sms", conversation_id: "...", message: "Deploy now" }send_sms
Send an SMS message to the user.
const result = await send_sms({
message: "Build complete! 42 tests passed.",
wait_for_reply: true,
timeout_ms: 180000
});
// { success: true, conversation_id: "...", reply: "Great, deploy it" }send_whatsapp
Send a WhatsApp message to the user.
const result = await send_whatsapp({
message: "Found 3 issues in code review:\n• Issue 1\n• Issue 2\n• Issue 3",
wait_for_reply: true
});
// { success: true, conversation_id: "...", reply: "Fix issue 1 first" }reply_to_conversation
Reply to an existing conversation (works for voice, SMS, or WhatsApp).
const result = await reply_to_conversation({
conversation_id: "abc-123",
message: "Got it, fixing issue 1 now.",
wait_for_reply: false
});get_conversation_history
Get the full message history for any conversation.
const history = await get_conversation_history({
conversation_id: "abc-123"
});
// { success: true, channel: "whatsapp", messages: [...], state: "active" }Costs
Voice Calls
| Service | Cost | |---------|------| | Telnyx outbound calls | ~$0.007/min | | Twilio outbound calls | ~$0.014/min | | OpenAI Whisper (STT) | ~$0.006/min | | OpenAI TTS | ~$0.015/1K chars |
Typical voice conversation: ~$0.03-0.05/minute
SMS
| Service | Cost | |---------|------| | Telnyx SMS (US) | ~$0.004/message | | Twilio SMS (US) | ~$0.0079/message |
Typical SMS exchange: ~$0.01-0.02/exchange
| Service | Cost | |---------|------| | Telnyx WhatsApp | ~$0.005/message | | Twilio WhatsApp | ~$0.005/message + conversation fees |
Typical WhatsApp exchange: ~$0.01-0.02/exchange
Infrastructure
| Service | Cost | |---------|------| | Phone number | ~$1/month | | ngrok | Free tier available | | Tailscale | Free for personal use |
Security Considerations
With ngrok
- Public URLs can be discovered (use custom domains for production)
- Webhook signatures verified by default
- Consider IP allowlisting in ngrok dashboard
With Tailscale
- No public exposure by default
- Funnel creates public endpoint but traffic routes through Tailscale
- Integrates with SSO/SCIM for enterprise
- Audit logs available
General
- Phone numbers are never logged
- Call transcripts are ephemeral (cleared on restart)
- Use environment variables, never hardcode credentials
Troubleshooting
Voice Issues
Claude doesn't answer calls
- Check the MCP server is running:
claude --debug - Verify webhook URL is configured in provider dashboard
- Ensure transport (ngrok/Tailscale) is active
Can't make outbound calls
- Verify
BETTERCALLCLAUDE_USER_PHONE_NUMBERis correct - Check phone number is verified with provider
- Ensure sufficient balance in provider account
Audio quality issues
- Check network connectivity
- Try different TTS voice:
BETTERCALLCLAUDE_TTS_VOICE=nova - Adjust silence detection:
BETTERCALLCLAUDE_STT_SILENCE_DURATION_MS=1000
SMS Issues
SMS not being received
- Verify SMS is enabled on your phone number in provider dashboard
- Check SMS webhook URL is set:
/webhook/telnyx/smsor/webhook/twilio/sms - Verify Messaging Profile is assigned to phone number (Telnyx)
Can't send outbound SMS
- Check phone number has SMS capability
- Verify destination number format (E.164: +15551234567)
- Check provider account balance
WhatsApp Issues
WhatsApp messages not received
- Verify WhatsApp Business is set up in provider portal
- Check webhook URL:
/webhook/telnyx/whatsappor/webhook/twilio/whatsapp - Ensure WhatsApp Business verification is complete
Can't send WhatsApp messages
- User must have messaged you first (WhatsApp 24-hour rule)
- Check WhatsApp Business approval status
- Verify message template compliance (for outbound-first messages)
Transport Issues
Tailscale Funnel not working
- Ensure Funnel is enabled:
tailscale funnel status - Check ACLs allow Funnel
- Verify HTTPS certificate is valid
ngrok tunnel disconnecting
- Upgrade to paid plan for stable URLs
- Use custom domain:
BETTERCALLCLAUDE_NGROK_DOMAIN - Check ngrok dashboard for connection limits
Development
# Clone the repo
git clone https://github.com/sns45/better-call-claude
cd better-call-claude
# Install dependencies
bun install
# Run in development mode
bun run devTesting locally
# Start the MCP server
bun run dev
# In another terminal, test with MCP inspector
npx @anthropics/mcp-inspectorTech Stack
- Runtime: Bun - Fast JavaScript runtime
- Web Framework: Hono - Lightweight, fast web framework
- Phone: Telnyx / Twilio - Telephony APIs
- Speech: OpenAI Whisper - STT/TTS
- Transport: ngrok / Tailscale - Tunneling
- Protocol: MCP - Model Context Protocol
Contributing
PRs welcome! Please see CONTRIBUTING.md for guidelines.
License
MIT
