Automatically Send Birthday Wishes with Python Flask and WhatsApp

September 10, 2021
Written by
Ashutosh Hathidara
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by

Automatically Send Birthday Wishes with Python Flask and WhatsApp

Do you forget to send birthday wishes to your friends and loved ones? I do, and it usually results in being scolded by my fiancée! I’ve often wished for a software program that could send birthday wishes on my behalf.

In this tutorial, we will create this exact end-to-end system. We will use Twilio's What’sApp API for sending the messages, the Flask web framework for the web server, and we will run messaging jobs periodically using APScheduler. We’ll also deploy the Flask application to AWS so it can be deployed resiliently and at scale.

The complete code for this tutorial is available in this Github repository.

Prerequisites

A screenshot of the Twilio Console showing where to locate the Account SID and Auth token

The following services downloaded to your computer:

Special notes about using WhatsApp

WhatsApp has to formally approve your account before you can send messages with WhatsApp in a production capacity, even for personal projects. That doesn't mean you have to wait to start building, though! Twilio Sandbox for WhatsApp lets you test your app in a developer environment. You’ll be able to complete this tutorial using the Sandbox, but you’ll need to have your WhatsApp account approved in order for the birthday wisher to run 24/7. This is because WhatsApp Sandbox sessions expire after 3 days and must be re-enabled.

Read our Help Center article called How Can I Get my Own WhatsApp Twilio Number for Use in Production? for more information.

Additionally, there are some limitations to the amount and types of messages that can be sent using WhatsApp. Please read our Help Center article called Rules and Best Practices for WhatsApp Messaging on Twilio for details.

Create the Python virtual environment

Create a new directory for this project in your command prompt called whatsapp-birthday-wisher, then navigate to this new directory:

$ mkdir whatsapp-birthday-wisher
$ cd whatsapp-birthday-wisher

We will create a new virtual environment for this project so that the dependencies we need to install don’t interfere with the global setup on your computer. To create a new environment called “env”, run the following commands:

$ python3 -m venv env 
$ source env/bin/activate


After you source the virtual environment, you'll see that your command prompt's input line begins with the name of the environment ("env"). Python has created a new folder called env/ in the whatsapp-birthday-wisher directory, which you can see by running the ls command in your command prompt. If you are using git as your version control system, you should add this new env/ directory to a .gitignore file so that git knows not to track it. To create the .gitignore file in the whatsapp-birthday-wisher directory, run this command:

(env) $ touch .gitignore

Open the .gitignore file in the text editor of your choice, then add the env/ folder to the contents of the .gitignore file:

env/

Store environment variables securely

You’ll need to use the Account SID and Auth Token you located at the beginning of this tutorial in order to interact with the Twilio API. These two environment variables should be kept private, which means we should not put their values in the code. Instead, we can store them in a .env file and list the .env file in our .gitignore file so git doesn’t track it. A .env file is used whenever there are environment variables you need to make available to your operating system.

Note that the env/ folder created by Python for the virtual environment is not the same thing as a .env file created to store secrets.

First, create the .env file:

(env) $ touch .env

Then, add the .env file as a line item in the .gitignore file:

env/
.env  # Add this

Next, open the .env file in your favorite text editor and add the following lines, replacing the random string placeholder values with your own values:

export ACCOUNT_SID=AzLdMHvYEn0iKSJz
export AUTH_TOKEN=thFGzjqudVwDJDga

Source the .env file so it becomes available to your operating system, then print the environment variable values to your console to confirm they were sourced successfully:

(env) $ source .env
(env) $ echo $ACCOUNT_SID
(env) $ echo $AUTH_TOKEN

Install the Python dependencies

The Python packages required for the project are:

  • twilio - provides access to the What’sApp API
  • pandas - makes the birthday dates easier to work with
  • apscheduler - runs our function at a specific time
  • flask - provides the web server for our project

Dependencies needed for Python projects are typically listed in a file called requirements.txt. Create a requirements.txt file in the whatsapp-birthday-wisher directory:

(env) $ touch requirements.txt

Copy and paste this list of Python packages into your requirements.txt file using your preferred text editor:

