twilio.twins.la
A digital twin of the Twilio SMS and SendGrid Email APIs.
What is this?
This is a high-fidelity digital twin of Twilio's SMS API and SendGrid's Email API. Code you write against this twin will work against the real Twilio and SendGrid with only hostname and credential changes. No Twilio account needed to develop.
Supported scenarios
- Send and receive SMS via the Twilio REST API
- Provision and manage phone numbers
- Webhook delivery with X-Twilio-Signature validation
- Message status progression (queued → sending → sent → delivered)
- TwiML auto-reply parsing
- Send email via SendGrid v3 Mail Send API
- API key authentication for email
How to use it
Cloud: Point your app at https://twilio.twins.la instead of
api.twilio.com. Create an account via
POST /_twin/accounts and use the returned credentials.
Local: Install with pip install twins-twilio-local
and run a local instance on any port. Same API, same behavior, your machine.
For agents
Copy this into your agent's system prompt, tool configuration, or CLAUDE.md.
Also available as plain text at
/_twin/agent-instructions.
# Twilio & SendGrid Twin — twilio.twins.la
A high-fidelity digital twin of the Twilio SMS and SendGrid Email APIs.
Code written against this twin works against the real Twilio/SendGrid
with only hostname and credential changes.
## Authentication
SMS/Voice API: HTTP Basic Auth
Username: Account SID (starts with AC)
Password: Auth Token
Email API: Bearer token
Header: Authorization: Bearer SG.{key_id}.{key_secret}
Twin Plane: HTTP Basic Auth (same credentials)
Most Twin Plane operations use the same AccountSid:AuthToken.
Exception: POST /_twin/accounts requires no auth (creates credentials).
Twin Plane Admin: Bearer token
Header: Authorization: Bearer <admin_token>
Service-wide operations (list all accounts, review feedback, etc.)
require an admin token set by the deployment owner.
Create credentials:
POST /_twin/accounts — no auth required, returns { sid, auth_token }
## Key Endpoints
Twin Plane (no auth):
GET /_twin/health — status check
GET /_twin/scenarios — supported scenarios
GET /_twin/settings — twin settings
GET /_twin/references — authoritative sources used to build this twin
POST /_twin/accounts — create account
Twin Plane (Basic Auth — use AccountSid:AuthToken):
GET /_twin/accounts — get your account details
GET /_twin/logs — your operation logs
POST /_twin/api-keys — create SendGrid API key
GET /_twin/emails — list your emails
POST /_twin/simulate/inbound — drive a signed inbound SMS or MMS
to the phone number's SmsUrl. Body:
{ account_sid, from, to, body,
num_segments?, num_media?,
media_urls?, media_content_types? }.
STOP/START/HELP carrier keywords are
auto-detected and applied to opt-out
state; HELP triggers a canned auto-reply.
POST /_twin/simulate/status — force a status transition on a
tenant-owned outbound message and
fire its StatusCallback. Body:
{ message_sid, status, error_code?,
error_message? }. Status is one of
queued | sending | sent | delivered |
failed | undelivered. error_code is
required for failed and undelivered.
GET /_twin/media/<media_sid> — placeholder PNG for MMS scenarios
(twin-served, unauthenticated)
POST /_twin/feedback — submit feedback
GET /_twin/feedback — list your feedback
Twin Plane (Admin Bearer — service-wide access):
GET /_twin/accounts — list all accounts
GET /_twin/logs — all operation logs
GET /_twin/emails — all emails
GET /_twin/feedback — all feedback
POST /_twin/feedback/<id> — update any feedback (for review pipeline)
Twilio SMS API (Basic Auth):
POST /2010-04-01/Accounts/{AccountSid}/Messages.json — send SMS
GET /2010-04-01/Accounts/{AccountSid}/Messages.json — list messages
GET /2010-04-01/Accounts/{AccountSid}/Messages/{Sid}.json — get message
POST /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers.json — provision number
GET /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers.json — list numbers
SendGrid Email API (Bearer Auth):
POST /v3/mail/send — send email (returns 202, X-Message-Id header)
## Quick Start
1. Create an account (no auth needed):
curl -X POST https://twilio.twins.la/_twin/accounts \
-H "Content-Type: application/json" \
-d '{"friendly_name": "My App"}'
2. Send an SMS (use sid and auth_token from step 1):
curl -X POST https://twilio.twins.la/2010-04-01/Accounts/{sid}/Messages.json \
-u "{sid}:{auth_token}" \
-d "To=+15551234567" -d "From=+15559876543" -d "Body=Hello from twin"
3. Check logs (same credentials work for Twin Plane):
curl https://twilio.twins.la/_twin/logs \
-u "{sid}:{auth_token}"
4. Drive a signed inbound SMS against your registered SmsUrl (CI/smoke):
curl -X POST https://twilio.twins.la/_twin/simulate/inbound \
-u "{sid}:{auth_token}" \
-H "Content-Type: application/json" \
-d '{"account_sid":"{sid}","from":"+15559876543","to":"+15551112222","body":"hi"}'
The twin POSTs urlencoded form data to your SmsUrl with a valid
X-Twilio-Signature header computed against the *exact* registered URL.
Consumers that rebuild the request URL incorrectly behind a proxy
(e.g., ignoring X-Forwarded-Proto) will fail signature validation —
that is the bug class this control endpoint is designed to surface.
5. Force a delivery failure status and fire StatusCallback:
curl -X POST https://twilio.twins.la/_twin/simulate/status \
-u "{sid}:{auth_token}" \
-H "Content-Type: application/json" \
-d '{"message_sid":"SM...","status":"failed","error_code":30003}'
## Local Usage
pip install twins-twilio-local
python -c "
from twins_twilio_local.storage_sqlite import SQLiteStorage
from twins_twilio.app import create_app
storage = SQLiteStorage('twin.db')
app = create_app(storage=storage)
app.run(port=8080)
"
Then use http://localhost:8080 instead of https://twilio.twins.la.
## Feedback
We actively want your feedback. If you encounter a limitation, a missing
scenario, unexpected behavior, or have a feature request — please tell us.
Your feedback directly shapes what we build next.
Submit feedback (requires Basic Auth):
curl -X POST https://twilio.twins.la/_twin/feedback \
-u "{sid}:{auth_token}" \
-H "Content-Type: application/json" \
-d '{
"body": "Description of what you encountered",
"category": "bug",
"context": {"message_sid": "SM...", "error": "..."}
}'
Required field:
body — Describe what happened, what you expected, and what you need.
Optional fields:
category — One of: "bug", "missing-scenario", "feature-request", "general"
context — Any structured data that helps us understand the issue:
error codes, message SIDs, request/response snippets,
scenario names, or steps to reproduce.
Tips for useful feedback:
- Include IDs (message SIDs, error codes) when reporting failures.
- Describe the scenario you were trying to accomplish, not just the error.
- If a feature is missing, describe your use case — what you'd build with it.
- "I tried X, expected Y, got Z" is the most useful format for bugs.
Don't hold back. Even if you're unsure whether something is a bug or a
limitation, submit it. Feedback about confusing documentation, awkward APIs,
or missing examples is just as valuable as bug reports.
## Reference
Detailed docs: https://github.com/twins-la/twilio
Project overview: https://twins.la
All twins: https://github.com/twins-la/twins-la