$ mkdir twilio_sync
cd
into the project directory and run the following command to create a virtual environment.$ python -m venv venv
$ source venv/bin/activate
$ venv\Scripts\activate
.env
file.$ pip install flask twilio python-dotenv
$ pip freeze > requirements.txt
requirements.txt
file for us which contains all our project’s dependencies along with their versions..env
in your text editor. Add the Twilio Service SID as a TWILIO_SERVICE_SID
variable.TWILIO_SERVICE_SID=xxxx
.env
file as TWILIO_API_KEY
and TWILIO_API_SECRET
variables below the Sync service SID:TWILIO_API_KEY=xxxx TWILIO_API_SECRET=xxxx
.env
file.TWILIO_AUTH_TOKEN=xxxx TWILIO_ACCOUNT_SID=xxxx
main.py
file at the root of your project’s directory and add the following code to it:import os from flask import Flask, request, jsonify, render_template, abort, Response from dotenv import load_dotenv from twilio.rest import Client from twilio.jwt.access_token import AccessToken from twilio.jwt.access_token.grants import SyncGrant load_dotenv() app = Flask(__name__) twilio_client = Client() @app.route('/dashboard', methods=['GET']) def view_dashboard(): return render_template('index.html') @app.route('/inbound/sms', methods=['POST']) def inbound_sms(): data = { 'MessageSid': request.form['MessageSid'], 'From': request.form['From'], 'To': request.form['To'], 'Message': request.form['Body'] } twilio_sync_service = twilio_client.sync.services(os.environ['TWILIO_SERVICE_SID']) sync_list_item = twilio_sync_service.sync_lists('twilio_incoming_sms').sync_list_items.create(data=data) return Response() @app.route('/token', methods=['GET']) def generate_token(): username = request.args.get('username') if not username: abort(401) # Create a grant identifying the Sync instance for this app sync_grant = SyncGrant(os.environ['TWILIO_SERVICE_SID']) # Create an access token which we will sign and return to the client, containing the grant we just created and specifying the identity token = AccessToken(os.environ['TWILIO_ACCOUNT_SID'], os.environ['TWILIO_API_KEY'], os.environ['TWILIO_API_SECRET']) token.add_grant(sync_grant) token.identity = username return jsonify(identity=username, token=token.to_jwt().decode()) if __name__ == '__main__': app.run()
load_dotenv()
function loads our environment variables from the .env
file. The twilio_client
object will be used for interacting with the Twilio API. The TWILIO_ACCOUNT_SID
and TWILIO_AUTH_TOKEN
environment variables loaded by the load_dotenv()
function will be automatically used to authenticate against the Twilio service.view_dashboard()
function defines a route at /dashboard
that will return an index.html
template we shall be creating in the next section.inbound_sms()
endpoint will be configured so that it is invoked every time our Twilio number receives an SMS message. The function creates a dictionary with the following keys:MessageSid
- A 34 character, unique ID assigned by Twilio for the message.From
- The phone number that sent the message.To
- The phone number of the recipient, which in this case will be our Twilio Phone number.Message
- The content of the SMS message that was received.POST
request using Flask’s request
object.twilio_sync_service
object is a reference to the Sync Services API client object which allows us to work with the information we want to share with the dashboard.twilio_sync_service.sync_lists('twilio_incoming_sms').sync_list_items.create(data=data)
creates a Sync List item containing the data
dictionary we created at the beginning of the function under a Sync List with a unique name of twilio_incoming_sms
. It’s important to note that at this point, the Sync List hasn’t yet been created and would be created on the client side.generate_token()
function is used to generate an Access Token for the Twilio SDK on the client side. Depending on your particular use case, some sort of authentication or authorization will need to be in place for this endpoint. To do this you might use your existing login system or any other mechanism you find suitable for securing your applications. If you don’t need to protect the endpoint, you can assign a temporary identity to the user.username
in the query string. If the client fails to provide a username
, then a 401 error is returned, which is the HTTP status code for unauthorized access. Next, a grant associated with our Sync Service is created and added to an Access Token. The Access Token is initialized with the Twilio Account SID and the API Key and Secret, and is also given an identity
value set to the provided username
. The endpoint returns the access token to the caller.templates
sub-directory. Run the following command from your terminal to create the directory:$ mkdir templates
templates
directory, create a index.html
file and add the following code to the file:<!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Dashboard Incoming SMS</title> <!-- Fonts --> <link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"> <!-- Styles --> <style> html, body { background-color: #fff; color: #636b6f; font-family: 'Nunito', sans-serif; font-weight: 200; height: 100vh; margin: 0; } .container { max-width: 600px; margin: 100px auto; } </style> </head> <body> <div class="container"> <table class="table"> <thead> <tr> <th>MessageSid</th> <th>From</th> <th>To</th> <th>Message</th> </tr> </thead> <tbody> </tbody> </table> </div> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script src="https://media.twiliocdn.com/sdk/js/sync/releases/0.12.4/twilio-sync.min.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"></script> <script src="{{ url_for('static', filename='index.js') }}"></script> </body> </html>
index.js
file we shall be creating next.static
sub-directory:$ mkdir static
index.js
file within the directory and add the following code to the file:$('document').ready(function () { fetchAccessToken(initializeSyncClient) }); function fetchAccessToken(handler) { $.getJSON('/token?username=dotun', function (data) { handler(data); }); } function initializeSyncClient(tokenResponse) { var syncClient = new Twilio.Sync.Client(tokenResponse.token) syncClient.list('twilio_incoming_sms').then(function(list) { list.getItems().then(function(pages) { for(const page of pages.items) { const data = page.value; addRowToTable(data); } }); list.on('itemAdded', function(pages) { const data = pages.item.data.value; addRowToTable(data); }); }); } function addRowToTable(data) { const markup = `<tr><td>${data.MessageSid}</td><td>${data.From}</td><td>${data.To}</td><td>${data.Message}</td></tr>` $("table tbody").append(markup) return; }
fetchAccessToken()
uses jQuery to make an Ajax request to the backend and obtain an Access Token. The initializeSyncClient()
function is then called. Within this function, a Twilio.Sync.Client
object is instantiated by passing in the Access Token as authentication.initializeSyncClient()
function to understand what’s happening better.syncClient.list('twilio_incoming_sms').then(function(list) { // ... }
twilio_incoming_sms
Sync List is obtained. If the List doesn’t already exist at this point, it will be created.list.getItems().then(function(pages) { for(const page of pages.items) { const data = page.value; addRowToTable(data); } });
list.getItems()
method. Each of the items is appended as a new row to the HTML table using the addRowToTable()
function.list.on('itemAdded', function(pages) { const data = pages.item.data.value; addRowToTable(data); });
(venv) $ python main.py
http://localhost:5000/dashboard
URL to see the Dashboard page.$ ngrok http 5000
5000
refers to the port your Flask application is listening on.HTTP POST
and then click the “Save” button at the bottom of the page to save the settings. This is the endpoint Twilio will send a request to whenever a call is placed to our Twilio number.http://localhost:5000/dashboard
endpoint and then send an SMS to your Twilio phone number.