The package tracker that reads my emailproject
Our household runs on deliveries, like most, I suspect, in the age of same-day shipping. On any given week something is inbound for the kitchen, my toddler, or the server rack in our garage, and the question “where’s the X” has historically been answered by multiple carrier apps, a search through email, and/or vibes. Package tracking is a solved problem commercially (I happily pay for Parcel, which is excellent), but a solved problem on an island: no commercial tracker can read my mail, talk to my house, or show up on the family’s wall display. Parcel is also iOS-only, so while my subscription is alive and well, my lack of iDevices leaves me with only a PWA on Android. I wanted the tracking to live where the rest of my systems live. So I built Waybill: a small Android app I wrote for myself, a relay service on the homelab, and a feed straight from the agent that already reads my email.
The first plan died on paperwork. The tracking API I designed around wanted a support-email exchange to enable USPS and a webhook exposed to the public internet, and neither friction was worth it for a personal tool. The pivot was sitting in my pocket: Parcel’s own API, included with the subscription I already pay for. It is a deliberately tiny surface (two endpoints, 20 lookups an hour, 20 package-adds a day, no webhooks), and those budgets became the design: a poller that diffs on a timer, an add-budget guard, and, since the API’s terms are personal-use, an app that will only ever be mine. Because the app had been built against a provider seam from day one, the swap touched the relay and nothing else.
The front door is the email agent I already employ. Mailroom, the agent that labels my email, now also spots tracking numbers and hands them to the relay. Deliberately, there is no AI in the extraction: tracking numbers come out of plain pattern-matching, because a hallucinated label is a cosmetic bug and a hallucinated tracking number is a lost package. Merchant emails with a real product name auto-add; ambiguous ones go to Discord, where an approve link (signed, so only my links work) admits them. The relay owns all policy, the app just renders.

Then I shipped it, and every bug that mattered for the next week was mine. A launch-day incident set the tone: sustained rate-limit errors that looked like my poller misbehaving, until some basic math proved otherwise. Home Assistant’s Parcel integration was drawing from the same API key, and two well-behaved consumers added up to one misbehaving budget. A vendor API key is a shared resource; inventory every consumer before adding a new one. Waybill superseded that integration anyway; it’s off now.
Real use found the rest. Two packages generated 14 notifications in a day, because I was pushing every scan instead of milestones. The auto-add heuristics happily admitted “Your UPS Package was delivered” as a product name, which is what happens when a token-based extractor has no concept of weekdays or verb tense. The first cure stayed deterministic: when a merchant’s email shows up later carrying the same tracking number as my generic carrier notification, with a real product name attached to it, the pipeline resubmits it and the name upgrades. The stubborn cases finally got a model, on a short leash: when the pattern-matching ladder comes up generic and the sender isn’t a carrier, a local model reads the email and proposes a display name (capped, URLs stripped). It names the package and touches nothing else; the tracking number it rides on still comes from plain pattern-matching. And when an email genuinely contains no product name, it says so instead of inventing one, which is more restraint than I expected to get for free.
Android later contributed a deeper issue: a push message that carries a notification payload gets rendered by the system when the app is backgrounded, skipping my app’s logic entirely, including the deduplication. The fix is data-only messages, so my code always draws the notification. And dedupe itself needed a ledger (each milestone fires at most once per package) rather than a diff, because carrier event feeds churn enough to re-trigger a comparison forever.
My favorite bug wasn’t even in Waybill. Mailroom’s message feed was filtered to the inbox, and a long-forgotten Gmail filter archives certain noisy merchant mail on arrival, meaning exactly the tracking emails the bridge existed for were invisible to it. Lesson learned: a downstream consumer inherits every blind spot of its feed.
My tracker now offers the customizability the carrier apps never could: mail arrives, the package appears, the phone speaks only at milestones, and nothing gets lost between systems.