Scheduling International Space Station emails in Python with SendGrid and Redis Queue

December 10, 2021
Written by
Sam Agnew
Twilion

Copy of C04 Blog Text (2).png

With cool APIs like Open Notify we can programmatically access the location of the International Space Station to determine when it is flying by a specific location, and with Twilio SendGrid we can send an email notification when this occurs.

Let's walk through how to do this in Python using Redis Queue to schedule the email.

Prerequisites and dependencies

Make sure you have the following before moving on:

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 rq-scheduler==0.11.0 requests==2.26.0

The RQ and Redis Python modules will install as dependencies with RQ Scheduler. In order for RQ to work, you'll also need to install Redis on your machine. That can be done with the following commands using wget:

wget https://download.redis.io/releases/redis-6.2.6.tar.gz
tar xzf redis-6.2.6.tar.gz
cd redis-6.2.6
make

Run Redis in a separate terminal window on the default port with the command src/redis-server from the directory where it's installed.

Accessing the International Space Station's location

Let’s begin by writing code that will call the Open Notify API for a given set of coordinates and print the next time that the ISS will fly by that latitude and longitude.

Create a file called iss.py (an “International Space Station” module) in the directory where you want your code to live, and add the following function:

from datetime import datetime
import pytz

import requests

ISS_URL = 'http://api.open-notify.org/iss-pass.json'


def get_next_pass(lat, lon):
    location = { 'lat': lat, 'lon': lon }
    response = requests.get(ISS_URL, params=location).json()

    if 'response' in response:
        next_pass = response['response'][0]['risetime']
        next_pass_datetime = datetime.fromtimestamp(next_pass, tz=pytz.utc)
        print('Next pass for {}, {} is: {}'
              .format(lat, lon, next_pass_datetime))
        return next_pass_datetime
    else:
        print('No ISS flyby can be determined for {}, {}'.format(lat, lon))

The get_next_pass function in this code will make a request to the Open Notify API with a given latitude and longitude, check to see if there is a valid response, and then convert the timestamp received from the API into a Python datetime object and print the corresponding time that the ISS will fly overhead next.

To test this code, open a Python shell and run the following two lines. For this example we will use the Twilio Headquarters in San Francisco as our test location (latitude: 37.788052, longitude: -122.391472):

from iss import get_next_pass
get_next_pass(37.788052, -122.391472)

You should see something along the lines of: Next pass for 37.788052, -122.391472 is: 2021-12-09 23:58:11+00:00 with an appropriate timestamp.

The international space station

Now we can move on to writing code to send an email.

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

Now that you have a SendGrid account and API key, you can update iss.py to include code to send an email:

from datetime import datetime
import os
import pytz

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


ISS_URL = 'http://api.open-notify.org/iss-pass.json'


def send_email(from_email, to_email, body):
    message = Mail(
        from_email=from_email,
        to_emails=to_email,
        subject='International Space Station passing by!',
        html_content=body)

    sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))
    response = sg.send(message)
    print(response.status_code, response.body, response.headers)


def get_next_pass(lat, lon):
    location = { 'lat': lat, 'lon': lon }
    response = requests.get(ISS_URL, params=location).json()

    if 'response' in response:
        next_pass = response['response'][0]['risetime']
        next_pass_datetime = datetime.fromtimestamp(next_pass, tz=pytz.utc)
        print('Next pass for {}, {} is: {}'
              .format(lat, lon, next_pass_datetime))
        return next_pass_datetime
    else:
        print('No ISS flyby can be determined for {}, {}'.format(lat, lon))

Remember to make sure the SendGrid API key environment variable is set before trying to run this code.

Note that in production applications, it's recommended to verify your Sender Identity by completing Domain Authentication. A Sender Identity represents your 'From' email address—the address your recipients see as the sender of your emails. For a step-by-step tutorial on this check out: How to set up domain authentication for Twilio SendGrid.

If you want to test this code, open up a Python shell and run the following, replacing the to_email argument with your own email address:

from iss import send_email
send_email('from_email@example.com', 'your_email@example.com', 'Look up!')

You should receive an email telling you to look up after running this code.

Scheduling a task with RQ Scheduler

Now that we have a function that provides us with a datetime, and a function that sends an email, we can use RQ Scheduler. Create another file called schedule_notification.py, and add the following code to it:

from datetime import datetime

from redis import Redis
from rq_scheduler import Scheduler

import iss

scheduler = Scheduler(connection=Redis()) # Get a scheduler for the "default" queue

# Change these latitude and longitude values for any location you want.
next_pass = iss.get_next_pass(37.788052, -122.391472)

if next_pass:
    scheduler.enqueue_at(next_pass, iss.send_email,
                         'from_email@example.com', 'your_email@example.com',
                         'Look up! The ISS is flying above you!')

This is just a quick script that calls the other functions you wrote, one to find out when the ISS is passing by your location next, and another that will send you an email. In this example, I'm using the coordinates for the Twilio office in San Francisco, but you can change the latitude and longitude to be wherever you are.

Before being able to run this code, you have to make sure you're running a Redis server, an RQ worker, and the RQ Scheduler process all in other terminal windows or as background processes. You should already have run the Redis server by using the command src/redis-server from the directory where you installed Redis. Open two more terminal windows, and in both of them navigate to the directory where your code exists and activate your virtual environment for this project. In one window run the command rqworker and in another run the command rqscheduler.

With this done, you should be ready to run your code to schedule a notification:

python schedule_notification.py

Now all you have to do is wait…

Traveling through time

A screenshot from the SNES game Chrono Trigger

This is great, but it's understandable if you don't feel like waiting around just to see if your code works. If you want instant gratification, we can use the time traveling method. On Unix based systems, you can change your system time using the date command.

If the space station is supposed to fly by on December 5th, 2019 at 4:02, then on Linux you can run date -s "12/05/2019 03:02:00". On OSX you would run date 1205160219 (you can even use the -u argument if you want to use a UTC time zone, which corresponds to the datetime your Python code is printing). If all else fails, there are also GUI options to change your computer's time on most operating systems.

On OSX you can set (and reset) this by opening "Date & Time" in your System Preferences

A screenshot of the time being changed on OSX

If you want to receive notifications every time the ISS passes by instead of just once, you can schedule another notification after each message by modifying your send_email function in iss.py and adding the appropriate import statements:

from redis import Redis
from rq_scheduler import Scheduler

scheduler = Scheduler(connection=Redis()) # Get a scheduler for the "default" queue


def send_email(from_email, to_email, body):
    message = Mail(
        from_email=from_email,
        to_emails=to_email,
        subject='International Space Station passing by!',
        html_content=body)

    sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))
    response = sg.send(message)
    print(response.status_code, response.body, response.headers)
    scheduler.enqueue_at(next_pass, iss.send_email,
                         'from_email@example.com', 'your_email@example.com',
                         'Look up! The ISS is flying above you!')

To Infinity and Beyond

Now that you can receive emails whenever the International Space Station flies by, you can use RQ Scheduler for all of your Python scheduling needs. The possibilities are endless.

For another example of a project that uses RQ, check out this post on how to create a phone number that plays computer generated music that sounds like the soundtracks of old Nintendo games.