A common problem when developing an application that uses Twilio services is how to effectively test it. Making real requests to Twilio for testing purposes is something that can be useful when done sparsely, but it is not ideal as a general approach to testing because it can end up being expensive. Also, when testing how your application responds to errors you are going to find that it is very hard to replicate all the possible error conditions that can occur in SMS or Voice communication.
In this article you are going to learn how to work with your Twilio Test Credentials to be able to send fake, yet realistic requests to Twilio that have a predictable result, and more importantly, are 100% free.
Tutorial requirements
To follow this tutorial you need the following components:
- Python 3.6 or newer. If your operating system does not provide a Python interpreter, you can go to python.org to download an installer.
- A phone with SMS support.
- A Twilio account. If you are new to Twilio create a free account now. If you use this link you’ll receive $10 in credit when you upgrade to a paid account (you can review the features and limitations of a free Twilio account).
Running the example application
For this tutorial we are going to use a simple Flask application that sends SMS. This application is available on GitHub. If you have git
installed, you can grab a copy as follows:
$ git clone https://github.com/miguelgrinberg/twilio-send-sms-demo
If you prefer to download the application from your web browser, click this download link.
Once you have the application installed, open a terminal and change to this directory:
$ cd twilio-send-sms-demo
Next we are going to create a virtual environment and install the dependencies for this application on it. If you use a Linux of Mac computer:
$ python3 -m venv venv
$ source venv/bin/activate
(venv) $ pip install -r requirements.txt
If you are using a Windows computer, use the following commands:
$ python -m venv venv
$ venv\Scripts\activate
(venv) $ pip install -r requirements.txt
This application uses a .env (dot-env) file for configuration. Create a .env file in this directory and define four variables in it:
TWILIO_ACCOUNT_SID="ACxxxxxxxxxxx"
TWILIO_AUTH_TOKEN="xxxxxxxxxxxxx"
FROM_NUMBERS="+1xxxxxxxxxx"
TO_NUMBERS="+1xxxxxxxxxx"
You can find out the values of the first two variables that are correct for your account by logging in to your Twilio Console and then clicking “Settings” on the left sidebar. Scroll down until you see the “API Credentials” section.
Copy the “Account SID” and “Auth Token” from the “LIVE Credentials” box into your .env file.
For the FROM_NUMBERS
variable, enter the Twilio phone number associated with your account. If you don’t have a phone number on your account, you can buy one just for this tutorial (if you are using a trial account you’ll be spending your trial credit for this, not real money). Enter the number in full E.164 format. For example, if your Twilio number is +12345678900, you would enter:
FROM_NUMBERS="+12345678900"
If you have more than one number in your Twilio account, you can enter all of them separated by commas:
FROM_NUMBERS="+12345678900,+19876543200"
For the TO_NUMBERS
variable, enter your mobile phone number. Here you can also enter a list of numbers you would like to send SMS to. Keep in mind that if you are using a trial account, Twilio requires that all numbers that will receive SMS be verified.
Once you have the four variables set in your .env file, save the file.
Now we are ready to start the application, with the flask run
command:
(venv) $ flask run
You should see the Flask server running as follows:
With the application still running, open your web browser and enter http://localhost:5000 in the address bar, and you should see the Send SMS Demo application in its full glory:
The Sender and Recipient dropdowns show the numbers that you configured in the .env file. If you select the sender and recipient numbers you would like to use, type a message and hit the “Submit” button, an SMS will be sent.
Make sure you can send yourself a message before continuing on to the next section.
Using Test Credentials
The main function in the example application is the send_sms()
function. You can see the implementation of this function in file app.py below:
def send_sms(from_phone, to_phone, message):
twilio.messages.create(from_=from_phone, to=to_phone, body=message)
The twilio.messages.create()
function comes from the Twilio Helper Library for Python, and is where the request to the Twilio service is made.
How robust do you think this function is? You can probably guess that having a single line code isn’t as robust as it can be. We would like this function to be able to withstand all possible error conditions, but to achieve that we need a way to generate all these possible failures in a controlled testing environment, and ideally without having to spend money on Twilio services.
We are going to address this need using our Test Credentials. Stop the example application by pressing Ctrl-C
on the terminal window where it was running. Then open the .env file again, and change the Account SID and Auth Token variables to the settings that appear on the ‘TEST Credentials” box in your Settings page.
When you use your test credentials, Twilio will accept your requests and process them normally, but it won’t actually do anything. That means that no SMS or phone calls will be made, and your account will not generate any charges.
Your test credentials function in a completely different environment than your live ones, with no connection between the two. In particular, any phone numbers that are associated with your live account are not going to be recognized under your test credentials. This gives us an opportunity to try our first error condition, using an invalid sender phone number.
Start the application again by running flask run
, and try to send yourself another SMS. Your web browser should now show a bad looking error message.
To find what was the error we have to look in the terminal window where the Flask application is running. The error should look like this:
[2020-06-12 14:52:07,910] ERROR in app: Exception on / [POST]
Traceback (most recent call last):
File "/users/miguel/twilio-send-sms-demo/venv/lib/python3.8/site-packages/flask/app.py", line 2447, in wsgi_app
response = self.full_dispatch_request()
File "/users/miguel/twilio-send-sms-demo/venv/lib/python3.8/site-packages/flask/app.py", line 1952, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/users/miguel/twilio-send-sms-demo/venv/lib/python3.8/site-packages/flask/app.py", line 1821, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/users/miguel/twilio-send-sms-demo/venv/lib/python3.8/site-packages/flask/_compat.py", line 39, in reraise
raise value
File "/users/miguel/twilio-send-sms-demo/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request
rv = self.dispatch_request()
File "/users/miguel/twilio-send-sms-demo/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/users/miguel/twilio-send-sms-demo/app.py", line 40, in index
send_sms(form.from_phone.data, form.to_phone.data,
File "/users/miguel/twilio-send-sms-demo/app.py", line 32, in send_sms
twilio.messages.create(from_=from_phone, to=to_phone, body=message)
File "/users/miguel/twilio-send-sms-demo/venv/lib/python3.8/site-packages/twilio/rest/api/v2010/account/message/__init__.py", line 89, in create
payload = self._version.create(method='POST', uri=self._uri, data=data, )
File "/users/miguel/twilio-send-sms-demo/venv/lib/python3.8/site-packages/twilio/base/version.py", line 210, in create
raise self.exception(method, uri, response, 'Unable to create record')
twilio.base.exceptions.TwilioRestException:
HTTP Error Your request was:
POST /Accounts/ACxxxxxxxxxxxxxxxx/Messages.json
Twilio returned the following information:
Unable to create record: The From phone number +12345678900 is not a valid, SMS-capable inbound phone number or short code for your account.
More information may be available here:
https://www.twilio.com/docs/errors/21606
This is a long and obscure error message, but you can ignore most of it and concentrate on the exception raised, which is shown near the bottom. In this case it was a TwilioRestException
, and the error message is actually self-explanatory:
Unable to create record: The From phone number +12345678900 is not a valid, SMS-capable inbound phone number or short code for your account.
So now we know that when there is an error, this application just crashes by raising a TwilioRestException
with a useful error message.
If we look in the place where the send_sms()
function is invoked in app.py, we can see that this code is already prepared to handle exceptions of type RuntimeError
and show them as errors to the user via Flask’s message flashing mechanism:
try:
send_sms(form.from_phone.data, form.to_phone.data,
form.message.data)
except RuntimeError as ex:
flash(str(ex))
else:
flash('SMS sent!')
One improvement we can make to this application is to catch the TwilioRestException
and raise a RuntimeError
exception in its place. Let’s make this change in the send_sms()
function, and also add the import for the TwilioRestException
at the top of the file:
from twilio.base.exceptions import TwilioRestException
# ...
def send_sms(from_phone, to_phone, message):
try:
twilio.messages.create(from_=from_phone, to=to_phone, body=message)
except TwilioRestException as exc:
raise RuntimeError(f'{exc.msg} (code: {exc.code})')
The msg
and code
attributes of the Twilio exception include the message and the numeric code for the error, which we format nicely into the RuntimeError
using an f-string.
Stop and restart the Flask application and try to send yourself an SMS one more time. Now the output is much nicer:
Magic numbers
The application is now able to handle unexpected errors in a more dignified way, but so far the only error we can replicate is the one that comes up when we use an invalid phone number as a sender. The test credentials do not give us a way to generate other error conditions, and more importantly, we have lost the ability to send a successful SMS, since now all messages fail.
To handle all these different scenarios, Twilio provides magic numbers, special phone numbers that have predefined behaviors.
For SMS, Twilio supports the following magic numbers as senders:
Number |
Behavior |
+15005550001 |
Invalid phone number. |
+15005550006 |
Valid number. |
+15005550007 |
The number is not owned by your account or not SMS capable. |
+15005550008 |
The number has a full SMS queue. |
Any other number |
The number is not owned by your account or not SMS capable. |
As receivers, the following SMS magic numbers are available:
Number |
Behavior |
+15005550001 |
Invalid phone number. |
+15005550002 |
Cannot route to this number. |
+15005550003 |
Your account does not have international permissions. |
+15005550004 |
The number is in the blocked list. |
+15005550009 |
The number is not SMS capable. |
Any other number |
Valid number. |
Let’s add all of these magic numbers to our .env configuration. Replace the values for the FROM_NUMBERS
and TO_NUMBERS
with the following values:
FROM_NUMBERS="+15005550006,+15005550001,+15005550007,+15005550008,+15005550009,+12345678900"
TO_NUMBERS="+15005550001,+15005550002,+15005550003,+15005550004,+15005550009,+19876543200"
After making these changes to your .env file, remember to stop the Flask server and restart it.
Now by selecting combinations of sender and receiver magic numbers we can generate all possible outcomes, valid or invalid, and all without incurring any charges with Twilio.
Conclusion
In this article, you have learned how to use Twilio test credentials and magic numbers to test sending of SMS. Twilio also provides magic numbers for making phone calls, and for buying phone numbers.
I hope you find the use of test credentials and magic numbers a useful technique to add to your bag of tricks when working with Twilio!
Miguel Grinberg is a Python Developer for Technical Content at Twilio. Reach out to him at mgrinberg [at] twilio [dot] com if you have a cool Python project you’d like to share on this blog!