twilio
flask
pandas
apscheduler

Install all of the dependencies with the command given below, making sure you still have your virtual environment (“env”) sourced:

(env) $ pip install -r requirements.txt

Send a WhatsApp message using Python

Sending messages with a Python script is straightforward when using the Twilio API. First, create an app.py file in the whatsapp-birthday-wisher directory:

(env) $ touch app.py

Copy and paste the starter code below into the app.py file:

import os

from twilio.rest import Client

account_sid = os.environ.get('ACCOUNT_SID')
auth_token = os.environ.get('AUTH_TOKEN')
client = Client(account_sid, auth_token)

On lines 1 and 3, we import Python’s built-in os library and the Client object from the twilio Python package which facilitates communication with the Twilio API. On lines 5 and 6, insert the Account SID and Auth token you found in the Prerequisites section of this tutorial in between the single quotes. On line 7, the Client object is instantiated with the Account SID and the Auth token.

Now we can use the client object to access Twilio’s APIs. Only one object method is needed to send a WhatsApp message after a few initial lines of code. In the example below, the from WhatsApp number is provided in your Twilio WhatsApp Sandbox. Replace the to WhatsApp number with your own number for now, including the country code. You can also customize the message associated with the body variable if you want.

Copy and paste this code into the app.py file, below the other code:

client.messages.create(
    body='Hello!',
    from_='whatsapp:+14155238886',  # This is the Twilio Sandbox number. Don't change it.
    to='whatsapp:+19876543210'  # Replace this with your WhatsApp number
)

In your command prompt, run the following command from the whatsapp-birthday-wisher directory (where the app.py file is located) to run the code snippet:

(env) $ python3 app.py

You’ll see a new message from the Twilio Sandbox appear in your WhatsApp chats. This is a great example of the power and scalability that Twilio APIs provide!

Send a birthday wish via WhatsApp

Let’s build on the code provided in the previous section by creating a function that can send birthday wishes to your loved ones. First, delete the client.messages.create() function call from the previous section. Then, copy and paste the following example code into your app.py file below the code you already have there. Keep reading for an explanation of what the code does.

def send_birthday_wish(client, recipient_number, recipient_name):
    """Send a birthday wish to a recipient using their WhatsApp number.

    Args:
        client (object): An instantiation of the Twilio API's Client object
        recipient_number (str): The number associated with the recipient's WhatsApp account,
            including the country code, and prepended with '+'. For example, '+14155238886'.
        recipient_name (str): The recipient's name

    Returns:
        True if successful, otherwise returns False
    """

    birthday_wish = """
        Hey {}, this is Ashutosh's personal birthday wisher.
        Happy Birthday to you! I wish you all the happiness that you deserve.
        I am so proud of you.""".format(recipient_name)

    try:
        message = client.messages.create(
            body=birthday_wish,
            from_='whatsapp:+14155238886',  # This is the Twilio Sandbox number. Don't change it.
            to='whatsapp:' + recipient_number
        )

        print("Birthday wish sent to", recipient_name, "on WhatsApp number", recipient_number)
        return True

    except Exception as e:
        print("Something went wrong. Birthday message not sent.")
        print(repr(e))
        return False

