Case Study
Spark Match: what to build when the budget is five dollars
A challenge to turn $5 into measurable real-world impact. Donated, $5 is $5 of impact exactly once. So I spent it on hosting instead and built a tool that removes the thing actually stopping people from volunteering — not money, but not knowing what to do, who to contact, or what to say.
Role
Solo Developer
Timeline
Aug 2026
Context
DoGoodie $5 Hack
Running cost
$0 / month
The problem
The brief was a constraint disguised as a budget: turn $5 into measurable impact. The obvious move is to donate it. That's real, and it's also finished the moment it happens.
The reframe I settled on was that money usually isn't the blocker for volunteering — decision friction is. People are willing. What stops them is a blank page: they don't know what needs doing, who to approach, or how to open the conversation without feeling awkward. Every one of those is a research task, and research tasks are where good intentions go to die.
So the $5 went into infrastructure rather than a one-time gift — a free, permanent tool that keeps working long after the money is spent. The measurable-impact bar then applies to the tool, not the donation, which is a much harder standard to meet.
Three questions, one answer
The interface is a chat thread with tappable chips instead of a form, because a form implies work and a conversation implies being helped. It asks only three things — your skill, your available time, your cause — then commits to a single recommendation.
Step 1
Your skill
Writing, design, coding, organizing people, or simply time. Five honest options rather than a taxonomy.
Step 2
Your time
15 minutes, about an hour, or 3+ hours — phrased as what you've actually got this week, not what you wish you had.
Step 3
Your cause
Animals, kids, environment, homelessness, mental health — or "surprise me", for people who want to help but don't have a preference.
The output is deliberately singular: one specific action, plus a ready-to-send outreach message you can copy and paste. Returning a list would have recreated the exact problem the tool exists to solve — another set of options to evaluate. Handing over the words to send is the part I think matters most, because "what do I even say?" is where most people stall after they've already decided to help.
The hard part was writing, not code
The matching logic is a lookup: 5 skills × 5 causes gives 25 cells, and each cell holds three time-scaled variants plus an outreach message. That's 100 hand-written pieces of content, and writing them was most of the project.
The rule I held myself to was that no cell could contain generic filler like "reach out to a local charity." Every entry names a real platform, a real kind of organization, and a concrete first move — writing a bio for a specific adoptable animal on a shelter's Petfinder listing, or drafting project descriptions for teachers at a Title I school.
Several also carry the reason the task matters, because "shelters are overwhelmed and bare-bones bios get far fewer views" is what converts a chore into something worth doing.
I chose a static lookup table over calling a language model, and I'd make that call again. The table is reviewable, costs nothing to run, can't hallucinate an organization that doesn't exist, and returns instantly. For a tool whose entire value is trustworthy specificity, generation would have added risk in exchange for variety nobody asked for.
Measuring something you can't observe
The brief demanded measurable impact, which is awkward when the impact happens off-platform. I can see that someone got matched; I cannot see whether they actually emailed the shelter.
The compromise is a final call to action — "I did it → tell Spark" — that opens a tracking form pre-filled from the session with the skill, time and cause already selected. Self-reporting is imperfect and I don't pretend otherwise, but pre-filling removes nearly all the effort from reporting, and what comes back is structured data rather than anecdotes.
The stack reflects the same constraint.
React 19 and TypeScript on Cloudflare Pages, hand-written CSS tokens with a
prefers-color-scheme dark mode, and Google Forms
as the entire analytics backend. Everything runs at $0/month, because a tool funded with $5
has to survive without anyone paying to keep it alive.
What I'd do differently
Distribution should have started before the build finished
This is the honest one. The measurement pipeline is instrumented and running, the tool works, and it's live — but building came first and getting it in front of people came second. For a project whose success metric is real-world actions taken, those two should have overlapped from the start. A matcher nobody has opened has produced exactly zero impact, no matter how good the content is.
Design the measurement into the flow, not onto the end
The completion form is bolted to the last screen, which means it only ever hears from people who finish in one sitting. Someone who gets matched, does the deed three days later, and never returns is invisible — and that's probably the most common successful path. A follow-up nudge would likely capture more real impact than anything on the results screen.
Five skills by five causes is a maintenance cliff
The grid is quadratic. Adding a sixth cause means writing 18 new pieces of content by hand, all held to the specificity bar. It was the right call for launch, but the structure makes growth expensive — a sparser model with strong fallbacks would scale better if this ever grew beyond its current shape.
Want something like this built?
I build products end to end — and I care as much about framing the right problem as about shipping the solution.