Inspiration
Food banks rarely turn down food because they don't want it. They turn it down because nobody could tell them about it in time.
A tray of prepared meals coming off a hot line has a window measured in hours. In that window somebody has to notice the surplus, find a nonprofit whose cold storage and schedule actually fit, find a driver, and make sure the handoff is safe and documented. Today most of that happens over phone calls and group texts. The coordination is the bottleneck — not generosity, and not demand.
We wanted to see whether that whole chain could be compressed into one accountable relay: post → match → deliver, with a record at every step.
What it does
Rescue Relay is a coordinator-led dispatch network for urgent food rescue. It connects four roles around a single rescue record:
- Food donors post surplus with the details that matter — weight, category, pickup address, the deadline it has to move by, storage conditions, and allergens.
- Verified nonprofit recipients are ranked by an explainable Rescue Score and can accept, decline, or flag a match as unsafe.
- Volunteer drivers claim a run from a live map with pickup and drop-off pinned, and confirm pickup and delivery as they happen.
- Coordinators oversee verification, membership requests, and exceptions.
When the food lands, the rescue closes with a receipt: delivered weight, estimated meals, and the real number of minutes from pickup to delivery.
How we built it
The app is a Tan Stack Start application — React 19, file-based routing, Vite, Tailwind CSS v4, and Radix primitives — backed by Supabase for Postgres, auth, row-level security, and Realtime. Schema and migrations are managed with Drizzle. The whole thing prerenders to static files and deploys to GitHub Pages through GitHub Actions, with a SPA fallback so deep links survive a refresh.
The matching is deliberately not a black box. Every eligible recipient gets a score built from five readable components:
$$ S = \text{round}\big(0.35\,U + 0.25\,F + 0.20\,D + 0.10\,N + 0.10\,V\big) $$
where urgency \( U = \max(0, \min(100,\, 100 - m/4)) \) for \( m \) minutes remaining, \( D = \max(0,\, 100 - 12d) \) for distance \( d \) in miles, \( N \) is households served, \( V \) reflects whether one volunteer can realistically carry the load, and fit \( F \) is all-or-nothing — a recipient without the right cold chain, without capacity, or that doesn't accept the category scores zero and is never offered the run.
That last part is the design opinion worth defending: speed is only useful if the match is safe. Alongside the number, the UI prints the reasoning in plain words — "0.8 miles away · storage ready · serves 180 households · accepts this food" — so a coordinator can disagree with it.
Meal estimates are kept honest too. We report \( \text{meals} = \text{pounds} / 1.2 \) and label it an estimate everywhere it appears, rather than presenting it as servings delivered.
Challenges we ran into
No session, no writes. The hardest problem wasn't the matching — it was that the deployed site has no server and the auth providers issue no browser session, so every database write was refused by RLS. State fell back to localStorage, which is per-browser, which meant a donation posted on one screen was invisible on every other. For a demo where a judge opens two tabs to play two roles, that's fatal.
We solved it with a Supabase Realtime broadcast channel. Broadcast carries ephemeral messages between subscribers and never touches the database, so it needs no session and no policy change. Each screen publishes its whole workspace snapshot rather than a diff — a few kilobytes, and a snapshot can't drift out of step the way a missed increment can. It is explicitly a demo mechanism, not a backend, and the UI says so in a banner rather than letting it look like persistence.
Keyless by default. We didn't want the project to be undemoable without a pile of API keys, so geocoding falls back through keyless providers and nearby food banks come from OpenStreetMap's Overpass API. Google Maps is used for the map when a browser key is present.
Our own UI lied to us. Late on, a QA pass caught something uncomfortable: the AI broadcast layer was toasting "Successfully published across 5 channels via real MCP servers!" when the Supabase Edge Functions behind it were returning 404 and nothing had been published at all. The failure path quietly returned success: true and the UI took it at face value. There was even a test asserting the function "returns 100% success," which encoded the bug.
We fixed it so the interface reports what actually happened — every simulated broadcast now carries a Simulated badge and says the MCP server wasn't connected — and rewrote the test to assert the honest contract.
What we learned
The most valuable thing we built wasn't a feature, it was a constraint: never show a success state you haven't earned. A demo that fakes a result is worse than one that admits a gap, because the fake version teaches you nothing about what's left to build. The same instinct shaped the rest of the product — the Rescue Score shows its reasoning, meal counts are labelled estimates, and the demo banner tells you plainly that nothing is being saved to the database.
We also learned how much of "AI for good" is really logistics. The interesting problem in food rescue isn't prediction — it's getting five constraints (time, distance, storage, capacity, category) to agree with each other fast enough to matter, and leaving a record that a food-safety auditor would accept.
What's next
Deploying the MCP edge functions so the automation layer can graduate from simulated to live; real organization verification; and pushing scored matches to recipients rather than waiting for them to look.
Rescue Relay supports an organization's food-safety policy. It does not replace it.
Built With
- deno
- drizzle-orm
- github
- github-actions
- google-maps
- mistral-ai
- openstreetmap
- playwright
- postgresql
- radix-ui
- react
- row-level-security
- shadcn-ui
- supabase
- supabase-auth
- supabase-edge-functions
- supabase-realtime
- tailwindcss
- tanstack-query
- tanstack-router
- tanstack-start
- typescript
- vite
- vitest


Log in or sign up for Devpost to join the conversation.