NBA Game Day Notifications: Real-time Alerting System using AWS Services

This project from Ifeanyi Otuonye focuses on creating an alerting system to deliver scores from sports games. I decided to go with NBA games since I enjoy watching Jokic in Denver. The services used include AWS SNS, AWS Lambda, AWS Event Bridge, IAM roles and policies, and the SportsData NBA API.
Architecture
The system queries the NBA API for scores, processes the data returned and sends it to our email via SNS. I used Terraform to provision resources on AWS to avoid using the console for projects like this.
Steps
Create an S3 bucket for the Terraform state
Create an SNS topic and subscription
Create IAM roles and policies for the SNS topic and Lambda function
Create the Lambda function
Create an EventBridge schedule
S3 bucket for Terraform state
I created an S3 bucket because I utilised an S3 backend and a pipeline to execute my Terraform commands as shown below.
resource "aws_s3_bucket" "game-day-notification-morolake" {
bucket = "game-day-notification-morolake"
}
terraform {
backend "s3" {
bucket = "game-day-notification-morolake"
key = "game-day-notification-morolake/terraform.tfstate"
region = "us-east-1"
}
}
name: Terraform CI/CD for Game Day Notification
on:
push:
branches:
- master
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Setup
uses: hashicorp/setup-terraform@v1
with:
terraform_version: 1.10.3
- name: Initialise
run: terraform init
- name: Validate
run: terraform validate
- name: Plan
run: terraform plan
- name: Apply
run: terraform apply -auto-approve
SNS topic and subscription
AWS SNS topics and subscriptions are core components used to facilitate a pub/sub messaging pattern. For this project, I used my email for the subscription endpoint. The depends_on argument is used to specify the dependency between resources and ensures that the SNS topic is created before the subscription.
resource "aws_sns_topic" "game-day-notification" {
name = "game-day-notification"
}
resource "aws_sns_topic_subscription" "game-day-subscription" {
topic_arn = "arn:aws:sns:us-east-1:<account-id>:game-day-notification"
protocol = "email"
endpoint = "morolaanney@gmail.com"
depends_on = [aws_sns_topic.game-day-notification]
}
Confirm the email subscription
IAM Role and Policy
resource "aws_iam_policy" "game-day-iam-policy" {
name = "game-day-iam-policy"
path = "/"
description = "Give permission to publish to SNS topic"
policy = jsonencode ({
Version: "2012-10-17",
Statement: [
{
"Effect": "Allow",
"Action": "sns:Publish",
"Resource": "arn:aws:sns:us-east-1:<account-id>:game-day-notification"
}
]
})
}
resource "aws_iam_role" "game-day-lambda-role-new" {
name = "game-day-lambda-role-new"
managed_policy_arns = [aws_iam_policy.game-day-iam-policy.arn, "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
Lambda Function
Retrieve the API key of your choice from SportsData.io and place that value in the NBA_API_KEY.
resource "aws_lambda_function" "game-day-lambda" {
function_name = "game-day-lambda"
runtime = "python3.13"
role = aws_iam_role.game-day-lambda-role-new.arn
filename = "notifications.zip"
handler = "notifications.lambda_handler"
environment {
variables = {
NBA_API_KEY = "api_key"
SNS_TOPIC_ARN = "arn:aws:sns:us-east-1:<account-id>:game-day-notification"
}
}
}
import os
import json
import urllib.request
import boto3
from datetime import datetime, timedelta, timezone
def format_game_data(game):
status = game.get("Status", "Unknown")
away_team = game.get("AwayTeam", "Unknown")
home_team = game.get("HomeTeam", "Unknown")
final_score = f"{game.get('AwayTeamScore', 'N/A')}-{game.get('HomeTeamScore', 'N/A')}"
start_time = game.get("DateTime", "Unknown")
channel = game.get("Channel", "Unknown")
# Format quarters
quarters = game.get("Quarters", [])
quarter_scores = ', '.join([f"Q{q['Number']}: {q.get('AwayScore', 'N/A')}-{q.get('HomeScore', 'N/A')}" for q in quarters])
if status == "Final":
return (
f"Game Status: {status}\n"
f"{away_team} vs {home_team}\n"
f"Final Score: {final_score}\n"
f"Start Time: {start_time}\n"
f"Channel: {channel}\n"
f"Quarter Scores: {quarter_scores}\n"
)
elif status == "InProgress":
last_play = game.get("LastPlay", "N/A")
return (
f"Game Status: {status}\n"
f"{away_team} vs {home_team}\n"
f"Current Score: {final_score}\n"
f"Last Play: {last_play}\n"
f"Channel: {channel}\n"
)
elif status == "Scheduled":
return (
f"Game Status: {status}\n"
f"{away_team} vs {home_team}\n"
f"Start Time: {start_time}\n"
f"Channel: {channel}\n"
)
else:
return (
f"Game Status: {status}\n"
f"{away_team} vs {home_team}\n"
f"Details are unavailable at the moment.\n"
)
def lambda_handler(event, context):
# Get environment variables
api_key = os.getenv("NBA_API_KEY")
sns_topic_arn = os.getenv("SNS_TOPIC_ARN")
sns_client = boto3.client("sns")
# Adjust for Central Time (UTC-6)
utc_now = datetime.now(timezone.utc)
central_time = utc_now - timedelta(hours=6) # Central Time is UTC-6
today_date = central_time.strftime("%Y-%m-%d")
print(f"Fetching games for date: {today_date}")
# Fetch data from the API
api_url = f"https://api.sportsdata.io/v3/nba/scores/json/GamesByDate/{today_date}?key={api_key}"
print(today_date)
try:
with urllib.request.urlopen(api_url) as response:
data = json.loads(response.read().decode())
print(json.dumps(data, indent=4)) # Debugging: log the raw data
except Exception as e:
print(f"Error fetching data from API: {e}")
return {"statusCode": 500, "body": "Error fetching data"}
# Include all games (final, in-progress, and scheduled)
messages = [format_game_data(game) for game in data]
final_message = "\n---\n".join(messages) if messages else "No games available for today."
# Publish to SNS
try:
sns_client.publish(
TopicArn=sns_topic_arn,
Message=final_message,
Subject="NBA Game Updates"
)
print("Message published to SNS successfully.")
except Exception as e:
print(f"Error publishing to SNS: {e}")
return {"statusCode": 500, "body": "Error publishing to SNS"}
return {"statusCode": 200, "body": "Data processed and sent to SNS"}
Test the lambda function after provisioning it, you should see a successful response.
Following that, check the email inbox you used for the SNS subscription and you should see an email containing the games played on the specified date alongside the scores from each quarter and the final scores.
I hope this was easy enough to understand, and here’s to hoping Joker gets a second ring. Find me on LinkedIn.
Subscribe to my newsletter
Read articles from Morolake directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Morolake
Morolake
Quality Assurance Analyst turned DevOps Engineer. Interested in all things Cloud Computing.