Booking Appointments with Twilio, Notion, and FastAPI

August 19, 2022
Written by
Ravgeet Dhillon
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by
Mia Adjei
Twilion

Booking Appointments with Twilio, Notion, and FastAPI

Most businesses run around the concept of appointments. Appointments allow you to schedule different events for different individuals. For example, before seeing a doctor, it might be necessary to book an appointment. Businesses can leverage the power of WhatsApp to allow their customers to book appointments easily just by sending messages. They can also get updates or check the status of their appointments through WhatsApp messages.

In this tutorial, you’ll learn to use Twilio’s WhatsApp API with the Notion API and FastAPI to create appointments and get their statuses as well. You will use Notion for storing data, Twilio for sending WhatsApp messages, and FastAPI for API and business logic.

Prerequisites

To follow this tutorial, you need the following items:

The entire code for this repository is available in this GitHub repository.

Creating a Notion Database

The first thing you need to do is to create a Notion database.

To do so, open your Notion account, click on Add a page, and select Table under the databases.

Creating a new page with a Table

Once the table is created, next, select the data source as a New database and give your database a name - Appointments.

Creating a new database named Appointments

Every database table in Notion has a set of fields in which you can store your data. So, next, add the following three fields after the Name field to your table:

  1. ID (Text) - for specifying the unique identifier of the appointment.
Creating the ID (text) field
  • Phone No. (Phone) - a phone number of the client.
Creating the phone number field
  • Scheduled On (Date) - for specifying the appointment’s date and time.
Creating the Scheduled on (date) field

Your Notion database is set up and next, you need to create a Notion integration to access the data in Notion.

Getting Notion API Token

To use Notion API, you need to create a Notion integration and get an API access token.

To do so, visit https://www.notion.so/my-integrations and create a new integration. Give it a name - Appointments Booking, and upload a logo if you want to.

Create a new integration form

Next, set its Content Capabilities to Read Content, Update Content, and Insert Content, its Comment Capabilities to None, and User Capabilities to No user information only and click Submit.

Customizing the settings for the integration

Once the integration is created, you will be presented with a secret API token. Copy and keep it safe, as you will be using it later while writing the code for the FastAPI application.

Receiving the secret API token

By default, Integrations don't have access to pages or databases in Notion. So, you need to share the specific page or database that you want to query with the Notion API with your integration.

To do so, open your database in Notion. Click on the Share button and use the selector to find your integration by its name, and click Invite.

Sharing the database with the new integration

With that, you can now access your Notion database using the Notion API in your FastAPI application.

Setting Up the FastAPI Project

First, open up your terminal, navigate to a path of your choice, create a project directory (appointment-booking), and set up a virtual environment by running the following commands in your terminal:

mkdir appointment-booking && cd appointment-booking
python3 -m venv venv
source venv/bin/activate

For this project, you need to install the following PIP dependencies:

  • fastapi - A Python framework for building REST APIs.
  • uvicorn - A lightning-fast ASGI server for FastAPI.
  • twilio - Twilio’s Python helper library for the Twilio API.
  • requests - Used to make HTTP requests.
  • python-dotenv - Used for reading environment variables from the .env file.
  • shortuuid - Utility for creating unique UIDs.
  • pyngrok - Python wrapper for ngrok to expose localhost to the public Internet.

Next, install the above-listed PIP dependencies by running the following command in your terminal:

pip install fastapi uvicorn[standard] twilio requests python-dotenv shortuuid pyngrok

Creating an Appointment

Now is the time to write some code. Firstly, you want to connect to the Notion API and create an appointment.

To do so, first, create a main.py file in the project’s root directory and add the following code to it:

import json
import os
import shortuuid
import requests

from datetime import datetime
from dotenv import load_dotenv

load_dotenv()

NOTION_API_BASE_URL = 'https://api.notion.com/v1'
NOTION_API_TOKEN = os.getenv('NOTION_API_TOKEN')
NOTION_DATABASE_ID = os.getenv('NOTION_DATABASE_ID')
NOTION_REQ_HEADERS: dict = {
    'Authorization': f'Bearer {NOTION_API_TOKEN}',
    'Content-Type': 'application/json',
    'Notion-Version': '2021-08-16',
}

# 1
def create_appointment(name: str, phone_no: str) -> str:
    # 2
    uid = shortuuid.ShortUUID().random(length=6).lower()

    # 3
    payload = {
        'parent': {
            'database_id': NOTION_DATABASE_ID
        },
        'properties': {
            'ID': {
                'rich_text': [
                    {
                        'type': 'text',
                        'text': {
                            'content': uid,
                        }
                    },
                ]
            },
            'Name': {
                'title': [
                    {
                        'type': 'text',
                        'text': {
                            'content': name
                        }
                    }
                ]
            },
            'Phone No.': {
                'phone_number': phone_no
            },
        },
    }

    # 4
    response: Response = requests.post(
        f'{NOTION_API_BASE_URL}/pages', data=json.dumps(payload), headers=NOTION_REQ_HEADERS)

    # 5
    if response.status_code == 200:
        return uid
    else:
        raise Exception(
            'Something went wrong while creating an appointment in Notion')

