Twilio

The Canvas SDK Twilio client provides a simple interface for sending SMS and MMS messages, managing phone numbers, and handling webhooks using the Twilio API.

Requirements #

Imports #

The Twilio client is included in the Canvas SDK. Import the necessary components:

from canvas_sdk.clients.twilio.libraries import SmsClient
from canvas_sdk.clients.twilio.structures import Settings, SmsMms, RequestFailed

Initialize the Client #

settings = Settings(
    account_sid="your_account_sid",
    key="your_api_key",
    secret="your_api_secret",
)
client = SmsClient(settings)

Send a Simple SMS #

from canvas_sdk.clients.twilio.libraries import SmsClient
from canvas_sdk.clients.twilio.structures import Settings, SmsMms, RequestFailed

# Initialize the client
settings = Settings(
    account_sid="ACxxxxxxxxxxxxxxxxx",
    key="SKxxxxxxxxxxxxxxxxx",
    secret="your_api_secret",
)
client = SmsClient(settings)

# First, get your phone number SID
phones = list(client.account_phone_numbers())
phone = phones[0]  # Use the first phone number
print(f"Using phone: {phone.phone_number} (SID: {phone.sid})")

# Create and send the SMS
sms = SmsMms(
    number_from=phone.phone_number,
    number_from_sid=phone.sid,
    number_to="+1234567890",
    message="Hello from Canvas SDK!",
    media_url="",
    status_callback_url="",
)

try:
    message = client.send_sms_mms(sms)
    print(f"Message sent! SID: {message.sid}, Status: {message.status.value}")
except RequestFailed as e:
    print(f"Failed to send: {e.message} (HTTP {e.status_code})")

Send an MMS with Image #

# Create an MMS with an image attachment
mms = SmsMms(
    number_from=phone.phone_number,
    number_from_sid=phone.sid,
    number_to="+1234567890",
    message="Check out this image!",
    media_url="https://example.com/image.jpg",
    status_callback_url="",
)

try:
    message = client.send_sms_mms(mms)
    print(f"MMS sent! SID: {message.sid}")
except RequestFailed as e:
    print(f"Failed to send: {e.message}")

Send SMS with Status Callback #

# Send SMS with a callback URL to track delivery status
sms = SmsMms(
    number_from=phone.phone_number,
    number_from_sid=phone.sid,
    number_to="+1234567890",
    message="Message with tracking",
    media_url="",
    status_callback_url="https://your-app.com/api/sms-status",
)

message = client.send_sms_mms(sms)
print(f"Message queued with callback. SID: {message.sid}")

Retrieve Message History #

from canvas_sdk.clients.twilio.constants import DateOperation

# Get all messages (no filters)
for message in client.retrieve_all_sms("", "", "", DateOperation.ON_EXACTLY):
    print(f"{message.date_sent}: {message.number_from} -> {message.number_to}: {message.body}")

# Get messages sent to a specific number
for message in client.retrieve_all_sms("+1234567890", "", "", DateOperation.ON_EXACTLY):
    print(f"To {message.number_to}: {message.body}")

# Get messages from a specific date onwards
for message in client.retrieve_all_sms("", "", "2024-01-01", DateOperation.ON_AND_AFTER):
    print(f"{message.date_sent}: {message.body}")

Handle Inbound Messages (Webhook) #

When Twilio receives an SMS to your number, it can call your webhook. Parse the callback data:

from canvas_sdk.clients.twilio.structures import StatusInbound, TwiMlMessage

def handle_inbound_sms(raw_body: str) -> str:
    """Process incoming SMS and return TwiML response."""
    # Parse the incoming message
    inbound = StatusInbound.callback_inbound(raw_body)

    print(f"Received from {inbound.number_from}: {inbound.body}")

    # Create a reply using TwiML
    if "hello" in inbound.body.lower():
        reply = TwiMlMessage.instance("Hello! Nice to hear from you!")
    else:
        reply = TwiMlMessage.instance("Thanks for your message!")

    return reply.to_xml()

Reply with MMS (TwiML) #

from canvas_sdk.clients.twilio.structures import TwiMlMessage

# Create a TwiML response with text and image
reply = TwiMlMessage.instance_with_media(
    message_text="Here's a picture for you!",
    media_url="https://example.com/image.jpg"
)

xml_response = reply.to_xml()
# Returns TwiML like:
# <?xml version="1.0" encoding="UTF-8"?>
# <Response><Message><Body>Here's a picture for you!</Body><Media>https://example.com/image.jpg</Media></Message></Response>

