Receive Flask Error Alerts by Email with Twilio SendGrid

April 17, 2020
Written by
Kyle Lawlor-Bagcal
Contributor
Opinions expressed by Twilio contributors are their own

Receive Flask Error Alerts by Email with Twilio SendGrid

It goes without saying that errors happen all the time in production. Gaining visibility into your application failures is the first step into being able to understand and therefore fix these errors.

In this tutorial we will set up email alerting in a Flask application with SendGrid. Every time an unhandled exception occurs, the application will send an email alert to an admin who can then determine the next steps for fixing the issue.

Tutorial requirements

Working through this tutorial will require access to the following items:

  • Python 3.6 or newer. If your operating system does not provide a Python interpreter, you can go to python.org to download an installer.
  • A Twilio SendGrid account. This article will walk through setting that up if you don’t already have an account. A free SendGrid account allows you to send up to 100 emails per day forever.

Setting up Twilio SendGrid

We are going to get started by setting up SendGrid for programmatic access. If you already have an account please skip to the “Getting your API key” section.

Creating your SendGrid account

If you don’t have a Twilio SendGrid account, the first step is to create it. To create your account follow these steps:

  1. Follow this link to the SendGrid sign-up page.
  2. Enter your username, password and email address.
  3. Proceed with the remainder of the form and then click “Create Account”

You will be prompted to give more information about yourself, please complete this as you see fit.

Verifying your account

You will receive an email from SendGrid to verify your account. Please complete the verification process before moving forward. Once you are done with that, please navigate to your SendGrid dashboard.

Getting your API key

Now we need to get the API key from SendGrid. The API key will allow us to authenticate with and make API requests to SendGrid.

  1. From the dashboard, click on “Settings” and then click on “API Keys”
  2. Click the “Create API Key” button
  3. Let’s name the API Key “Flask Error Alerts”
  4. Select “Restricted Access”
  5. Go to the “Mail Send” dropdown and click it
  6. Enable the “Mail Send” option by using the slider
  7. Scroll to the bottom and click “Create & View”

sendgrid api keys

sendgrid create api key

When we are done with these steps we will be provided with the API Key. Let’s copy and paste it into an empty text file for now.

Create a Python virtual environment

The next thing we need to do is get our Python environment set up and running. This setup will be used throughout the tutorial, so let’s make sure we get this right.

Following Python best practices, we are going to make a new directory for our project, and inside it we are going to create a virtual environment. We then are going to install the Python packages that we need within the virtual environment.

If you are using a Unix or Mac OS system, open a terminal and enter the following commands to do the tasks described above:

$ mkdir flask-error-alerts
$ cd flask-error-alerts
$ python3 -m venv venv
$ source venv/bin/activate
(venv) $ pip install flask python-dotenv sendgrid

For those of you following the tutorial on Windows, enter the following commands in a command prompt window:

$ md flask-error-alerts
$ cd flask-error-alerts
$ python -m venv venv
$ venv\Scripts\activate
(venv) $ pip install flask python-dotenv sendgrid

The last command uses pip, the Python package installer, to install the two packages that we are going to use in this project, which are:

  • Flask is a microframework for web applications.
  • Flask can optionally use python-dotenv for managing environment variables, so we are also installing this package.
  • The sendgrid library is the official Twilio SendGrid Python API client.

Setting up the Flask application

Now that we have a directory for our project and virtual environment we can get going on writing the code for our basic Flask application. Let’s navigate into the flask-error-alerts directory in the previous step and fire-up our favorite text editor. We are going to create two files:

  1. .env
  2. app.py

The first file is called our “dot-env” file. It will be where we place our SendGrid API key from the previous step, as well as other configuration values for our Flask application. The second file is where our Flask application will be located. So with that said let’s make some changes to these files and test our Flask application.

Edit the .env file to contain:

SENDGRID_API_KEY=your-sendgrid-api-key-goes-here

Edit app.py to contain:

from flask import Flask

app = Flask(__name__)


@app.route("/")
def index():
    return "Helloworld!"


Now we can check the application runs as expected. To do that let’s first open a terminal make sure our virtualenv is active and then we’ll run the Flask app with the built-in development server:

$ flask run

Next let’s navigate our browser to localhost:5000/ and check that we see the “Helloworld!” text.

hello world flask application

Configuring alerts with SendGrid

Creating an unhandled exception

Now we need to configure the Flask application to send us email alerts when there are unhandled exceptions. The first thing we are going to do is modify our app.py to throw an unhandled exception.

from flask import Flask

app = Flask(__name__)


class BigBangException(Exception):
    pass


@app.route("/")
def index():
    raise BigBangException("Something bad happened here..")
    return "Helloworld!"

Now that we have created a situation where an unhandled exception occurs, let’s restart the application and observe the current behavior. Use “Ctrl+C” to stop the Flask application, and let’s start it again:

$ flask run