In the above code:

  1. You define the create_appointment function to which you pass the user’s name (name) and user’s phone number (phone_no).
  2. You create a UID of length 6 with the help of shortuuid module.
  3. You define the payload object, which contains the information about the appointment. You can read more about property values in the Notion documentation.
  4. You send a POST request to the Notion API to create a page. You can read more about creating a page from Notion's documentation.
  5. If the response’s status code is 200, you return the uid of the appointment to the user — otherwise you raise an exception.

Next, create a .env file in the project’s root directory and add the following two environment variables to it:

NOTION_API_TOKEN=<YOUR_NOTION_API_TOKEN>
NOTION_DATABASE_ID=<YOUR_NOTION_DATABASE_ID>

Note: The <YOUR_NOTION_API_TOKEN> token is the secret token that you got while creating the Notion integration earlier.

To get your database ID, check out the URL structure for a Notion page - https://www.notion.so/{workspace_name}/{database_id}?v={view_id}. The part that corresponds to {database_id} in the URL is your database’s ID. It is a 36-character long string.

If your Notion database is not within a workspace, or if it simply doesn’t match the URL shown above, it probably looks like this: https://www.notion.so/{database_id}?v={view_id}.

Getting Appointment Details

After the user has created an appointment, they should be able to get the appointment details.

To do so, first, update the main.py file by adding the following code to it:

...

# 1
def get_appointment_details(uid: str) -> dict:
    # 2
    payload = {
        'filter': {
            'property': 'ID',
            'rich_text': {
                'equals': uid
            }
        },
    }

    # 3
    response: Response = requests.post(
        f'{NOTION_API_BASE_URL}/databases/{NOTION_DATABASE_ID}/query', data=json.dumps(payload), headers=NOTION_REQ_HEADERS)

    # 4
    if response.status_code == 200:
        json_response: dict = response.json()
        results = json_response['results']
        if len(results) > 0:
            appointment_details = results[0]
            appointment_date = appointment_details['properties']['Scheduled On']['date']
            # 5
            return {
                'id': appointment_details['properties']['ID']['rich_text'][0]['plain_text'],
                'name': appointment_details['properties']['Name']['title'][0]['plain_text'],
                'phone_no': appointment_details['properties']['Phone No.']['phone_number'],
                'scheduled_on': datetime.fromisoformat(appointment_date['start']).strftime('%d %B, %Y at %I:%M %p') if appointment_date is not None else None
            }
    else:
        raise Exception(
            'Something went wrong while getting the appointment details from Notion')

In the above code:

  1. You define the get_appointment_details function, to which you pass the UID (uid) of the appointment.
  2. You define the payload object for creating a filter to fetch the appointment with the specified UID. You can read more about filtering the database from the Notion documentation.
  3. You send a POST request to Notion to get the required appointment. You can read more about querying a database from the Notion documentation.
  4. If the response’s status code is 200, you get the following response:
Data returned from Notion API
  • As the response is pretty big and you don’t need all of the data, you reduce the response by creating a new object in which you return the ID, Name, Phone number, and Scheduled On time for the appointment.
  • Adding an API Endpoint

    To receive WhatsApp messages in your FastAPI application, you need to provide Twilio with a URL to which it can forward the incoming messages. So, you need to create an API endpoint to receive messages from Twilio.

    To do so, first, update the main.py file by adding the following code to it:

# 1
from fastapi import FastAPI, Form, Response, Request, HTTPException
...

# 2
app = FastAPI()

# 3
@app.post('/message')
def handle_message(request: Request, From: str = Form(...), Body: str = Form(...)) -> dict:
    # 4
    if 'BOOK' in Body:
        args = Body.split(' ')
        name = args[1]
        phone_no = From.replace('whatsapp:', '')
        uid = create_appointment(name, phone_no)
        if uid:
            respond(From, f'An appointment has been created. Appointment ID is {uid}. Use this ID for any future communication.')

    # 5
    elif 'STATUS' in Body:
        args = Body.split(' ')
        uid = args[1]
        status = get_appointment_details(uid)
        if status:
            if status['scheduled_on']:
                return respond(From, f"Your appointment is scheduled on {status['scheduled_on']}.")
            else:
                return respond(From, f'Your appointment is yet to be scheduled. Please check again later.')
        else:
            return respond(From, f'Your appointment could not be found.')

    # 6
    else:
        return respond(From, 'Hi.\\nTo create an appointment, send BOOK <name> <phone_no>\\n To get the status of your appointment, send STATUS <appointment_id>\\nThanks')