SmsClient #

The main class for interacting with the Twilio SMS/MMS API.

Constructor #

SmsClient(settings: Settings)
ParameterTypeDescription
settingsSettingsConfiguration object with Twilio credentials

Phone Number Management #

account_phone_numbers() -> Iterator[Phone] #

Retrieve all phone numbers associated with the Twilio account.

for phone in client.account_phone_numbers():
    print(f"{phone.friendly_name}: {phone.phone_number}")
    print(f"  SMS: {phone.capabilities.sms}, MMS: {phone.capabilities.mms}")

Returns: Iterator of Phone objects

Raises: RequestFailed on error

account_phone_number(phone_sid: str) -> Phone #

Retrieve details for a specific phone number by its SID.

phone = client.account_phone_number("PNxxxxxxxxxxxxxxxxx")
print(f"Phone: {phone.phone_number}, Status: {phone.status}")
ParameterTypeDescription
phone_sidstrThe Twilio SID of the phone

Returns: Phone object

Raises: RequestFailed on error

set_inbound_webhook(phone_sid: str, webhook_url: str, method: HttpMethod) -> bool #

Configure the webhook URL for receiving inbound messages on a phone number.

from canvas_sdk.clients.twilio.constants import HttpMethod

success = client.set_inbound_webhook(
    phone_sid="PNxxxxxxxxxxxxxxxxx",
    webhook_url="https://your-app.com/api/inbound-sms",
    method=HttpMethod.POST
)
ParameterTypeDescription
phone_sidstrThe Twilio SID of the phone
webhook_urlstrURL to receive inbound messages
methodHttpMethodHTTP method (GET or POST)

Returns: True on success

Raises: RequestFailed on error

Sending Messages #

send_sms_mms(sms_mms: SmsMms) -> Message #

Send an SMS or MMS message. The method automatically validates phone capabilities.

sms = SmsMms(
    number_from="+15551234567",
    number_from_sid="PNxxxxxxxxxxxxxxxxx",
    number_to="+15559876543",
    message="Hello!",
    media_url="",  # Empty for SMS, URL for MMS
    status_callback_url="https://your-app.com/status",
)

message = client.send_sms_mms(sms)
print(f"Sent! SID: {message.sid}, Status: {message.status.value}")
ParameterTypeDescription
sms_mmsSmsMmsMessage details to send

Returns: Message object with sent message details

Raises: RequestFailed if the phone lacks required capabilities or API fails

Retrieving Messages #

retrieve_sms(message_id: str) -> Message #

Get details for a specific message by its SID.

message = client.retrieve_sms("SMxxxxxxxxxxxxxxxxx")
print(f"Status: {message.status.value}")
print(f"Body: {message.body}")
print(f"Sent: {message.date_sent}")
ParameterTypeDescription
message_idstrThe Twilio message SID

Returns: Message object

Raises: RequestFailed on error

retrieve_all_sms(number_to, number_from, date_sent, date_operation) -> Iterator[Message] #

Retrieve messages with optional filtering.

from canvas_sdk.clients.twilio.constants import DateOperation

# All messages
for msg in client.retrieve_all_sms("", "", "", DateOperation.ON_EXACTLY):
    print(msg.body)

# Messages to a specific number
for msg in client.retrieve_all_sms("+15551234567", "", "", DateOperation.ON_EXACTLY):
    print(msg.body)

# Messages from a specific date
for msg in client.retrieve_all_sms("", "", "2024-06-01", DateOperation.ON_AND_AFTER):
    print(f"{msg.date_sent}: {msg.body}")
ParameterTypeDescription
number_tostrFilter by recipient (empty = no filter)
number_fromstrFilter by sender (empty = no filter)
date_sentstrDate to filter by (YYYY-MM-DD format)
date_operationDateOperationHow to compare the date

Returns: Iterator of Message objects

Raises: RequestFailed on error

delete_sms(message_id: str) -> bool #

Delete a message from Twilio.

deleted = client.delete_sms("SMxxxxxxxxxxxxxxxxx")
print(f"Deleted: {deleted}")
ParameterTypeDescription
message_idstrThe Twilio message SID

Returns: True on success

Raises: RequestFailed on error

Media Handling #

retrieve_media_list(message_id: str) -> Iterator[Media] #

Get all media attachments for a message.

for media in client.retrieve_media_list("SMxxxxxxxxxxxxxxxxx"):
    print(f"Media SID: {media.sid}")
    print(f"Content Type: {media.content_type}")