send_birthday_wish(client, '+19876543210, 'Ashutosh')

In the new function, we first define the birthday_wish variable as a multiline string. Feel free to customize this! Then, we define the message variable using the same syntax we used before to interact with Twilio’s Client object. For testing purposes, use the Twilio Sandbox WhatsApp number as the from_ number.

In the last line of the example code, the function is called and you’ll pass in your own WhatsApp number as the to number and your own name for testing purposes.

Notice that the code that’s responsible for sending the messages is wrapped in a try/except block. This ensures that you’ll get helpful output in your console if something goes wrong and the message isn’t sent.

To test the functionality, run the app.py file again:

(env) $ python3 app.py

You should receive the WhatsApp message on your phone. Great job so far!

Store birth dates in a CSV file

We are now able to send the birthday message but we are not storing our loved ones’ birth dates anywhere. To keep things simple, we can do this by storing the birth date information in a Comma Separated Value (CSV) file like the one shown below:

A screenshot of the example CSV file

The CSV file will contain 3 columns corresponding to the recipient’s name, their date of birth and their WhatsApp number. Each row represents one person, and you can add as many rows as you want. If you make new friends in the future (or if new family members are born!) you can simply update the CSV file.

Create a new CSV file in the whatsapp-birthday-wisher directory with this command:

(env) $ touch birth_dates.csv

Open the file in your preferred text editor. Copy and paste the two example rows shown below. Change the day and month to today’s date and use your own WhatsApp number so you can test the functionality. Note that there are no spaces in between the data points, just commas. Add as many more rows as you like.

Name,Birth Date,WhatsApp Number
Alfred,09-08-1997,+15055543332

Convert the CSV file to a pandas dataframe

We can now import the CSV file into the app.py file using the pandas library we installed earlier. The pandas library will help us convert the information in the CSV file to something called a dataframe. You can learn more about the pandas library and dataframes if you’re interested, but no knowledge of them is required for this tutorial.

First, import the datetime and pandas libraries. Note that it’s common to separate imports of built-in Python libraries from imports of 3rd-party packages with a blank line. (And if you were importing modules from your own project, those would go underneath the 3rd-party packages and be separated from them with another blank line.)

from datetime import datetime
import os

import pandas as pd
from twilio.rest import Client

Next, we’ll create a new function called create_birthdays_dataframe(). This function will load the birth_dates.csv file and return a pandas dataframe that represents the birthday info. Add the following sample code underneath the code already present in app.py, and delete the send_birthday_wish() function call that was formerly on the last line of the file.

def create_birthdays_dataframe():
    """Create a pandas dataframe containing birth date information from a CSV file.

    Args:
        None

    Returns:
        A dataframe if successful, otherwise returns False.
    """

    try:
        dateparse = lambda x: datetime.strptime(x, "%m-%d-%Y")
        birthdays_df = pd.read_csv(
            "birth_dates.csv",
            dtype=str,
            parse_dates=['Birth Date'],
            date_parser=dateparse
        )
        return birthdays_df

    except Exception as e:
        print("Something went wrong. Birthdays dataframe not created.")
        print(repr(e))
        return False

In the newly-introduced code above, the dateparse object is a Python lambda function which parses the dates. The birthdays_df object is a pandas dataframe that represents the information in our birth_dates.csv file. The code related to parsing the dates in the CSV file is wrapped in a try/except block which will print a message to the console if something goes wrong.

You can test the code to ensure that a dataframe is returned by this function. Simply add a function call at the end of app.py:

create_birthdays_dataframe()

Then re-run the file. A dataframe will print to your console.

(env) $ python3 app.py

Determine if it’s someone’s birthday today

The code we added to app.py in the previous section imports the data from the CSV file into our script, but we haven’t yet written the code to use that data.

Now that we’ve created a function to send messages and we’ve imported the CSV within our app.py file, we can schedule a daily time for the script to run. Anyone represented in the CSV file with a birth date that matches the date of the computer that’s running our program will automatically get a WhatsApp birthday message. Pay special attention to time zones if you have loved ones on the other side of the world!

We’ll create a function that checks today’s date and compares it to the birth dates in our CSV file. If the dates match, the person whose birthday it is will get a WhatsApp message. Add the code shown below to the bottom of the app.py file:

def check_for_matching_dates():
    """Calls the send_birthday_wish() function if today is someone's birthday.

    Args:
        None

    Returns:
        True if successful, otherwise returns False.
    """
    try:
        birthdays_df = create_birthdays_dataframe()
        birthdays_df["day"] = birthdays_df["Birth Date"].dt.day
        birthdays_df["month"] = birthdays_df["Birth Date"].dt.month
        today = datetime.now()
        for i in range(birthdays_df.shape[0]):
            birthday_day = birthdays_df.loc[i, "day"]
            birthday_month = birthdays_df.loc[i, "month"]
            if today.day == birthday_day and today.month == birthday_month:
                send_birthday_wish(client, birthdays_df.loc[i, "WhatsApp Number"], birthdays_df.loc[i, "Name"])
        return True

    except Exception as e:
        print("Something went wrong. Birthday check not successful.")
        print(repr(e))
        return False

check_for_matching_dates(create_birthdays_dataframe())

This new function compares the day and month of each row in our CSV file to today’s day and month. If both the days and the months match, the check_for_matching_dates() function sends a birthday wish to the person whose birthday it is by calling the send_birthday_wish() function we defined earlier. If two or more people share a birthday, they will each receive a WhatsApp message.

Schedule the birthday checker using APScheduler

Let’s schedule the check_for_matching_dates() function to run at 12:01am every day using APScheduler. Remember that the run time will be 12:01am according to the computer that is hosting your code, so you may need to adjust its time zone to better reflect where most of your loved ones live.

Import the BackgroundScheduler object from the APScheduler package in app.py:

from datetime import datetime
import os

from apscheduler.schedulers.background import BackgroundScheduler
import pandas as pd
from twilio.rest import Client

Delete the existing function call to check_for_matching_dates() at the bottom of app.py. Then add the code shown below at the very bottom of app.py:

scheduler = BackgroundScheduler()
job = scheduler.add_job(check_for_matching_dates, 'cron', day_of_week ='mon-sun', hour=0, minute=1)
scheduler.start()

The new code creates an instance of BackgroundScheduler called scheduler. We call the scheduler object’s add_job() method which defines when and how to run the check_for_matching_dates() function. To learn more about defining scheduled jobs, check out the APScheduler documentation.

If we run the app.py file from our command line now, as we’ve been doing, it will execute the job once and then the program will terminate. We need a way to keep the file running indefinitely so that the script will run every day at 12:01am. To do this, we’ll set up a lightweight web server called Flask.

Create a web server using Flask

To set up a web server using Flask, we first need to import it:

from datetime import datetime
import os

from apscheduler.schedulers.background import BackgroundScheduler
from flask import Flask
import pandas as pd
from twilio.rest import Client

Then, we can instantiate a Flask application with just 3 lines of code!

app = Flask(__name__)
if __name__ == '__main__':
    app.run()

Put this line right under the imports, towards the top of app.py:

from datetime import datetime
import os

from apscheduler.schedulers.background import BackgroundScheduler
from flask import Flask
import pandas as pd
from twilio.rest import Client

app = Flask(__name__)

account_sid = os.environ.get('ACCOUNT_SID')
auth_token = os.environ.get('AUTH_TOKEN')
client = Client(account_sid, auth_token)

Paste the other 2 lines of Flask code at the very bottom of the app.py file, underneath the scheduler-related code we added in the previous section.

scheduler = BackgroundScheduler()
job = scheduler.add_job(check_for_matching_dates, 'cron', day_of_week ='mon-sun', hour=0, minute=1)
scheduler.start()

if __name__ == '__main__':
    app.run()

You can start the Flask server with either of these commands:

(env) $ python3 app.py

or

(env) $ flask run

And that’s all the code we need! The complete code for this tutorial is available in this Github repository.

Run the Flask web server in the cloud

Running the Flask application on your local machine is fine for development purposes, but it means that your computer has to stay on 24/7 so your server doesn’t crash. And since we don’t want to miss anyone’s birthday, we need a much more resilient way to host the server. Follow the steps in Twilio’s tutorial, Deploy a Flask App on AWS EC2 Instance - No Stress Involved!.

If you prefer not to use AWS, there are other popular cloud providers you can use. Most cloud providers have a free tier or offer several months’ worth of free credits.

Conclusion

Congratulations! You created an automated birthday wisher and you never need to stress about forgetting someone’s birthday ever again.

In this tutorial you learned how to:

  • Write a function to send a WhatsApp message
  • Run functions periodically using APScheduler
  • Create a web server using Flask
  • Deploy your Flask app to the cloud provider of your choice

If you would like to extend this tutorial, you could modify the send_birthday_wish() function so you get a reminder message in WhatsApp and you can then call the person yourself.

I can’t wait to see what you build!

 

Ashutosh Hathidara is an artificial intelligence engineer with experience in full stack development and design. He can be reached via: