Python Central HubSign in

Automation and Scripting / Phase 4: Communication Automation

Sending SMS with Twilio API

Send SMS messages using Twilio’s API with a secure token workflow.

Setup overview

  • Create a Twilio account
  • Get Account SID + Auth Token
  • Buy/verify a sender phone number

Store secrets in env vars:

  • TWILIO_ACCOUNT_SID
  • TWILIO_AUTH_TOKEN

Example

Python
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:

Python
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 queued means delivered.
  • No cap on messages. Put a maximum in the loop.

Check your understanding

quizCheck yourself
  1. Which value must be kept secret and out of source control?

    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.

  2. Which is a valid E.164 phone number?

    Show answer

    B — +14155552671 — E.164 is a plus sign, the country code, and digits with no spaces or punctuation.

  3. The API returns status queued for your message. What does that tell you?

    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.

Was this page helpful?

Let us know how we did