Using Event Webhooks for Emails with Twilio SendGrid in Python

April 06, 2022
Written by
Sam Agnew
Twilion

Copy of C04 Blog Text.png

When sending emails with Twilio SendGrid, sometimes you want to be able to keep track of the status of these emails, such as when a recipient opens the message or reports it as spam. This is made possible by the use of Event Webhooks. Let's walk through how to use Python and Flask to track the status of emails that you send.

Prerequisites and dependencies

Make sure you have the following before moving on:

  • Python 3 installed on your machine
  • A free SendGrid account
  • An email address to test out this project
  • A domain on which you will receive emails. For the purposes of this article, I’m going to use yourdomainhere.com. You will need to replace it with your own domain name.

Here is a guide you can follow for setting up your development environment if you are going to be doing more web development with Python in general and are unfamiliar with things like virtual environments.

Before writing code, you'll need to install some dependencies:

Make sure you create and activate a virtual environment, and then install these with the following command:

pip install sendgrid==6.9.6 Flask==1.1.2

Sign up for SendGrid and create an API key

When creating a SendGrid account, you can choose the free tier for the purpose of this tutorial. Once you have an account, you need to create an API key as seen in this screenshot. You can name it whatever you want, but once it is created make sure you save it before moving on!

Creating a SendGrid API Key

A good way to save this API key is to set it as an environment variable that you can access from your Python code in order to avoid writing it directly in your code. Set the value of the SENDGRID_API_KEY environment variable to be the API key from your SendGrid account. It still doesn't hurt to make note of it elsewhere though, because you cannot view it again. Here's a useful tutorial if you need help setting environment variables. We will use this API key later on.

Sending an Email with Python

Before being able to track the status of emails, we need to actually send one. Let's write some quick code to send an email containing a picture taken by the Mars Rover. Create a file called mars_email.py and add the following code to it:

import os

from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail


def send_mars_email(from_email, to_email, img_url):
    message = Mail(
        from_email=from_email,
        to_emails=to_email,
        subject='Here is your Mars Rover picture',
        html_content='<strong>Check out this Mars pic</strong><br><br>'
                     f'<img src="{img_url}"></img><br><br>'
                     f'<a href="{img_url}">Here is a link to the Mars Pic</a>')

    sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))

    response = sg.send(message)
    print(response.status_code, response.body, response.headers)


send_mars_email('from@example.com', 'to@example.com', 'https://mars.nasa.gov/msl-raw-images/proj/msl/redops/ods/surface/sol/01000/opgs/edr/fcam/FLB_486265257EDR_F0481570FHAZ00323M_.JPG')

This code contains a function for sending an email that contains a picture, and some standalone code at the bottom that calls that function to send the following picture.

A picture taken by the Mars Rover

Replace from@example.com and to@example.com with the email addresses you want to use. Run this code with the terminal command python mars_email.py, and you should receive an email with that Mars Rover photo.

Creating a Flask application for the Event Webhook

The next step is to write some Python code to create a web application to receive HTTP requests whenever delivery events or engagement events occur with emails that you sent. Create a file called app.py and add the following code to it:

from flask import Flask, request


app = Flask(__name__)


@app.route('/event', methods=['POST'])
def event_webhook():
    event = request.json[0]['event']

    if event == 'delivered':
        print('Your email has been delivered!')
    elif event == 'open':
        print('Your email has been opened.')
    elif event == 'click':
        print('The link in your email has been clicked.')
    elif event == 'spamreport':
        print('The recipient has marked your email as spam.')

    return '200'


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

This code will receive an HTTP request from Twilio SendGrid, and will print a different statement depending on which event has occurred, such as a user clicking on a link that was in the email. Run the code with either python app.py or flask run in the same directory that app.py resides in. This will run a Flask server on port 5000 of your machine. Now, we need a publicly accessible URL to provide SendGrid with for the Event Webhook.

Configuring the Event Webhook

We are going to use a tool called ngrok to create a public URL that SendGrid can use to forward requests to our locally running application.

If you haven’t yet, install ngrok on your system. With the Python code running, open a second terminal window to start ngrok as follows:

ngrok http 5000

This is telling ngrok to create a “tunnel” from the public Internet into port 5000 in our local machine, where the Flask app is listening for requests. The output should look something like this:

What the ngrok screen looks like in your terminal

With this URL handy, head to your Mail Settings page in your SendGrid dashboard, and click on "Event Webhook" as seen in the following screenshot.

Configuring the SendGrid Event Webhook

Configure the Event Webhook by copying and pasting your ngrok URL, and appending /event to it, which is the route on your Flask app that you want HTTP requests to be sent to. In this example I am checking off the "Delivered", "Opened", "Clicked", and "Spam Reports" events but you can choose whichever are relevant to you.

Setting your ngrok URL and selecting which delivery and engagement events you want to receive webhook requests for

Don't forget to click "ENABLED" for the Event Webhook Status before hitting save!

Testing it out

Now that you have a Flask application running behind an ngrok URL, and a SendGrid Event Webhook configured to send requests to that URL, you can run your code in mars_email.py one more time in another terminal window to see your delivery and engagement events logged to the screen.

A terminal screen showing the Flask application running and logging events

Try different things like clicking the link in the email, or reporting it as spam to see more events logged on your screen. From here you can change the code to fit the needs of your specific use case.

If your web server does not return a 2xx response type, SendGrid will retry a POST request until they receive a 2xx response or the maximum time has expired. All events are retried at increasing intervals for up to 24 hours after the event occurs. Note that this time limit is a rolling 24 hours, which means new failing events will receive their own 24-hour retry period.

I can't wait to see what you build with Twilio SendGrid in the future.