Setup overview
- Create a Twilio account
- Get Account SID + Auth Token
- Buy/verify a sender phone number
Store secrets in env vars:
TWILIO_ACCOUNT_SIDTWILIO_AUTH_TOKEN
Example
import os
# from twilio.rest import Client
def send_sms(to: str, body: str):
account_sid = os.environ["TWILIO_ACCOUNT_SID"]
auth_token = os.environ["TWILIO_AUTH_TOKEN"]
# client = Client(account_sid, auth_token)
# msg = client.messages.create(
# body=body,
# from_="+15551234567",
# to=to,
# )
# return msg.sid
# send_sms("+15550001111", "hello")Notes
- keep rate limits in mind
- never hardcode auth tokens
How Twilio sends a message
Twilio is a service that connects your code to phone networks. You do not talk to carriers yourself. Your script calls Twilio's REST API with your credentials, and Twilio delivers the SMS. You need three things from the Twilio console:
- Account SID, which identifies your account.
- Auth Token, which is the secret. Treat it like a password.
- A sender, meaning a Twilio phone number or an approved alphanumeric sender ID.
A trial account can only send to phone numbers you have verified, and messages are prefixed with a trial notice. That is enough for learning and for alerts to yourself.
The official twilio package (pip install twilio) wraps the API. The example on this page has the client lines commented out so it can be read without an account. Here is the complete, working version:
import os
from twilio.base.exceptions import TwilioRestException
from twilio.rest import Client
def send_sms(to: str, body: str) -> str | None:
client = Client(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"])
try:
message = client.messages.create(
body=body[:1500], # long texts are split and billed per segment
from_=os.environ["TWILIO_FROM_NUMBER"],
to=to,
)
except TwilioRestException as err:
print("Twilio error", err.code, err.msg)
return None
print("queued", message.sid, message.status)
return message.sid
# send_sms("+15550001111", "Backup finished: 3 files copied")from_ has a trailing underscore because from is a Python keyword. Phone numbers must be in E.164 format: a plus sign, the country code, and the number with no spaces or dashes, like +14155552671. The status queued means Twilio accepted the message. It does not mean the phone received it. To confirm delivery, fetch the message later with client.messages(sid).fetch().status, or set a status callback URL.
Cost and limits
Every message costs money, and long messages cost more. An SMS segment holds 160 characters of basic text, or 70 if it contains emoji or other non-GSM characters. Longer text is split into more segments, and each is billed. A bug that sends a message in a loop can spend your balance quickly. Add a limit on how many messages a script can send per run, and use Twilio's usage triggers or a low balance on the account as a safety net.
Rules you must follow
In many countries you may only message people who agreed to receive texts, and US messages to US numbers require registration (A2P 10DLC). Check Twilio's compliance pages for your region before using it for anything beyond alerts to yourself or your own team. Include opt-out instructions for any recurring message.
Common mistakes
- Committing the auth token. Rotate it in the console if it leaks.
- Wrong number format. Missing the
+and country code returns an error. - Sending to unverified numbers on a trial account.
- Assuming
queuedmeans delivered. - No cap on messages. Put a maximum in the loop.
Check your understanding
Which value must be kept secret and out of source control?
The Auth Token authorises actions on your account. Load it from an environment variable and rotate it if it is ever exposed.
Show answer
A — The Auth Token — The Auth Token authorises actions on your account. Load it from an environment variable and rotate it if it is ever exposed.
Which is a valid E.164 phone number?
E.164 is a plus sign, the country code, and digits with no spaces or punctuation.
Show answer
B — +14155552671 — E.164 is a plus sign, the country code, and digits with no spaces or punctuation.
The API returns status queued for your message. What does that tell you?
queued means Twilio has accepted the request. Delivery status arrives later and can be fetched or received by callback.
Show answer
B — Twilio accepted it for sending, but delivery is not confirmed — queued means Twilio has accepted the request. Delivery status arrives later and can be fetched or received by callback.