In the above code:

  1. You import the required classes and methods from the fastapi module.
  2. You create a FastAPI instance (app).
  3. You define a URL path @app.post('/message') to which POST requests can be made and its corresponding controller function handle_message.
  4. If the message body (Body) contains the word BOOK, you define the logic to create an appointment. To create an appointment, the user is required to send a message in the following format:
BOOK <name>
  • If the message body contains the word STATUS, you define the logic to get the details of an appointment. For appointment details, the user is required to send a message in the following format:
STATUS <appointment_id>
  • For any other message, you send the instructions to the user on how to create an appointment or how to get the details of an appointment.

In the above code, you can see the function respond. In the next section, you’ll be writing the respond function for sending WhatsApp messages via Twilio.

Sending WhatsApp Notifications

To send appointment-related messages to the user, you can use the Twilio WhatsApp API to perform this task.

To do so, first, log in to your Twilio account and visit the Twilio Console. On the console, look out for the Account Info section and obtain the Account SID and Auth Token.

Account Info in Twilio Console

Next, add them as environment variables to your .env file:

TWILIO_ACCOUNT_SID=<YOUR_TWILIO_ACCOUNT_SID>
TWILIO_AUTH_TOKEN=<YOUR_TWILIO_AUTH_TOKEN>

Next, update the main.py file by adding the following code to it:

# 1
from twilio.rest import Client

...

# 2
TWILIO_ACCOUNT_SID = os.getenv('TWILIO_ACCOUNT_SID')
TWILIO_AUTH_TOKEN = os.getenv('TWILIO_AUTH_TOKEN')

...

# 3
def respond(to_number, message) -> None:
    twilio_client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
    from_whatsapp_number = 'whatsapp:+14155238886',
    twilio_client.messages.create(body=message,
                                  from_=from_whatsapp_number,
                                  to=to_number)

In the above code:

  1. You import the Client class from the twilio module.
  2. You define the TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN environment variables.
  3. You define the respond function that takes to_number and message as parameters to send the message to the specified number.

Finally, your code is complete, and next, you need to configure your Twilio WhatsApp sandbox.

Forwarding Requests from Twilio to FastAPI

Before you can test your appointment booking system, you need to expose the localhost URL of your FastAPI application to the public internet.

To do so, first, start the FastAPI application by running the following command in your terminal:

uvicorn main:app --reload

The above command will start your server at http://127.0.0.1:8000:

Log messages that appear when server starts

Next, open another terminal window and run the following command in your terminal to get a public Internet URL for your localhost URL:

ngrok http 8000

The above command will provide you with an HTTP and HTTPS URL:

Log messages from ngrok

Next, copy the HTTPS URL and visit the WhatsApp Sandbox in Twilio Console and add <HTTPS_URL>/message URL to “WHEN A MESSAGE COMES IN” under the Sandbox Configuration section and click Save:

WhatsApp Sandbox configuration

Now whenever Twilio receives a WhatsApp message, it will forward the request to the specified URL.

Testing

To verify all of your work till now, open WhatsApp and connect to the Twilio WhatsApp sandbox by sending a message to the Twilio WhatsApp sandbox phone number as specified under the Sandbox Participants section:

Invite code for Twilio WhatsApp Sandbox

WhatsApp message from the Twilio Sandbox

Once you have joined the Twilio Sandbox, send the following message to book an appointment:

BOOK John

If the request is successful, you will receive the following response with the appointment’s UID:

WhatsApp conversation for creating an appointment

Check the Notion database as well for the newly created appointment:

New appointment created in Notion Appointments database

Next, to check the status of your appointment, send the following message:

STATUS <appointment_id>

If the request is successful, you will receive the following response saying that your appointment is yet to be scheduled:

WhatsApp conversation for checking the status of an appointment

Next, visit your Notion database and update the Scheduled On field for the new appointment:

Updating the appointment in Notion database

Next, send the status message again and you will receive the following response with the appointment details:

WhatsApp conversation for checking the status of a scheduled appointment

Finally, send a status message with an incorrect UID and you will receive the following response saying that the appointment doesn’t exist:

WhatsApp conversation for checking an appointment with an incorrect UID

And with that, you have successfully implemented an appointment booking system.

Conclusion

Well done! In this tutorial, you learned to book appointments and get appointment details as well using Twilio, Notion, and FastAPI. You used Notion to store the appointment’s data, FastAPI for the logic and REST API, and Twilio for WhatsApp notifications.

Let me know if this project helped you or just say Hi! by reaching out to me over email! The entire code for this project is available in this GitHub repository.

Ravgeet is a remote, full-time, full-stack developer and technical content writer based in India. He works with and writes about React, Vue, Flutter, Strapi, Python, and Automation. He can be reached via: