Mock Twilio service and Go SDK wrapper for local development and testing.
- SMS Messaging: Send SMS via Twilio Messaging API v2010
- Verify (SMS): Twilio Verify v2
Verifications+VerificationCheckwith seedable per-number behavior - Lookup: Twilio Lookup v2
line_type_intelligencedriven by the same seed - Delivery Simulation: Automatic status callbacks for tracking
- SDK Wrapper: Simplified Go client wrapping
github.com/twilio/twilio-go - Configurable Modes: dev, test, prod-like behavior
- Compatible: Drop-in replacement for real Twilio API
# Simple docker run
just docker
# Run locally
just run
# Run tests
just ciCredentials must use proper Twilio format:
- AccountSID: AC + 32 hex characters
- AuthToken: 32 hex characters
ACCOUNT_SID=AC0123456789abcdef0123456789abcdef
AUTH_TOKEN=0123456789abcdef0123456789abcdef
FROM_NUMBER=+15551234567
PORT=8080
MODE=dev # dev, test, prod-like
DELAY=1s # Delivery delay
FAILURE_RATE=0.0 # 0.0 to 1.0account_sid: ACtest
auth_token: test_token
from_number: "+15551234567"
port: 8080
mode: dev
delay: 1s
failure_rate: 0.0package main
import (
"context"
"log"
"github.com/46labs/twilio/pkg/client"
)
func main() {
cfg := client.Config{
AccountSID: "ACtest",
AuthToken: "test_token",
BaseURL: "http://localhost:8080", // or https://api.twilio.com
FromNumber: "+15551234567",
}
c, err := client.New(cfg)
if err != nil {
log.Fatal(err)
}
req := &client.SendSMSRequest{
To: "+14155552345",
Body: "Hello from Twilio!",
}
resp, err := c.SendSMS(context.Background(), req)
if err != nil {
log.Fatal(err)
}
log.Printf("Message sent: %s (status: %s)", resp.SID, resp.Status)
}POST /2010-04-01/Accounts/{AccountSid}/Messages.json
Authorization: Basic {base64(AccountSid:AuthToken)}
Content-Type: application/x-www-form-urlencoded
To=+14155552345
From=+15551234567
Body=Message text
StatusCallback=https://example.com/callback (optional)
Response (201):
{
"sid": "SMxxxxx",
"account_sid": "ACtest",
"from": "+15551234567",
"to": "+14155552345",
"body": "Message text",
"status": "queued",
"direction": "outbound-api",
"date_created": "Mon, 12 Jan 2026 12:00:00 +0000",
"date_sent": null,
"date_updated": "Mon, 12 Jan 2026 12:00:00 +0000"
}GET /2010-04-01/Accounts/{AccountSid}/Messages/{MessageSid}.json
Authorization: Basic {base64(AccountSid:AuthToken)}
POST /v2/Services/{ServiceSid}/Verifications
Authorization: Basic {base64(AccountSid:AuthToken)}
Content-Type: application/x-www-form-urlencoded
To=+15551230001
Channel=sms
Per-number behavior is driven by the phone_numbers seed (see Configuration):
line_type: landline→ 400 with Twilio error60205(SMS not supported by landline)valid: false→ 400 with Twilio error60200(invalidTo)- otherwise → 201, status
pending, a 6-digit code is generated and stored
Generated codes are visible at GET /status/verify for tests.
POST /v2/Services/{ServiceSid}/VerificationCheck
Authorization: Basic {base64(AccountSid:AuthToken)}
Content-Type: application/x-www-form-urlencoded
To=+15551230001
Code=123456
Matches Twilio behavior: a wrong code returns 200 with status: pending (so the
caller can retry); a correct code returns status: approved, valid: true. If
no pending verification exists for To/VerificationSid, 404 is returned.
GET /v2/PhoneNumbers/{E164}?Fields=line_type_intelligence
Authorization: Basic {base64(AccountSid:AuthToken)}
Returns the seeded classification (mobile, landline, nonFixedVoip, etc.) and
carrier metadata. Unseeded numbers default to type: mobile. Numbers seeded with
valid: false return 404.
GET /status # List all SMS messages
DELETE /status # Clear SMS messages
GET /status/verify # List verifications (includes the generated code)
DELETE /status/verify # Clear verifications
GET /status/lookup # List seeded phone numbers
DELETE /status/lookup # Clear seeded phone numbers
GET /status/email # List emails
DELETE /status/email # Clear emails
The phone_numbers block in config.yaml (or unmarshalled via
config.WithPhoneNumbers(...)) drives both Verify and Lookup:
phone_numbers:
- phone_number: "+15551230001"
line_type: mobile # mobile | landline | fixedVoip | nonFixedVoip | tollFree | personal | unknown
carrier_name: "Verizon"
mobile_country_code: "310"
mobile_network_code: "012"
valid: true
- phone_number: "+15551230002"
line_type: landline # Verify will reject SMS to this number with 60205
valid: true
- phone_number: "+15551239999"
valid: false # Lookup 404 + Verify 60200- dev: All messages succeed immediately, 1s delay
- test: Support failure injection via headers
- prod-like: Random failures, realistic delays
Add to your go.mod:
require github.com/46labs/twilio v0.1.0Configure for your service:
TWILIO_ACCOUNT_SID=ACtest
TWILIO_AUTH_TOKEN=test_token
TWILIO_BASE_URL=http://localhost:8080 # Mock
# TWILIO_BASE_URL=https://api.twilio.com # Production
TWILIO_FROM_NUMBER=+15551234567# Run all tests
just ci
# Or directly
go test -v ./...
# With linting
golangci-lint run# Local binary
go build -o bin/twilio ./cmd
# Docker image
docker build -t twilio:dev .
# Multi-arch (via CI)
# Automatically builds for linux/amd64 and linux/arm64- Local Development: Replace Twilio in development environments
- Testing: Automated testing without external dependencies
- CI/CD: Integration tests with SMS delivery simulation
- Demos: Quick setup for proof-of-concepts
This is a mock service for development and testing. Key differences:
- No Actual SMS: Messages are simulated, not sent
- In-Memory Storage: Messages are not persisted
- Simplified API: Only essential Messaging API endpoints
- No Rate Limiting: Unlimited requests
- No Production Security: Use only for development/testing
See LICENSE for details.