Now when we navigate to localhost:5000/ we are getting a 500 internal server, uh oh! But this is what we expected, the same will happen on an application that crashes due to a bug.

internal server error

Hooking into unhandled exceptions

In order to be able to fire off an email in response to an unhandled exception, we need to add some code that will allow us to hook into Flask’s built-in exception handler. Once we’ve done that we will be able to use SendGrid to send an email about the exception. Fire up that text editor and let’s add this code to app.py:

import traceback
from flask import Flask
from werkzeug.exceptions import InternalServerError

app = Flask(__name__)


@app.errorhandler(InternalServerError)
def handle_500(e):
    error_tb = traceback.format_exc()
    return app.finalize_request(e, from_error_handler=True)


class BigBangException(Exception):
    pass


@app.route("/")
def index():
    raise BigBangException("Something bad happened here..")
    return "Helloworld!"

One noteworthy item is that Flask has built-in error handlers which you can read more about here. We use the @app.errorhandler decorator to register a function with the Flask application. It’s quite similar to the @app.route decorator except that it allows us to register a function that gets executed when particular types of errors occur in our application. In this case we are paying attention to the InternalServerError exception. This is the exception that Flask and it’s underlying utility library Werkzeug raise whenever an unhandled exception occurs.

Another point to take note of is that we are using Python’s built-in traceback module to retrieve the traceback information. The variable we created called error_tb is a string that contains the traceback. This traceback is exactly the same as what we see in the terminal when the unhandled exception occurs in the Flask application. We will pass this information into our SendGrid email in the next section.

We also added a call to the app.finalize_request method. This maintains the default behaviour of our Flask application, which is that when an unhandled exception occurs, we will still return the internal server error response to the browser.

Sending the alert email with SendGrid

We’re now at the point where we can set up the code to send the alert email. So let’s do that by updating app.py to contain the following:

import os
import traceback
from flask import Flask
from werkzeug.exceptions import InternalServerError
import sendgrid
from dotenv import load_dotenv

load_dotenv()
app = Flask(__name__)
sg = sendgrid.SendGridAPIClient()


def create_message(email_text):
    return sendgrid.helpers.mail.Mail(
        from_email=os.environ["FROM_EMAIL"],
        to_emails=os.environ["TO_EMAIL"],
        subject='[my app] unhandled exception occurred!',
        plain_text_content=email_text,
    )


@app.errorhandler(InternalServerError)
def handle_500(e):
    error_tb = traceback.format_exc()
    try:
        resp = sg.send(create_message(error_tb))
    except Exception as exc:
        print(exc.message)
    return app.finalize_request(e, from_error_handler=True) 


class BigBangException(Exception):
    pass


@app.route("/")
def index():
    raise BigBangException("Something bad happened here..")
    return "Helloworld!"

We have added a function called create_message which sets up a SendMail Mail object. This object pulls in the FROM_EMAIL and the TO_EMAIL from the .env file. With that said we need to add the following lines into our .env file:

SENDGRID_API_KEY=your-sendgrid-api-key-goes-here
FROM_EMAIL=your-email-address
TO_EMAIL=your-email-address

Because we are using the .env file to load values we also had to add a call to the dotenv module’s load_dotenv function. This ensures that when our code runs the required environment variables will be available in our application code.

Near the top we have created an instance of the SendGrid API client:

sg = sendgrid.SendGridAPIClient()

This client automatically looks for an environment variable named SENDGRID_API_KEY and uses it to authenticate against the SendGrid servers. As with the other variables in our .env file, the load_dotenv call ensures that the variable is imported into the environment of the process.

The last noteworthy addition in this section is the call we make to the SendGrid API:

    try:
        resp = sg.send(create_message(error_tb))
    except Exception as exc:
        print(exc.message)

All that’s happening in that code block is that we attempt to send the email using SendGrid but if an exception occurs while doing that we just print out what the error was. In a production application you might want to retry this operation because the most likely source of error here is that the SendGrid service is temporarily unavailable.

Testing the alerts

Once you’ve made the changes to your code, let’s test it out! So the expectation here is that we visit our Flask application at localhost:5000/ we are going to:

  1. Receive a 500 internal server error in the browser.
  2. Receive an email alert that contains the traceback.

To test that we need to restart the Flask application so that our changes take effect. So if you have the Flask development server running, stop that with “Ctrl+C” and then restart it with:

$ flask run

Now let’s navigate to localhost:5000/ and receive the 500 internal server error. A minute or two later you should receive an email that looks like the following:

error alert by email

Conclusion

There you have it! We have set up our Flask application to send email alerts whenever unhandled exceptions occur. With a relatively small amount of code we have built out a pretty handy alerting system.

I hope you’ve enjoyed following along in this tutorial and if you have any questions feel free to reach out. All the code developed in this tutorial can be found here.

Kyle Lawlor-Bagcal (wgwz [at] protonmail [dot] com) is a software engineer and systems developer who is passionate about good code and constantly learning new things. https://github.com/wgwz