A small Python agent that runs on a Mac, checks Google Flights results through SerpApi, filters the trip against your rules, and sends a Gmail alert every time it runs.
The default configuration watches:
- Austin, Texas (
AUS) to Punta Cana, Dominican Republic (PUJ) - Departure date:
2026-07-03 - Return date:
2026-07-08 - 2 adults and 2 children, ages 4 and 5
- Maximum 1 stop each way
- Outbound departure before noon
- Return arrival before 10 PM
- Maximum total price from
MAX_TOTAL_PRICE - Optional layover-duration filtering
The project stores credentials in a local .env file. Do not commit .env.
- macOS
- Python 3
- A SerpApi account and API key
- A Gmail account with a Gmail app password
git clone https://github.com/evelita17/flight-watcher-agent.git
cd flight-watcher-agent
python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
cp .env.example .envEdit .env with your own values.
Example .env:
SERPAPI_API_KEY=your_serpapi_key
SERPAPI_NO_CACHE=0
DEPARTURE_ID=AUS
ARRIVAL_ID=PUJ
OUTBOUND_DATE=2026-07-03
RETURN_DATE=2026-07-08
ADULTS=2
CHILDREN=2
CHILD_AGES=4,5
CURRENCY=USD
MAX_TOTAL_PRICE=2200
MAX_STOPS=1
MAX_LAYOVER_MINUTES=180
IGNORE_LAYOVER_TIME=1
OUTBOUND_DEPART_BEFORE_HOUR=12
RETURN_ARRIVE_BY_HOUR=22
GMAIL_ADDRESS=your_gmail@gmail.com
GMAIL_APP_PASSWORD=your_16_character_app_password
ALERT_TO=your_destination_email@example.com
ALERT_REPEAT_MATCHES=0Notes:
DEPARTURE_IDandARRIVAL_IDare airport codes.- Dates use
YYYY-MM-DD. MAX_TOTAL_PRICEis the total trip price limit in the configured currency.IGNORE_LAYOVER_TIME=1disables layover-duration filtering.IGNORE_LAYOVER_TIME=0usesMAX_LAYOVER_MINUTES.OUTBOUND_DEPART_BEFORE_HOUR=12means outbound flights must leave before noon.RETURN_ARRIVE_BY_HOUR=22means return flights must arrive before 10 PM.ALERT_REPEAT_MATCHES=0avoids repeated emails for the exact same found deal.- No-results status emails are sent every run.
This project uses SerpApi's Google Flights API. It does not use an official Google Flights API key, because Google does not provide a simple public Google Flights booking-search API for this use case.
- Create or sign in to a SerpApi account at serpapi.com.
- Open the SerpApi API key page: serpapi.com/manage-api-key.
- Copy your private API key.
- Put it in
.env:
SERPAPI_API_KEY=your_serpapi_keySerpApi documents that api_key is the private key used to authenticate requests and that it is available in your account/API key area. See SerpApi Account API and SerpApi status/error docs.
The agent sends through Gmail SMTP using:
- Host:
smtp.gmail.com - Port:
587 - Security: STARTTLS
You must use a Gmail app password. Do not use your normal Gmail login password.
- Open Google Account Security.
- Turn on 2-Step Verification.
- Open Google App Passwords.
- Create an app password for Mail or another custom app name.
- Copy the 16-character app password.
- Add it to
.env:
GMAIL_ADDRESS=your_gmail@gmail.com
GMAIL_APP_PASSWORD=your_16_character_app_password
ALERT_TO=where_to_send_alerts@example.comGoogle's help page explains that app passwords require 2-Step Verification and are intended for apps or devices that cannot use "Sign in with Google": Sign in with app passwords.
Dry run: searches flights and prints the email body, but does not send email or mark deals as emailed.
. .venv/bin/activate
python flight_watcher.py --once --dry-runReal run: searches flights and sends either a found-deal email or a no-results email.
. .venv/bin/activate
python flight_watcher.py --onceThis keeps a Python process open and checks every 30 minutes by default:
. .venv/bin/activate
python flight_watcher.pySet a different interval:
python flight_watcher.py --interval-minutes 60The repo includes a LaunchAgent installer. It creates:
~/Library/LaunchAgents/com.evelyn.flight-watcher.plist
The schedule uses StartInterval=3600, so it runs once when loaded and then once per hour while the Mac is awake and the user session is active.
Install the schedule:
chmod +x install_launch_agent.sh run_flight_watcher.sh
./install_launch_agent.shLoad/start it:
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.evelyn.flight-watcher.plist
launchctl kickstart -k gui/$(id -u)/com.evelyn.flight-watcherCheck status:
launchctl print gui/$(id -u)/com.evelyn.flight-watcherView logs:
tail -f logs/flight-watcher.log
tail -f logs/flight-watcher.err.logStop/unload it:
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.evelyn.flight-watcher.plistOlder macOS versions may also accept:
launchctl load ~/Library/LaunchAgents/com.evelyn.flight-watcher.plist
launchctl unload ~/Library/LaunchAgents/com.evelyn.flight-watcher.plistLaunchAgent jobs do not run while the Mac is fully asleep. If you want hourly checks to keep running, keep the Mac plugged in and awake.
On macOS Monterey:
- Apple menu -> System Preferences -> Battery.
- Choose Power Adapter.
- Enable "Prevent your Mac from automatically sleeping when the display is off."
- Optionally enable "Wake for network access."
Terminal option:
caffeinate -sKeep that terminal open. Stop it with Control+C.
flight_watcher.py- the flight-search and email agentrun_flight_watcher.sh- wrapper used by launchdinstall_launch_agent.sh- creates the hourly macOS LaunchAgent plistrequirements.txt- Python dependencies.env.example- safe example configuration.gitignore- excludes.env, virtualenvs, logs, caches, and state
- Never commit
.env. - Never commit SerpApi keys, Gmail app passwords, or personal passwords.
- If a key or password is accidentally published, rotate it immediately.
- The agent intentionally keeps sent-deal state in
.state/sent_flights.json, which is ignored by git.
Flight websites change frequently and direct scraping can be blocked or violate terms. This agent uses SerpApi as a structured flight-search provider instead of scraping browser pages directly.