ParameterTypeDescription
message_idstrThe Twilio message SID

Returns: Iterator of Media objects

Raises: RequestFailed on error

retrieve_raw_media(message_id: str, media_sid: str) -> bytes #

Download the raw binary content of a media attachment.

for media in client.retrieve_media_list(message_sid):
    content = client.retrieve_raw_media(message_sid, media.sid)

    # Save to file
    with open(f"media_{media.sid}.jpg", "wb") as f:
        f.write(content)
ParameterTypeDescription
message_idstrThe Twilio message SID
media_sidstrThe Twilio media SID

Returns: Raw binary content (bytes)

Raises: RequestFailed on error

Data Structures #

Settings #

Configuration for the SmsClient.

FieldTypeDescription
account_sidstrTwilio Account SID
keystrTwilio API Key SID
secretstrTwilio API Key Secret

SmsMms #

Represents an SMS or MMS message to send.

FieldTypeDescription
number_fromstrSender phone number (E.164 format)
number_from_sidstrTwilio SID of the sender phone number
number_tostrRecipient phone number (E.164 format)
messagestrText content of the message
media_urlstrURL of media to attach (empty for SMS)
status_callback_urlstrURL to receive delivery status updates

Message #

Represents a Twilio message with full metadata.

FieldTypeDescription
sidstrUnique message identifier
bodystrMessage text content
date_createddatetimeWhen the message was created
date_sentdatetime \| NoneWhen the message was sent
date_updateddatetimeWhen the message was last updated
directionMessageDirectionMessage direction
number_fromstrSender phone number
number_tostrRecipient phone number
pricestr \| NoneCost of the message
price_unitstrCurrency of the price
error_codeint \| NoneError code if failed
error_messagestr \| NoneError description if failed
uristrAPI URI for this resource
count_mediaint \| NoneNumber of media attachments
count_segmentsintNumber of SMS segments
statusMessageStatusCurrent message status
sub_resource_urisdict[str, str] \| NoneURIs to related resources

Phone #

Represents a Twilio phone number with configuration.

FieldTypeDescription
account_sidstrTwilio Account SID
capabilitiesCapabilitiesPhone capabilities (SMS, MMS, etc.)
date_createddatetimeWhen added to account
date_updateddatetimeLast configuration update
friendly_namestrUser-defined name
phone_numberstrPhone number in E.164 format
sidstrUnique phone number identifier
sms_fallback_methodHttpMethodHTTP method for fallback URL
sms_fallback_urlstrFallback URL if primary fails
sms_methodHttpMethodHTTP method for SMS webhook
sms_urlstrWebhook URL for inbound SMS
status_callback_methodHttpMethodHTTP method for status callbacks
status_callbackstrURL for status updates
statusstrCurrent phone number status

Capabilities #

Phone number communication capabilities.

FieldTypeDescription
faxboolSupports fax
mmsboolSupports MMS (multimedia)
smsboolSupports SMS (text)
voiceboolSupports voice calls

Media #

Represents media attached to a message.

FieldTypeDescription
sidstrUnique media identifier
content_typestrMIME type (e.g., image/jpeg)
date_createddatetimeWhen the media was created
date_updateddatetimeWhen the media was last updated
parent_sidstrMessage SID this media belongs to
uristrAPI URI for this resource

StatusInbound #

Parsed data from an inbound message webhook callback.

FieldTypeDescription
account_sidstrTwilio Account SID
message_sidstrMessage SID
messaging_service_sidstrMessaging Service SID
sms_message_sidstrSMS Message SID
sms_sidstrSMS SID
sms_statusMessageStatusMessage status
to_countrystrRecipient country
to_zipstrRecipient ZIP code
to_statestrRecipient state
to_citystrRecipient city
from_countrystrSender country
from_zipstrSender ZIP code
from_statestrSender state
from_citystrSender city
number_tostrRecipient phone number
number_fromstrSender phone number
bodystrMessage text
count_mediaintNumber of media attachments
count_segmentsintNumber of SMS segments
media_content_typelist[str]MIME types of attached media
media_urllist[str]URLs of attached media

Class Methods:

MethodDescription
StatusInbound.callback_inbound(raw_body)Parse URL-encoded webhook body

StatusOutboundApi #

Parsed data from an outbound message status callback.

