Automatically search for Alaska Airlines award flight availability and get notified of deals via email.
- 🔍 Search multiple routes in parallel
- 🔥 Highlight deals below your mileage threshold
- 📅 Filter by date range
- ⏰ Scheduled searches every 10 minutes via GitHub Actions
- 📧 Email notifications when deals are found
| Search | Route | Date Range | Deal Threshold |
|---|---|---|---|
| Main (nonstop) | SFO → LIR | Dec 26, 2026 | < 50k miles |
| Main (nonstop) | LIR → SFO | Jan 2, 2027 | < 50k miles |
Both searches are for 2 adults using Lowest+price+available, restricted to nonstop Main fares. Prices and the below-50k threshold are per passenger.
Nonstop searches use the detailed flight-results page, apply “Nonstop only”, and verify each flight card. They support one exact date per search. Calendar searches remain available by omitting nonstop_only or setting it to False.
Each route gets up to 3 attempts, with 5 and 15 seconds between attempts and a fresh browser for each nonstop attempt. Exhausted searches produce an Actions warning and are skipped; even if both routes are skipped, the search step exits successfully so temporary search failures do not trigger workflow-failure emails. Successful routes still proceed to the deal summary. A skipped route has unknown availability, not zero fares, and previous parsed results are removed before each refresh. Dependency installation and other workflow failures can still fail the workflow.
Search logs include route, date, attempt, step, elapsed time, and tracebacks. Browser failures also save the failed step, current URL, filter-button expansion state, and existing nonstop options' visibility/checked state in the raw JSON. Diagnostic collection is limited to 3 seconds. Initial and final nonstop checks have distinct step names. Raw files contain the latest attempt; Actions logs retain earlier attempts.
# Install dependencies
pip install -r requirements.txt
playwright install chromium
# Run the script
python alaska.pyResults are saved to the results/ directory.
Each search now writes separate raw and parsed files so multiple cabin searches on the same route do not overwrite each other.
Run the parser and failure-handling checks with python -m unittest -v.
The workflow runs every 10 minutes and sends an email when deals are found.
- Go to your repo → Actions tab
- Click "I understand my workflows, go ahead and enable them"
-
Create a Gmail App Password:
- Go to https://myaccount.google.com/apppasswords
- Generate a new app password for "Mail"
-
Add secrets to your repo:
- Go to Settings → Secrets and variables → Actions
- Add
EMAIL_USERNAME: your Gmail address - Add
EMAIL_PASSWORD: the 16-character app password
- Manual: Actions → "Alaska Award Flight Search" → "Run workflow"
- Automatic: Runs every 10 minutes
- Logs: Actions → click workflow run → "Display results and check for deals"
- Email: Sent automatically when deals are found
Edit alaska.py to modify searches in the main() function:
searches = [
{
"origin": "SFO",
"destination": "LIR",
"outbound_date": "2026-12-26",
"date_range_start": "2026-12-26",
"date_range_end": "2026-12-26",
"highlight_below": 50, # Alert if below this mileage
"adults": 2,
"fare_type": FARE_TYPES["lowest"],
"search_name": "Main",
"nonstop_only": True,
},
{
"origin": "LIR",
"destination": "SFO",
"outbound_date": "2027-01-02",
"date_range_start": "2027-01-02",
"date_range_end": "2027-01-02",
"highlight_below": 50, # Alert if below this mileage
"adults": 2,
"fare_type": FARE_TYPES["lowest"],
"search_name": "Main",
"nonstop_only": True,
},
# Add more routes...
]Available fare types:
FARE_TYPES["lowest"]→Lowest+price+availableFARE_TYPES["partner_premium"]→Partner+PremiumFARE_TYPES["partner_business"]→Partner+Business
Edit .github/workflows/alaska.yml:
schedule:
- cron: '*/10 * * * *' # Every 10 minutes.
├── alaska.py # Main scraper script
├── requirements.txt # Python dependencies
├── .gitignore # Ignore results/ directory
├── .github/
│ ├── workflows/
│ │ └── alaska.yml # GitHub Actions workflow
│ └── scripts/
│ └── summarize.py # Results summary script
└── results/ # Output directory (gitignored)
├── alaska_*_raw.json # Raw API responses, one file per search
└── alaska_*_parsed.json # Parsed flight data, one file per search