Cron-friendly email alerts for significant USGS earthquakes and NWS weather events. Runs on a schedule, pulls fresh events, filters by your thresholds/AOI, and emails concise HTML + plain-text digests.
- Multi-provider: USGS seismic events, NWS watches/warnings/advisories
- Config-driven: thresholds, recipients, providers, and optional AOI in
config/*.yaml - Email out of the box: HTML + text bodies via SMTP (yagmail or standard SMTP)
- Cron-ready: single entrypoint script for scheduled runs
- Tested: unit tests keep parsing/formatting stable
disaster-alerts/
├─ config/ # YAML configs (thresholds, recipients, providers, optional AOI)
├─ cron/ # Example crontab snippets (optional)
├─ logs/ # Runtime logs (gitignored)
├─ scripts/
│ └─ run.sh # One-shot runner for local/cron
├─ src/
│ └─ disaster_alerts/ # Package source code
├─ tests/ # pytest test suite
├─ .env.example # Copy to .env and fill SMTP creds
├─ environment.yml # Conda environment
├─ pyproject.toml # Build/deps for pip/uv/poetry
└─ README.md
git clone https://github.com/ehavazli/disaster-alerts.git
cd disaster-alertsUsing Conda (recommended):
conda env create -f environment.yml
conda activate disaster-alertsThen make an editable install:
pip install -e .pre-commit installThis runs linting and formatting checks before each commit.
Copy .env.example to .env and set your SMTP credentials:
cp .env.example .envCommon values:
YAGMAIL_USER=you@example.com
YAGMAIL_APP_PASSWORD=app_password_or_token
For Gmail, create an app password and use that instead of your regular password.
Edit the YAML files under config/:
app.yaml– enable/disable NWS, USGS, set email routing,AOIthresholds.yaml– e.g., USGS minimum magnitude, NWS event types to includerecipients.yaml– recipient groups and email addressesnws_events_list.json- list of all events available via NWS alerts
Keep configs minimal and explicit; the pipeline only reads what you define.
./scripts/run.shYou should see a small log and (if events match your filters) receive an email.
Open your crontab:
crontab -eRun every 15 minutes, logging to logs/cron.log:
*/15 * * * * /bin/bash -lc 'cd /path/to/disaster-alerts && conda activate disaster-alerts && ./scripts/run.sh >> logs/cron.log 2>&1'Tips:
- Use absolute paths in cron.
- The
-lcensures your conda initialization is sourced. - Rotate
logs/as needed.
To display the HTML events map in a web browser from a disaster-alerts cron job using Flask:
Run Flask in the background:
nohup python web/app.py > logs/flask.log 2>&1 &Check if the job is running, and kill it when needed
ps aux | grep '[w]eb/app.py'
kill <pid>You can then access the HTML event map locally at http://localhost:8000 after SSH'ing the location of the code (if needed)
ssh -L 8000:localhost:8000 username@server/domain_nameGET /- Main page showing HTML event mapGET /test_ping- Health check endpointPOST /process_bbox- Process events for a bounding boxGET /processing_status- Check processing job statusGET /maps/<run_id>/<filename>- Retrieve generated map filesGET /show_maps- List all available maps
- Typical filters:
min_magnitude,max_depth_km - Recent change: Earthquake reporting window extended to 1 week to keep events visible longer in HTML map
- Note: Magnitude threshold adjustable in
thresholds.yaml
- Typical filters: allowed product types (e.g., Severe Thunderstorm Warning, Flash Flood Warning, etc. see list),
aoi - Shapes may be polygons or county/zone references
- Define groups (keys) that map to one or more email addresses
- Use these groups in the pipeline/tests for clarity
- Subject: Provider + concise event summary
- HTML + Text body:
- Deduplicated, sorted list of new/updated events
- Core attributes (time, severity, magnitude, location)
- Links to official pages (USGS event, NWS product)
- Optional geometry summary (centroid, bbox, polygon size)
Editable install:
pip install -e .[dev]pytest # Run all tests
pytest tests/test_providers.py # Run specific test file
pytest -v # Verbose output
pytest --cov=src # With coverage reportWhere to add things:
- New providers →
src/disaster_alerts/providers/ - Filters →
src/disaster_alerts/filters.py(or a new module) - Email rendering → message builder/templates in
src/disaster_alerts/
- Create
src/disaster_alerts/providers/<name>.py - Implement:
fetch()→ returns raw itemsnormalize()→ yields internalEventdicts with a consistent schema
- Wire it in the provider registry and
providers.yaml - Add unit tests in
tests/to lock in parsing behavior
- Write a boolean function that takes a normalized
Eventand returnsTrue/False - Register it in the pipeline before email formatting
- Add tests to cover edge cases (missing fields, boundary thresholds)
- Tweak HTML/text templates and the message builder
- Keep both HTML and text in sync for deliverability
- Secrets live only in
.env(never commit them) - Avoid logging secrets; keep logs minimal
- Use app-specific passwords or tokens
- Prefer a low-privilege sender identity (e.g.,
alerts@yourdomain)
- No emails? Check:
logs/for errors- SMTP credentials / network egress / SPF-DKIM
- Thresholds too strict / AOI excludes all events
- Cron runs but fails silently:
- Use absolute paths
- Ensure the environment is activated in the cron command
- Redirect stderr (
2>&1) to a log
- HTML shows weird characters:
- Ensure UTF-8 when composing and sending
- Tests fail locally but pass in CI:
- Pin package versions via
environment.ymlorpyproject.toml
- Pin package versions via
- Web app won't start:
- Check port 8000 not already in use:
lsof -i :8000 - Verify Flask installed:
pip list | grep -i flask - Check logs at
logs/flask.log - If multiple processes running, kill all and restart:
pkill -f 'web/app.py' nohup python web/app.py > logs/flask.log 2>&1 &
- Check port 8000 not already in use:
- Dependency conflicts after
conda env update:- Most packages unpinned - may get breaking changes
- Pin critical versions in
environment.ymlif needed - Use
conda env export > environment.lock.ymlto capture working state
Apache-2.0 (see LICENSE).