This content originally appeared on Twilio Blog and was authored by Neil Ruaro
Texting is one of the most common forms of communication in our modern age. Twilio provides a simple and easy-to-use interface for sending SMS text messages through their SMS API.
In this tutorial, you'll be using the Twilio SMS API to create your very own CLI texting application using Python and the Typer framework.
If you're unfamiliar, Typer is a Python library for creating CLI applications. Typer is easy to use, and intuitive to write. It borrows these features from its sister, the FastAPI framework, written by the same author.
Prerequisites
In order to complete this tutorial, you will need the following:
- A Twilio account, and Twilio CLI installed. (I'll show instructions on how to do this.) If you haven't signed up yet, sign up for a free Twilio trial.
- A Twilio phone number. (Learn how to buy a Twilio phone number.)
Install Twilio CLI, and setup
You'll first need to install and set up the Twilio Command Line Interface (CLI). For Linux, the setup commands are below.
$ sudo apt-get install sqlite3
$ sudo apt install -y twilio
$ twilio login
Instructions for installing sqlite3 on Windows or MacOS can be found here. If you're on MacOS, or Windows, check the CLI quickstart on the Twilio Docs to install the Twilio CLI.
twilio login
will ask for your credentials, which you can see in the Twilio Console, and will prompt you to input a username for your local profile. Once you finish that, run the following:
$ twilio profiles:use username
If you haven't bought a Twilio phone number already, you can do that through the command line by running the command below and picking a phone number from the list produced:
$ twilio phone-numbers:buy:local --country-code US --sms-enabled
Development environment setup
First, set up your development environment by installing all the needed dependencies. Create a new directory named twilio-sms-cli
, and then navigate to that directory.
As a part of best practices for Python, you should also create a virtual environment. If you are working on UNIX or macOS, run the following commands to create and activate a virtual environment:
python3 -m venv venv
source venv/bin/activate
However, if you are working on Windows, run these commands instead:
python -m venv venv
venv\bin\activate
Then, create a requirements.txt
file in the twilio-sms-cli
directory and input the following lines.
typer==0.6.1
twilio==7.11.0
python-dotenv==0.20.0
Then, run pip3 install -r requirements.txt
to install the dependencies. After running that, you should have installed Typer, the Twilio Python Helper Library, and python-dotenv - which you will be using for accessing environment variables.
Next, run pip3 install pytest
in order to install pytest, which you will use for testing the application logic.
After doing that, create a file named .env
in the twilio-sms-cli
directory, and add the following:
TWILIO_ACCOUNT_SID='your-account-sid'
TWILIO_AUTH_TOKEN='your-auth-token'
You'll need your account SID and auth token for interfacing with the Twilio helper library. You can access these under Account Info
in your Twilio Console. Plus, it's best practice to include confidential stuff like these environmental variables in a .env
file instead of keeping it in your codebase. If using a platform like GitHub, don't forget to create a .gitignore
file and add the .env
file to this file.
Send a message with the Twilio SMS API
With that out of the way, you can finally start making your application. First off, let me show you how you can easily send SMS messages with the Twilio SMS API. Create a send_sms.py
file in the twilio-sms-cli
directory, and add in the following:
import os
from twilio.rest import Client
from dotenv import load_dotenv
load_dotenv()
# Find your Account SID and Auth Token at twilio.com/console
account_sid = os.getenv("TWILIO_ACCOUNT_SID")
auth_token = os.getenv("TWILIO_AUTH_TOKEN")
client = Client(account_sid, auth_token)
message = client.messages.create(
body="Hello, from Twilio and Python!",
to="number-verified-in-your-twilio-account",
from_="number-you-bought-through-twilio-cli",
)
print(f"message: {message.body}")
print(f"sent from: {message.from_}")
print(f"sent to: {message.to}")
Remember to replace the highlighted to
and from
placeholders with the corresponding phone numbers written in E.164 format. Also, see adding a verified phone number to know how to get your number verified in your Twilio account.
In the code snippet above, you use the load_dotenv()
function so that Python knows to look through the .env
file. To interface with the Twilio API, you need to use the Twilio Client -> Client
. There, you pass in your account_sid
and auth_token
, which you have in your .env
file.
Note that a Twilio trial account is restricted to only sending messages to verified phone numbers. Additionally, you can only send SMS via the phone number you bought through the CLI, and all sent SMS messages will start with Sent from a Twilio trial account
. For our use case, this is fine.
Now, run the application by typing python3 send_sms.py
in the command line. You should then see the print messages on the console, as well as receive a text message at your phone number.
Quick intro to Typer
Now, create a main.py
file and add in the following:
import typer
app = typer.Typer()
@app.command()
def send():
print("Send")
@app.command()
def receive():
print("Just for show")
if __name__ == "__main__":
app()
As you can see, we have a simple template for creating CLI commands in Typer:
@app.command()
def functionName():
# do something
Running the command python3 main.py send
will run the send
function, while running python3 main.py receive
will run the receive
function.
Now, what about these things we commonly refer to as command line arguments? If you've used git, you're probably familiar with this command:
$ git commit -m "commit!"
Here we create a git commit, with the commit message being "commit!". The -m "commit"
in CLI terms is actually a command line option. Now how do we add that in Typer? It's pretty straightforward actually — just add function parameters to the command and then add a = typer.Option()
as shown below:
# in the send() function earlier
@app.command()
def send(message: str = typer.Option()):
print("Send " + message)
Here we create an optional command line option. How about if we want to make it required? Then just do the following:
# in the same send() function
@app.command()
def send(message: str = typer.Option(...)):
print("Send " + message)
Brief intro to testing Typer applications
Testing in Typer is pretty straightforward, as it allows you to use pytest
directly. In particular, you use CliRunner
, and pass in your app
variable from earlier. You then write assert
statements to test out the application.
Create a tests
directory in the twilio-sms-cli
directory, and inside, create a test_main.py
file. There, add in the tests for main.py
:
from typer.testing import CliRunner
from main import app
runner = CliRunner()
def test_send_success():
result = runner.invoke(app, ["send", "--message", "message"])
assert "Send message" in result.stdout
def test_receive_success():
result = runner.invoke(app, ["receive"])
assert "Just for show" in result.stdout
This short test just tests that our send
and receive
functions work as expected. If you run the command python3 -m pytest tests/test_main.py
, you should see that the tests pass.
Create the command for sending SMS messages
Now, let's create the command for sending SMS messages. You will basically port the code you have in send_sms.py
to your main.py
file which will contain the command.
At the top of main.py
, update the file with the following new lines:
import os
import typer
from twilio.rest import Client
from dotenv import load_dotenv
load_dotenv()
Then, replace the send()
function with the following code below:
@app.command()
def send(toNumber: str = typer.Option(...), fromNumber: str = typer.Option(...), message: str = typer.Option(...)):
if (toNumber == None or toNumber == "" or fromNumber == None or fromNumber == "" or message == None or message == ""):
print("Missing required values for required arguments")
raise typer.Exit()
if (toNumber[0] != "+" or fromNumber[0] != "+"):
print("Phone numbers must start with a '+'")
raise typer.Exit()
account_sid = os.getenv("TWILIO_ACCOUNT_SID")
auth_token = os.getenv("TWILIO_AUTH_TOKEN")
# in the case this happens and you already have your .env file setup, just run `source .env`
if (account_sid == None and auth_token == None):
error_detail = "Missing values for TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN\n" + "SID: " + account_sid + "\n" + "Token: " + auth_token
print(error_detail)
raise typer.Exit()
elif (account_sid == None):
error_detail = "Missing value for TWILIO_ACCOUNT_SID\n" + "SID: " + account_sid
print(error_detail)
raise typer.Exit()
elif (auth_token == None):
error_detail = "Missing value for TWILIO_AUTH_TOKEN\n" + "Token: " + auth_token
print(error_detail)
raise typer.Exit()
client = Client(account_sid, auth_token)
clientMessage = client.messages.create(
body=message,
to=toNumber,
from_=fromNumber,
)
print("Message Body: " + clientMessage.body)
...
...
...
In the send
command, you added three required option parameters, which are the variables you'll be needing for sending your SMS messages. You'll also see that you have some raise typer.Exit()
statements. Here you just check for some simple edge cases that would make the application not work as expected. In the case these edge cases are met, you simply exit the program. There are loads more edge cases to tackle, but for our use case, these will do.
Once you get past the checks for errors, you then create the Twilio Client
, and send a message with the values passed into the query parameters. Similar to send_sms.py
, you must use the number you bought from Twilio, and the number you used for verifying your Twilio account.
You then just simply print the message sent through the Twilio SMS API.
Test the endpoint for sending SMS messages
Now that you have updated the command, you can also update its related tests. In test_main.py
, update the test_send_success()
function, and add in the following tests, replacing your_number
with your verified phone number, and your_twilio_number
with your Twilio phone number:
def test_send_success():
result = runner.invoke(app, ["send","--tonumber", "your_number", "--fromnumber", "your_twilio_number", "--message", "Hello from the test suite!"])
assert "Message Body: Sent from your Twilio trial account - Hello from the test suite!" in result.stdout
def test_send_missing_values_all_args():
result = runner.invoke(app, ["send","--tonumber", "r", "--fromnumber", "", "--message", ""])
assert "Missing required values for required arguments" in result.stdout
def test_send_missing_value_one_arg():
result = runner.invoke(app, ["send","--tonumber", "your_number", "--fromnumber", "your_twilio_number", "--message", ""])
assert "Missing required values for required arguments" in result.stdout
def test_send_invalid_phone_number():
result = runner.invoke(app, ["send","--tonumber", "00000", "--fromnumber", "000000", "--message", "Hello from the test suite!"])
assert "Phone numbers must start with a '+'" in result.stdout
If you are working with an upgraded Twilio account, you will need to change the assert
line in test_send_success()
to assert "Message Body: Hello from the test suite!" in result.stdout
.
For the first test, you test a successful send
command. In the next two tests, you check for missing option parameters. Since you have print statements in the application code for when you encounter the different edge cases included, you assert the test cases for those edge cases by verifying that the printed statement is shown. The last test, on the other hand, checks for an invalid phone number, i.e. a phone number not starting with "+".
Running python3 -m pytest tests/test_main.py
again, you should see that all tests pass.
You've now successfully finished your CLI application! To see it all in action, just run the following command, replacing your_number
with your verified phone number, your_twilio_number
with your Twilio number , and message
with your message:
$ python3 main.py send --tonumber "your_number" --fromnumber "your_twilio_number" --message "your_message"
As you may have noticed, in the send()
function, you defined the parameters as toNumber
and fromNumber
, but Typer automatically turns these into lowercase when running the app. Thus we use tonumber
and fromnumber
instead when running the send
command.
Conclusion
In this article, you learned how to send SMS messages with the Twilio SMS API, develop a CLI application using Python and Typer, and test the CLI app using Pytest.
If you find this article helpful, feel free to share, and let us connect on Twitter! You can also support me by buying me a coffee.
Neil is a software engineer who recently graduated from high school. He started learning how to program during the start of the COVID-19 pandemic, and got his first internship a year later. Currently, he's doing research and development for Angkas, a ride-sharing company similar to Uber and has professional experience working on the mobile, backend, and blockchain areas of an application. Besides that, he has also given talks and workshops on mobile development with Flutter, as well as written articles on backend-related stuff such as the article you just read, as well as on machine learning.
This content originally appeared on Twilio Blog and was authored by Neil Ruaro
Neil Ruaro | Sciencx (2022-10-05T00:25:41+00:00) How to Create a CLI App for Sending Text Messages Using Twilio SMS and Python. Retrieved from https://www.scien.cx/2022/10/05/how-to-create-a-cli-app-for-sending-text-messages-using-twilio-sms-and-python/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.