Best Price Flights is a live cloud product that monitors flight prices several times a day — querying Kiwi, Google Flights, and Skyscanner directly — and alerts users the moment fares drop. Rather than relying on occasional manual checks, it works on an operator-controlled poll schedule so people can catch a usable price while it is still bookable.
Creating a watch is straightforward: a structured form (one-way or round-trip, exact or flexible dates, adults/children/infants) or plain English in the Flight Assistant — “two adults from Bristol to Rome 23 May, flexible departure, roundtrip with return in 7 days, direct flights only”. The assistant extracts airports, dates, and passengers and starts tracking. Alerts arrive as Android push, browser Web Push, and optional email.
The stack is Microsoft .NET 10 on the backend and native clients, with an Angular 21 web portal at bestpriceflights.info. Google sign-in is shared. Secrets stay on Azure Functions — devices only hold a public client ID.
Plenty of products offer a price alert. Best Price Flights is built as a small SaaS around a different job: keep several independent providers honest, on your schedule, and get the user to a booking link in time.
• Multi-provider aggregation — Kiwi, Google Flights, and Skyscanner are queried in one watch, each through the current v2 backends.
• Native Android push plus browser Web Push plus email — the web portal is a first-class client, not a brochure.
• Optional Android notification agent (BPF Alerts) — install after sign-in if you manage searches on the web and only want alerts on the phone.
• Configurable polling — the operator owns the schedule and can run manual or per-provider polls from admin.
• Server-side Flight Assistant — Azure OpenAI behind POST /api/chat. Web and MAUI use the same API; no model key on the device.
• Flexible dates (±1 day) and family passengers.
• Full price history per search and provider, not a single “price changed” flag.
• Stripe Test subscriptions (Free / Basic / Pro) with live Cosmos tier documents — quotas and the flexible-dates flag can change without a redeploy.
The GitHub solution is larger than the original four-project snapshot:
• BestPriceFlightsWeb — Angular 21 marketing site and signed-in portal on Azure Static Web Apps (bestpriceflights.info).
• BestPriceFlightsClient — .NET MAUI Blazor Hybrid full app (Android first; iOS still Release 2). Same Google account and API as the portal.
• BestPriceFlights.Agent — slim MAUI Android app (BPF Alerts) for push only. OAuth through Functions, same Desktop client as the full app.
• BestPriceFlights.Admin — Blazor Server operator console (users, devices, polls, tiers, Stripe purge). All /api/ops/* calls require a Functions key.
• BestPriceFlights.Functions — Azure Functions v4 isolated worker on .NET 10: HTTP API, timer polls, Stripe, chat, Web Push, APK SAS, Google token exchange.
• BestPriceFlights.Shared — models, catalog, chat prompt, search-draft rules. Covered by dedicated test projects (kept on purpose).
A user signs in with Google, creates one or more watches, and leaves them running. The backend polls allowed providers, writes history, and raises alerts when a fare is cheaper than the stored best.
Capabilities now in production:
• One-way and round-trip tracking, exact or flexible dates
• Adults, children, and infants (provider quirks handled server-side)
• Quota check before a new search is created (web and MAUI)
• Alternative-flight exploration on JWT user routes (/api/searches/{id}/alt/…)
• Booking-link resolve for deferred Google/Sky tokens
• Alert fan-out: FCM (notifyByPush) → Web Push (notifyByWebPush) → email (notifyByEmail)
• Sideload APKs from private Azure Blob via short-lived SAS — public full-app download; agent only after portal sign-in
• Admin: schedule, manual poll, per-provider poll, device test/re-register, user delete + Stripe subscription cancel
Cosmos DB is the system of record. Containers include UserProfiles, SearchRequests, PriceRecords, PriceHistory, PollSettings, AlternativeFlights, TierDefinitions, poll-run analytics, and BillingEvents.
TierDefinitions stay live documents (Free / Basic / Pro): max searches, daily polls, allow-flexible-dates, price, and description. Admin edits take effect on the next API call. Startup seeding creates missing containers and default tiers without overwriting operator changes.
Profiles also store notification flags, registered FCM devices (full app and agent), and Web Push subscriptions.
Paid upgrades go through Stripe Checkout (web return URLs on bestpriceflights.info; MAUI uses an info.bestpriceflights.app://billing deep link). The Functions webhook updates the Cosmos profile. A “purge billing” admin path cancels Stripe subscriptions before a user delete — and that route, like every other ops/* endpoint, requires a function key.
Quota still applies when saving, reactivating, listing, and during the scheduled poll (oldest excess watches pause). Admin diagnostic polls can bypass the user’s tier so operators can test any provider.
A timer function (plus admin “poll all / poll one / poll one provider”) loads active searches, enforces quotas, archives expired date windows, queries each logical provider, writes history, and raises alerts.
Google and Sky booking URLs are often deferred: the client asks Functions to resolve a token when the user taps “Get booking link”, then the URL is persisted for the next click.
Android delivery goes through Azure Notification Hubs and FCM. The agent and full app register separately. Browser users enable Web Push from the portal (VAPID keys on Functions; service worker on the Static Web App origin). Email still uses SMTP and the profile toggle.
Alert copy includes the provider name so a drop is attributable, not anonymous.
Google ID tokens are validated in Functions middleware (OIDC discovery). MAUI and the agent exchange the auth code on the server (POST /api/auth/google-token, client: "maui") so the Desktop client secret never ships in an APK. The Angular portal uses the Web OAuth client. Refresh stays on Functions as well.
Web portal (Angular)
Marketing pages, Google sign-in, search list, new search (flexible dates and passengers), results and price history, alternatives, Flight Assistant, profile and Stripe, Web Push, and Android agent download after sign-in. The marketing “Download Android app” button hits Functions and redirects to a blob SAS URL.
Full Android app (MAUI Blazor Hybrid)
Portal-parity: same watches, chat, billing return, FCM. Package info.bestpriceflights.app. Sideload from the site; Play Store is not the current distribution path.
Android agent (BPF Alerts)
Sign in, register for push, group alerts per search. No second search UI. Download is gated on a signed-in portal request so the APK is not a public marketing file.
iPhone
Native app is still Release 2. Until then: web portal and Safari Home Screen / browser notifications.
Blazor Server, Interactive Server Components, talking to Cosmos and to keyed Functions ops:
• Tier settings — quotas, flexible-dates flag, pricing text
• Poll schedule and manual / per-provider polls with diagnostics
• Users, searches, price history, booking-link resolve
• Devices — list, test push, re-register, delete Notification Hub installations
• Danger zone — cancel Stripe then delete user data
Backend — Azure Functions (.NET 10 isolated)
• Microsoft.Azure.Functions.Worker + Http AspNetCore + Timer + Cosmos DB extensions
• Microsoft.Azure.NotificationHubs / FirebaseAdmin — Android push
• Lib.Net.Http.WebPush — browser push
• Stripe.net — Checkout, portal, webhooks, admin purge
• Azure.Storage.Blobs — private APK container and SAS
• Azure.AI.OpenAI / Microsoft.Extensions.AI — Flight Assistant
• Microsoft.IdentityModel.Protocols.OpenIdConnect + JWT — Google tokens
Web — Angular 21
Static Web Apps (West Europe), prerendered marketing routes, SPA portal. Public APKs are not in the SWA artifact.
MAUI
Microsoft.Maui.Controls, BlazorWebView, Xamarin.Firebase.Messaging, embedded appsettings with public Client ID only.
Admin
Microsoft.Azure.Cosmos, HttpClient to Functions with x-functions-key.
Platform — .NET 10 + Angular 21
End-user web — Azure Static Web Apps, bestpriceflights.info
Native — MAUI full app + MAUI agent (Android)
Admin — Blazor Server
API — Azure Functions v4 isolated
Data — Azure Cosmos DB
Auth — Google OAuth (Web + Desktop) with server token exchange
Payments — Stripe (Test)
Alerts — Notification Hubs / FCM, Web Push, email
APKs — private blob + SAS
Monitoring — Application Insights
Best Price Flights is a live, cloud-native fare-watch product: one Google identity across an Angular portal, a MAUI Android app, and a notification agent — backed by Azure Functions, Cosmos DB, and Stripe — built to catch a multi-provider drop in time to book.
Close