Checks one route against your price target each day and emails you once, when the fare first drops below it.

UsesGmail

Email me when a flight fare drops below my target

Once a day, your fare provider's search endpoint is asked what one route costs on the date you want to fly, and the cheapest offer that comes back is compared with the price you would actually book at.

You are emailed on the change, not the state. The first day the fare sits at or below your target you get one alert with the price, plus the airline and a booking link where the provider supplies them. While it stays there you hear nothing more, and when it climbs back above your target you get a single note saying the watch is armed again. It knows what it has already told you by searching your own mailbox for the newest alert about this route, so nothing is repeated. If that search fails, nothing is sent rather than risk a duplicate, and once your departure date has passed the checks stop on their own.

You need a fare search endpoint and key from a travel data provider, answering with a list of offers that each carry a price.

ScheduleRun every day.Run every day.
ValuesSet fareApiUrl to blank.Set fareApiUrl to blank.
ValuesSet fareApiToken to blank.Set fareApiToken to blank.
ValuesSet originAirport to blank.Set originAirport to blank.
ValuesSet destinationAirport to blank.Set destinationAirport to blank.
ValuesSet departureDate to blank.Set departureDate to blank.
ValuesSet returnDate to blank.Set returnDate to blank.
ValuesSet targetPrice to 0.Set targetPrice to 0.
ValuesSet currencyCode to USD.Set currencyCode to USD.
ValuesSet alertEmailAddress to blank.Set alertEmailAddress to blank.
ValuesSet priceTarget to 0.Set priceTarget to 0.
Decisions & LoopsIf a step below fails, save the error as targetPriceErrorIf a step below fails, save the error as targetPriceError
Try
MathRound targetPrice to 2 decimal places and save it as priceTarget.Round targetPrice to 2 decimal places and save it as priceTarget.
On error
Decisions & LoopsStop and report a problem: Put the most you are willing to pay into the 'targetPrice' step as a plain number - 450, not $450 or 1,450 - then run it again.Stop and report a problem: Put the most you are willing to pay into the 'targetPrice' step as a plain number - 450, not $450 or 1,450 - then run it again.
Decisions & LoopsIf priceTarget is less than or equal to 0If priceTarget is less than or equal to 0
Yes
Decisions & LoopsStop and report a problem: The 'targetPrice' step is still 0, so no fare could ever count as a drop. Set it to the most you are willing to pay, then run it again.Stop and report a problem: The 'targetPrice' step is still 0, so no fare could ever count as a drop. Set it to the most you are willing to pay, then run it again.
No
No steps
ValuesSet daysUntilDeparture to 0.Set daysUntilDeparture to 0.
Decisions & LoopsIf a step below fails, save the error as departureDateErrorIf a step below fails, save the error as departureDateError
Try
Dates & TimesCount the days from today's date to departureDate and save it as daysUntilDeparture.Count the days from today's date to departureDate and save it as daysUntilDeparture.
On error
Decisions & LoopsStop and report a problem: The 'departureDate' step is not a date this workflow can read. Write it as 2026-11-14, then run it again.Stop and report a problem: The 'departureDate' step is not a date this workflow can read. Write it as 2026-11-14, then run it again.
Decisions & LoopsIf daysUntilDeparture is less than 0If daysUntilDeparture is less than 0
Yes
Decisions & LoopsFinish the run: The date in the 'departureDate' step has already passed, so there is nothing left to watch on this route. Set a new date to start watching again.Finish the run: The date in the 'departureDate' step has already passed, so there is nothing left to watch on this route. Set a new date to start watching again.
No
No steps
ValuesSet routeLabel to originAirport to destinationAirport.Set routeLabel to originAirport to destinationAirport.
ValuesSet watchTag to Fare watch originAirport-destinationAirport departureDate.Set watchTag to Fare watch originAirport-destinationAirport departureDate.
ValuesSet underTargetMarker to fare is under your target.Set underTargetMarker to fare is under your target.
ValuesSet aboveTargetMarker to fare is back above your target.Set aboveTargetMarker to fare is back above your target.
ValuesSet fareSearch to a record of origin, destination, departure date, and currency.Set fareSearch to a record of origin, destination, departure date, and currency.
ValuesSet returnLine to blank.Set returnLine to blank.
Decisions & LoopsIf returnDate isn't blankIf returnDate isn't blank
Yes
ValuesChange fareSearch to a record of origin, destination, departure date, and 2 more.Change fareSearch to a record of origin, destination, departure date, and 2 more.
ValuesChange returnLine to Returning: returnDateChange returnLine to Returning: returnDate .
No
No steps
ValuesSet fareResponse to an empty record.Set fareResponse to an empty record.
Decisions & LoopsIf a step below fails, save the error as fareApiErrorIf a step below fails, save the error as fareApiError
Try
HTTPFetch fareApiUrl and save it as fareResponse.Fetch fareApiUrl and save it as fareResponse.
On error
Decisions & LoopsStop and report a problem: The fare search did not answer, so no fare was checked. Check the address in the 'fareApiUrl' step and the key in the 'fareApiToken' step. The endpoint said: fareApiError as text.Stop and report a problem: The fare search did not answer, so no fare was checked. Check the address in the 'fareApiUrl' step and the key in the 'fareApiToken' step. The endpoint said: fareApiError as text.
ValuesSet offers to fareResponse.body.offers.Set offers to fareResponse.body.offers.
ValuesSet offerCount to 0.Set offerCount to 0.
Decisions & LoopsIf a step below fails, save the error as fareShapeErrorIf a step below fails, save the error as fareShapeError
Try
ListsCount the items in offers and save it as offerCount.Count the items in offers and save it as offerCount.
On error
Decisions & LoopsStop and report a problem: The fare search answered in a shape this workflow cannot read. The endpoint in the 'fareApiUrl' step has to answer with a JSON object holding a list called 'offers', and each offer needs a number in 'price'.Stop and report a problem: The fare search answered in a shape this workflow cannot read. The endpoint in the 'fareApiUrl' step has to answer with a JSON object holding a list called 'offers', and each offer needs a number in 'price'.
Decisions & LoopsIf offerCount is 0If offerCount is 0
Yes
Decisions & LoopsFinish the run: No fares came back for routeLabel on departureDate, so there was nothing to compare with your target.Finish the run: No fares came back for routeLabel on departureDate, so there was nothing to compare with your target.
No
No steps
ValuesSet bestPrice to 0.Set bestPrice to 0.
ValuesSet bestOffer to an empty record.Set bestOffer to an empty record.
ValuesSet pricedOffers to 0.Set pricedOffers to 0.
ValuesSet skippedOffers to 0.Set skippedOffers to 0.
Decisions & LoopsFor each offer in offers:For each offer in offers:
ValuesSet offerPrice to 0.Set offerPrice to 0.
ValuesSet offerHasPrice to false.Set offerHasPrice to false.
Decisions & LoopsIf a step below fails, save the error as offerPriceErrorIf a step below fails, save the error as offerPriceError
Try
MathRound offer.price to 2 decimal places and save it as offerPrice.Round offer.price to 2 decimal places and save it as offerPrice.
ValuesChange offerHasPrice to true.Change offerHasPrice to true.
On error
ValuesAdd 1 to skippedOffers.Add 1 to skippedOffers.
Decisions & LoopsIf offerHasPrice is yesIf offerHasPrice is yes
Yes
Decisions & LoopsIf pricedOffers is 0 or offerPrice is less than bestPriceIf pricedOffers is 0 or offerPrice is less than bestPrice
Yes
ValuesChange bestPrice to offerPrice.Change bestPrice to offerPrice.
ValuesChange bestOffer to offer.Change bestOffer to offer.
No
No steps
ValuesAdd 1 to pricedOffers.Add 1 to pricedOffers.
No
No steps
Decisions & LoopsIf skippedOffers is greater than 0If skippedOffers is greater than 0
Yes
ValuesShow in the output: Skipped skippedOffers offer(s) that carried no usable price. Each offer needs a number in 'price'.Show in the output: Skipped skippedOffers offer(s) that carried no usable price. Each offer needs a number in 'price'.
No
No steps
Decisions & LoopsIf pricedOffers is 0If pricedOffers is 0
Yes
Decisions & LoopsStop and report a problem: The fare search returned offerCount offer(s) but none of them carried a number in 'price', so no fare could be compared with your target.Stop and report a problem: The fare search returned offerCount offer(s) but none of them carried a number in 'price', so no fare could be compared with your target.
No
No steps
ValuesSet airlineLine to blank.Set airlineLine to blank.
Decisions & LoopsIf a step below fails, save the error as airlineErrorIf a step below fails, save the error as airlineError
Try
TextTrim bestOffer.airline and save it as airlineText.Trim bestOffer.airline and save it as airlineText.
Decisions & LoopsIf airlineText isn't emptyIf airlineText isn't empty
Yes
ValuesChange airlineLine to Airline: airlineTextChange airlineLine to Airline: airlineText .
No
No steps
On error
ValuesChange airlineLine to blank.Change airlineLine to blank.
ValuesSet bookingLine to blank.Set bookingLine to blank.
Decisions & LoopsIf a step below fails, save the error as bookingLinkErrorIf a step below fails, save the error as bookingLinkError
Try
TextTrim bestOffer.link and save it as linkText.Trim bestOffer.link and save it as linkText.
Decisions & LoopsIf linkText isn't emptyIf linkText isn't empty
Yes
ValuesChange bookingLine to Book here: linkTextChange bookingLine to Book here: linkText .
No
No steps
On error
ValuesChange bookingLine to blank.Change bookingLine to blank.
GmailGmailList emails matching 'subject:@watchTag'. Save it as priorAlerts.List emails matching 'subject:@watchTag'. Save it as priorAlerts.
ValuesSet priorMessages to priorAlerts.emails.Set priorMessages to priorAlerts.emails.
ValuesSet priorAlertCount to 0.Set priorAlertCount to 0.
Decisions & LoopsIf a step below fails, save the error as alertHistoryErrorIf a step below fails, save the error as alertHistoryError
Try
ListsCount the items in priorMessages and save it as priorAlertCount.Count the items in priorMessages and save it as priorAlertCount.
On error
Decisions & LoopsStop and report a problem: Could not read your mailbox to see what this watch has already reported, so nothing was sent rather than risk repeating an alert. Reconnect Gmail and run it again.Stop and report a problem: Could not read your mailbox to see what this watch has already reported, so nothing was sent rather than risk repeating an alert. Reconnect Gmail and run it again.
ValuesSet newestAlertSubject to blank.Set newestAlertSubject to blank.
Decisions & LoopsIf priorAlertCount is greater than 0If priorAlertCount is greater than 0
Yes
Decisions & LoopsIf a step below fails, save the error as alertSubjectErrorIf a step below fails, save the error as alertSubjectError
Try
TextTrim the first item of priorMessages.subject and save it as newestAlertSubject.Trim the first item of priorMessages.subject and save it as newestAlertSubject.
On error
ValuesChange newestAlertSubject to blank.Change newestAlertSubject to blank.
No
No steps
TextCheck whether newestAlertSubject contains underTargetMarker and save it as alreadyReported.Check whether newestAlertSubject contains underTargetMarker and save it as alreadyReported.
ValuesSet fareIsUnderTarget to bestPrice is less than or equal to priceTarget.Set fareIsUnderTarget to bestPrice is less than or equal to priceTarget.
Decisions & LoopsIf fareIsUnderTarget is yes and alreadyReported is noIf fareIsUnderTarget is yes and alreadyReported is no
Yes
GmailGmailSend an email to alertEmailAddress.Send an email to alertEmailAddress. Subject: watchTag - underTargetMarkerA fare on this route has dropped below your target. Route: routeLabel Leaving: departureDate returnLineCheapest fare found: currencyCode bestPrice formatted as a number with thousands separators with 2 decimal places Your target: currencyCode priceTarget formatted as a n...
Else if @alreadyReported is yes and @fareIsUnderTarget is no
GmailGmailSend an email to alertEmailAddress.Send an email to alertEmailAddress. Subject: watchTag - aboveTargetMarkerThe fare you were told about has gone back above your target. Route: routeLabel Leaving: departureDate returnLineCheapest fare now: currencyCode bestPrice formatted as a number with thousands separators with 2 decimal places Your target: currencyCode priceTarget formatte...
Otherwise
ValuesShow in the output: Cheapest fare for routeLabel on departureDate is currencyCode bestPrice formatted as a number with thousands separators with 2 decimal places against a target of currencyCode priceTarget formatted as a number with thousands separators with 2 decimal places. Nothing has changed since the last email, so none was sent.Show in the output: Cheapest fare for routeLabel on departureDate is currencyCode bestPrice formatted as a number with thousands separators with 2 decimal places against a target of currencyCode priceTarget formatted as a number with thousands separators with 2 decimal places. Nothing has changed since the last email, so none was sent.