this is a python-based course monitoring application that fetches course section information from the university of maryland's testudo, tracks changes, and sends email notifications when updates occur.
built for local use and deployment on render.
- persistence: environment-based state tracking. uses local json files by default for the cli, with optional upstash redis support for cloud deployments (configurable via
PERSISTENCE_MODE). - dockerized: bundles the app using
uvfor fast, reproducible builds. - smart term detection: automatically targets spring or fall based on the current date (with manual overrides).
- fastapi server: full api for health checks, listing mappings, and triggering monitoring cycles.
- automated monitoring:
- tracks new sections.
- tracks seat availability changes.
- tracks section removals.
- sends html email notifications.
this project uses uv for extremely fast dependency management.
uv synccreate a .env file with the following:
EMAIL_USER: your gmail address (for local SMTP fallback).EMAIL_PASS: your gmail app password (for local SMTP fallback).RESEND_TOKEN: your resend.com api key (required for production).API_KEY: a secret key of your choice to restrict API access (e.g.,my-super-secret-key).EMAIL_FROM: the verified sender email for resend (defaults toonboarding@resend.dev).REDIS_URL: your upstash redis rest url.REDIS_TOKEN: your upstash redis rest token.PERSISTENCE_MODE: set toredisorlocal(defaults tolocal).
render's free tier blocks all outbound smtp traffic (ports 25, 465, 587). to send notifications from render, you must use the resend http api:
- sign up: create a free account at resend.com.
- get api key: create an api key and add it as
RESEND_TOKENin your render environment variables. - set sender: by default, resend free accounts can only send from
onboarding@resend.dev. the application uses this as the defaultEMAIL_FROM. - recipient rule: on the Resend free tier, you can only send emails to the same email address you used to sign up for Resend. ensure the email in your
user-course-map.jsonmatches your Resend account email.
Important
if you want to send notifications to multiple people or different addresses, you must verify a custom domain in the Resend dashboard.
to prevent unauthorized users from triggering your monitor or viewing your mappings, you can set an API_KEY environment variable.
- set the key: add
API_KEYto your render environment variables with a secret value. - use the header: when making requests to
/api/monitoror/api/mappings, include theX-API-Keyheader:curl -X POST https://your-app.onrender.com/api/monitor \ -H "X-API-Key: your-secret-key"
Note
the /api/health endpoint remains public so you can perform health checks if needed.
# add a mapping
uv run main.py add
# list current mappings
uv run main.py list-mappings
# start the monitor locally (continuous loop)
uv run main.py monitor --interval 15
# run the monitor once (ideal for cron)
uv run main.py monitor --once
# start the api server
uv run main.py serve
# set default persistence mode via config file
uv run main.py config --mode redisnotes:
- mappings live in
user-course-map.json. update this file locally and push to trigger changes in production. - the
monitorcommand prompts for a term id by default. use--no-promptor--oncefor non-interactive runs.
GET /api/mappings: list all bundled course mappings.POST /api/monitor: trigger a single monitoring cycle.GET /api/health: service health status.
this project is designed to be deployed to render using the provided render.yaml blueprint.
- connect repository: connect your github repository to render.
- blueprint: render will automatically detect the blueprint and provision a web service (api).
- secrets: set your
EMAIL_USER,EMAIL_PASS,REDIS_URL,REDIS_TOKEN, andAPI_KEYin the render dashboard.
to trigger the monitor every 30 minutes for free, use the included github action:
- github secrets: in your repo settings, go to
Settings > Secrets and variables > Actionsand add:RENDER_URL: your app's public url (e.g.,https://testudot.onrender.com).API_KEY: the same secret key you set in render.
- enable: the action in
.github/workflows/monitor.ymlwill now run automatically every 30 minutes.
- heuristic: maps oct-feb to spring (
01) and march-sept to fall (08). - override: to target summer (
05) or winter (12), use the--termflag in the cli or provide it via the api.
- core: beautifulsoup4 (scraping), upstash-redis (state)
- api: fastapi + uvicorn
- cli: typer + rich
- deployment: render (docker runtime)
mit license.