A lightweight Django web app for configuring and testing Twilio SMS credentials. Supports both Auth Token and API Key authentication methods. Enter your credentials, save them, then fire a test message to any number — all from a single-page UI. All secrets are encrypted at rest using Fernet symmetric encryption.
- Two auth methods — Auth Token (quick setup) and API Keys (recommended for production)
- Credential management — Both credential sets stored independently in local SQLite
- Encrypted at rest — Auth token and API key secret are Fernet-encrypted before being written to the database; never stored in plaintext
- Prefilled forms — Saved credentials load and prefill on every page visit
- Test SMS — Send a real message and see the full Twilio API response inline
- Debug output — Full Twilio response surfaced in the UI: status, error code, error message, price, direction, auth method used, and more
- Single-page UI — No full-page reloads; all interactions use
fetch()with inline status banners
| Auth Token | API Keys | |
|---|---|---|
| Credentials needed | Account SID + Auth Token | Account SID + API Key SID + API Key Secret |
| Recommended for | Quick testing / local dev | Production, CI/CD, team environments |
| Can be revoked individually | No — rotating the auth token affects everything | Yes — each key is independent |
| Scoped permissions | No | Yes (Standard or ReadOnly) |
| Twilio recommendation | — | Preferred |
Recommendation: Use Auth Token to get started quickly. Switch to API Keys once you're integrating with an application or sharing access with a team.
| Layer | Technology |
|---|---|
| Backend | Python 3.11+, Django 5.2 |
| Database | SQLite (Django default) |
| SMS | Twilio Python SDK 9.x |
| Encryption | cryptography — Fernet (AES-128-CBC + HMAC-SHA256) |
| Frontend | Tailwind CSS (CDN), Vanilla JS |
- Python 3.11 or higher
- A Twilio account with a phone number capable of sending SMS
- For Auth Token auth: your Account SID and Auth Token (found on the Twilio Console homepage)
- For API Key auth: an API Key SID + Secret (create one at Console → API keys & tokens)
git clone https://github.com/nicolasguzca/twilio-sms-python.git
cd twilio-sms-pythonpython3 -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
pip install -r requirements.txtDependencies (requirements.txt):
| Package | Version | Purpose |
|---|---|---|
Django |
5.2 | Web framework |
twilio |
9.10.6 | Official Twilio Python SDK |
cryptography |
44.0.0 | Fernet encryption for secrets at rest |
python-dotenv |
1.0.1 | Loads .env into the environment |
cp .env.example .envOpen .env and set the following:
DJANGO_SECRET_KEY=your-django-secret-key
FIELD_ENCRYPTION_KEY=your-fernet-encryption-key
DEBUG=TrueGenerate a Django secret key:
python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"Generate a Fernet encryption key:
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"These two keys are app secrets — keep them in
.envonly. The file is already in.gitignoreand must never be committed.
python manage.py migrateThis creates a local db.sqlite3 with two tables — one for Auth Token credentials and one for API Key credentials.
source .venv/bin/activate && python manage.py runserver| Screen | URL |
|---|---|
| Auth Token | http://localhost:8000 |
| API Keys | http://localhost:8000/api-key/ |
Subsequent runs: only
source .venv/bin/activate && python manage.py runserver— no need to repeat the setup steps.
Both screens are reachable via the tab nav at the top of the app. They work independently — credentials are saved and tested separately for each auth method.
Where to find your credentials: Twilio Console homepage — Account SID and Auth Token are shown at the top.
Fields:
| Field | Format | Description |
|---|---|---|
| Account SID | ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
Your Twilio account identifier |
| Auth Token | 32-char hex string | Master credential — keep it secret |
| From Phone Number | +15551234567 |
Your Twilio number (E.164, US only) |
- Enter your credentials and click Save Credentials
- A green banner confirms the save; the form prefills on every subsequent visit
- Enter a recipient number and click Send Test SMS
API Keys are Twilio's recommended authentication method for applications. Unlike the Auth Token, each key can be revoked independently, scoped to read-only access, and rotated without affecting other integrations.
Creating an API key:
- Go to Twilio Console → API keys & tokens
- Click Create API key
- Give it a friendly name, choose Standard type (or ReadOnly if you only need to read — but sending SMS requires Standard)
- Copy the SID (
SK...) and Secret — the secret is only shown once at creation time
Fields:
| Field | Format | Description |
|---|---|---|
| Account SID | ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
Still required — used in Twilio API URL paths even with key auth |
| API Key SID | SKxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
The key identifier, starts with SK |
| API Key Secret | 32-char string | The secret shown once on creation |
| From Phone Number | +15551234567 |
Your Twilio number (E.164, US only) |
- Enter your credentials and click Save API Key Credentials
- A green banner confirms the save; the API Key SID and Account SID prefill on every visit (the secret prefills too if saved)
- Enter a recipient number and click Send Test SMS
After sending a test SMS, a Twilio Response panel appears with the full API response:
{
"sid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"status": "queued",
"to": "+15559876543",
"from": "+15551234567",
"direction": "outbound-api",
"error_code": null,
"error_message": null,
"price": null,
"price_unit": "USD",
"date_created": "2026-05-07 18:00:00+00:00",
"auth_method": "api_key",
"api_key_sid": "SKxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}The
auth_methodandapi_key_sidfields appear only on the API Keys screen, confirming which key was used.
Message statuses:
| Status | Meaning |
|---|---|
queued |
Twilio accepted the message; delivery is in progress |
sent |
Passed to the carrier |
delivered |
Confirmed delivered (requires status callbacks to be configured) |
failed |
Twilio rejected it before sending — check error_code |
undelivered |
Carrier rejected it — check error_code |
Common error codes:
| Code | Meaning |
|---|---|
21211 |
Invalid To phone number |
21614 |
To number is not a mobile number capable of receiving SMS |
21408 |
Permission to send to this region not enabled on your account |
20003 |
Authentication failed — wrong credentials |
30003 |
Unreachable destination handset |
30005 |
Unknown destination handset |
twilio-sms-python/
├── .env # Local secrets (gitignored)
├── .env.example # Template for .env
├── .gitignore
├── manage.py
├── requirements.txt
├── config/ # Django project package
│ ├── settings.py # App settings, loads from .env
│ ├── urls.py # Root URL config
│ └── wsgi.py
└── sms/ # Main Django app
├── crypto.py # Fernet encrypt/decrypt helpers
├── models.py # TwilioConfig + TwilioApiKeyConfig singletons
├── views.py # 6 views — 3 per auth method
├── urls.py # App URL routes
├── migrations/
│ ├── 0001_initial.py # TwilioConfig table
│ └── 0002_twilioapikeyconfig.py # TwilioApiKeyConfig table
└── templates/
└── sms/
├── index.html # Auth Token screen
└── index_api_key.html # API Keys screen
sms/models.py — Two singleton models, each enforced via pk=1. TwilioConfig stores Auth Token credentials; TwilioApiKeyConfig stores API Key credentials. Saving always upserts the single row.
sms/crypto.py — Fernet encrypt/decrypt helpers. The FIELD_ENCRYPTION_KEY env var must be a valid 32-byte URL-safe base64 key. Both the auth token and API key secret are encrypted on every write and decrypted on every read.
sms/views.py — Six views:
| Method | Endpoint | Description |
|---|---|---|
GET |
/ |
Auth Token screen — prefills saved credentials |
POST |
/api/credentials/ |
Save Auth Token credentials |
POST |
/api/send-test/ |
Send test SMS via Auth Token |
GET |
/api-key/ |
API Keys screen — prefills saved credentials |
POST |
/api/credentials-api-key/ |
Save API Key credentials |
POST |
/api/send-test-api-key/ |
Send test SMS via API Key auth |
Saves Auth Token credentials.
Request:
{
"account_sid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"auth_token": "your_auth_token",
"from_number": "+15551234567"
}Response (success): { "success": true }
Response (error): { "success": false, "error": "Account SID must start with 'AC'" }
Sends a test SMS using saved Auth Token credentials.
Request: { "to_number": "+15559876543" }
Response (success):
{
"success": true,
"debug": {
"sid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"status": "queued",
"to": "+15559876543",
"from": "+15551234567",
"direction": "outbound-api",
"error_code": null,
"error_message": null,
"price": null,
"price_unit": "USD",
"date_created": "2026-05-07 18:00:00+00:00"
}
}Response (Twilio error):
{
"success": false,
"error": "The 'To' number is not a valid phone number.",
"debug": { "twilio_code": 21211, "status": 400, "details": {} }
}Saves API Key credentials.
Request:
{
"account_sid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"api_key_sid": "SKxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"api_key_secret": "your_api_key_secret",
"from_number": "+15551234567"
}Validation rules:
account_sidmust start withACapi_key_sidmust start withSKapi_key_secretmust be non-emptyfrom_numbermust be E.164 format (+1XXXXXXXXXX)
Response (success): { "success": true }
Response (error): { "success": false, "error": "API Key SID must start with 'SK'" }
Sends a test SMS using saved API Key credentials. Initializes the Twilio client as Client(api_key_sid, api_key_secret, account_sid).
Request: { "to_number": "+15559876543" }
Response (success):
{
"success": true,
"debug": {
"sid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"status": "queued",
"to": "+15559876543",
"from": "+15551234567",
"direction": "outbound-api",
"error_code": null,
"error_message": null,
"price": null,
"price_unit": "USD",
"date_created": "2026-05-07 18:00:00+00:00",
"auth_method": "api_key",
"api_key_sid": "SKxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}- Secrets encrypted at rest — Both the Auth Token and API Key Secret are encrypted with Fernet (AES-128-CBC + HMAC-SHA256) before being stored in SQLite. The encryption key lives only in
.env. - CSRF protection — All POST endpoints are protected by Django's CSRF middleware. The JS reads the
csrftokencookie and sends it as theX-CSRFTokenheader on every request. - Local use only —
ALLOWED_HOSTSis restricted tolocalhostand127.0.0.1. Do not expose this app to the public internet without adding authentication. - API Key Secret shown once — Twilio only displays the API Key Secret at creation time. Save it before closing the console window.
Run Django system checks:
python manage.py checkVerify Auth Token credentials are encrypted:
sqlite3 db.sqlite3 "SELECT account_sid, from_number, auth_token_encrypted FROM sms_twilioconfig;"Verify API Key credentials are encrypted:
sqlite3 db.sqlite3 "SELECT account_sid, api_key_sid, from_number, api_key_secret_encrypted FROM sms_twilioapikeyconfig;"Both _encrypted columns should contain Fernet ciphertext starting with gAAAAA — never the raw secret value.
Reset saved credentials:
# Auth Token
sqlite3 db.sqlite3 "DELETE FROM sms_twilioconfig;"
# API Keys
sqlite3 db.sqlite3 "DELETE FROM sms_twilioapikeyconfig;"MIT