FieldTypeDescription
account_sidstrTwilio Account SID
message_sidstrMessage SID
sms_sidstrSMS SID
sms_statusMessageStatusSMS status
message_statusMessageStatusMessage status
number_tostrRecipient phone number
number_fromstrSender phone number

Class Methods:

MethodDescription
StatusOutboundApi.callback_outbound_api(raw_body)Parse URL-encoded webhook body

TwiMlMessage #

Generates TwiML XML for responding to inbound messages.

FieldTypeDescription
number_tostrRecipient (optional in response)
number_fromstrSender (optional in response)
status_callback_urlstrStatus callback URL
message_textstrMessage text content
media_urlstrMedia URL to attach
methodHttpMethod\|NoneHTTP method for callbacks

Class Methods:

MethodDescription
TwiMlMessage.instance(message_text) -> TwiMlMessageCreate text-only TwiML message
TwiMlMessage.instance_with_media(message_text, media_url) -> TwiMlMessageCreate TwiML with media

Instance Methods:

MethodDescription
to_xml() -> strGenerate TwiML XML string

Example:

# Simple text reply
reply = TwiMlMessage.instance("Thanks for your message!")
xml = reply.to_xml()

# Reply with media
reply = TwiMlMessage.instance_with_media("Check this out!", "https://example.com/image.jpg")
xml = reply.to_xml()

Constants (Enums) #

MessageStatus #

Message lifecycle status values.

ValueDescription
ACCEPTEDMessage accepted by Twilio
SCHEDULEDMessage scheduled for future delivery
CANCELEDScheduled message was canceled
QUEUEDMessage queued for sending
SENDINGMessage is being sent
SENTMessage sent to carrier
FAILEDMessage failed to send
DELIVEREDMessage delivered to recipient
UNDELIVEREDMessage could not be delivered
PARTIALLY_DELIVEREDSome recipients received the message
RECEIVINGInbound message being received
RECEIVEDInbound message received
READMessage was read (WhatsApp only)

MessageDirection #

Message direction types.

ValueDescription
INBOUNDMessage received from external number
OUTBOUND_APIMessage sent via API
OUTBOUND_CALLMessage sent during a call
OUTBOUND_REPLYMessage sent as webhook reply

DateOperation #

Date filtering operations for message queries.

ValueDescription
ON_EXACTLYMessages on exactly this date
ON_AND_BEFOREMessages on or before this date
ON_AND_AFTERMessages on or after this date

HttpMethod #

HTTP methods for webhook configuration.

ValueDescription
GETHTTP GET method
POSTHTTP POST method

Error Handling #

RequestFailed #

Exception raised when a Twilio API request fails (extends RuntimeError).

AttributeTypeDescription
status_codeintHTTP status code
messagestrError message from Twilio

Example:

try:
    message = client.send_sms_mms(sms)
except RequestFailed as e:
    if e.status_code == 0:
        # Client-side validation error (e.g., phone lacks MMS capability)
        print(f"Validation error: {e.message}")
    else:
        # Twilio API error
        print(f"API error {e.status_code}: {e.message}")

Complete Webhook Example #

Here’s a complete example of handling inbound SMS and sending replies:

from canvas_sdk.clients.twilio.structures import StatusInbound, TwiMlMessage

def handle_webhook(raw_body: str) -> str:
    """
    Handle incoming SMS webhook from Twilio.

    Args:
        raw_body: URL-encoded form data from Twilio POST request

    Returns:
        TwiML XML response string
    """
    # Parse the inbound message
    inbound = StatusInbound.callback_inbound(raw_body)

    # Log the message
    print(f"From: {inbound.number_from}")
    print(f"To: {inbound.number_to}")
    print(f"Body: {inbound.body}")
    print(f"Media count: {inbound.count_media}")

    # Check for media attachments
    if inbound.count_media > 0:
        for i, url in enumerate(inbound.media_url):
            print(f"Media {i}: {inbound.media_content_type[i]} - {url}")

    # Generate appropriate response
    body_lower = inbound.body.lower()

    if "help" in body_lower:
        reply = TwiMlMessage.instance("Commands: HELP, STATUS, HELLO")
    elif "hello" in body_lower:
        reply = TwiMlMessage.instance_with_media(
            "Hello! Here's a welcome image!",
            "https://example.com/welcome.jpg"
        )
    elif "status" in body_lower:
        reply = TwiMlMessage.instance("System is operational.")
    else:
        reply = TwiMlMessage.instance("Unknown command. Text HELP for options.")

    return reply.to_xml()

Additional Resources #