Voice: A 0→1 Community Product, Built and Shipped Solo
TL;DR
- What it is: A community engagement product where residents can find their elected representatives, sign petitions, join local groups, discuss neighborhood issues, and post about community events — mapped to their actual geography.
- What I did: Owned the product end-to-end. User research, problem discovery, roadmap, feature scoping, prioritization, design, build, deploy, and iOS App Store submission — solo product manager and solo engineer.
- Where it is now: Live at voiceapp.live, on TestFlight, and submitted to the App Store as v2.0.0.
The user problem
Community engagement is fragmented across a dozen different surfaces: reps have websites, petitions live on one platform, local groups are on another, event flyers are on a third, ballot info is on a state site that changes every cycle. The residents most affected by local decisions have the hardest time finding the tools to participate. Interviews with residents surfaced the same pattern: they wanted one mobile surface, geographically-grounded, that took them from "who represents me?" to "signed a petition" to "RSVP'd to a community event" in a single flow.
The stack
Mobile (Flutter / Dart): Native iOS app with a component library I designed from scratch, provider-based state management, optimistic UI everywhere, and offline-tolerant reads. Shipped to TestFlight and submitted to the App Store as v2.0.0.
Backend (Flask + Flask-SocketIO): REST API with JWT auth, session management, and structured error handling. Background workers for long jobs (Celery on Redis) — feed hydration, notification fan-out, article summarization. Postgres 15 with the PostGIS extension for geospatial queries.
Geospatial layer: TIGER/Line shapefiles for state and federal district boundaries loaded into PostGIS. A single point-in-polygon query resolves a user's coordinates to their federal, state, and local reps. Precomputed indexes keep resolution under 15 ms at the API layer.
Infrastructure: Cloud-hosted VM behind Nginx with a Let's Encrypt TLS chain and auto-renewal. Docker-composed backend with health checks. Automated nightly Postgres backups with 7-day retention. Blue/green-style deploys through a single scripted playbook so I can roll back in one command.
Product decisions I'm proud of
Optimistic UI as a product bet, not a UX polish. Save/unsave, join/leave, like/dislike, vote counts, comment counts — all local-first with rollback on API failure. This wasn't a UI decision; it was a scope decision. It let me defer a whole realtime workstream (Socket.IO + eventlet was hitting DNS resolution issues inside Docker's bridge network) with essentially zero user-visible cost at beta scale, and spend that week on features residents actually asked for.
PostGIS for the geospatial core, not a hosted geo-service. Buy-vs-build call. A single Postgres extension replaces what would otherwise be a third-party dependency, a network hop, and a recurring monthly bill. TIGER/Line data is free and authoritative. Point-in-polygon queries are indexed and fast. Cheaper unit economics, no vendor lock-in.
Testability-first bug triage as a release-management call. For the v2 release I had 29 tracked bugs across two repos and a 7-day App Store window. Instead of severity-first (the default project-management instinct), I prioritized by testability: bugs that blocked other bugs' reproduction went first, then bugs visible in 30 seconds of smoke testing, then multi-account state bugs last. All 29 shipped. Zero crashes on TestFlight build 2. This is exactly the kind of prioritization call a PM or TPM makes every release.
What I killed
Realtime. Fighting eventlet + Redis DNS resolution inside a Docker container was 2–4 hours of uncertain debug with no guarantee the week would end with working WebSockets. I killed it for v2 and wrote a deferral doc with a specific fire-condition: realtime re-opens when a rolling 7-day median shows more than 3 concurrent actors per entity per minute. Below that, optimistic UI is user-indistinguishable from true realtime.
A "post everywhere" content model. Early on I considered letting posts belong to multiple groups, districts, and topics simultaneously. It would have doubled the write complexity and made the feed algorithm quadratic in tag count. Killed for v2 in favor of single-scope posting.
Where you can see it
Website: voiceapp.live
iOS: Submitted for App Store review. TestFlight available on request.
Writing: The build is documented on Medium, including detailed pieces on the ZIP-code-to-point-in-polygon architecture, the API/app release-cadence problem, and the pre-ship bug-triage rules.
The one KPI I'd watch
Once users are on the app in numbers, the north-star metric is community actions per active user per week — signed petitions, contacted reps, RSVPs, saved events, comments left. Below 1, Voice is a passive feed and I've built the wrong product. Above 3, it's a habit and the value proposition is real. Everything else — DAU, session length, retention — is a proxy for that number.
What would tell me I was wrong
If community-actions-per-user plateaus below 1 for 4 weeks while retention still looks fine, that's the failure signal: users are showing up but not doing anything. In that case Voice would need a full product re-think, not a growth push.
What I'd do differently
I'd build the moderation and safety surface earlier in the roadmap. Community content of any kind invites strong opinions and I underweighted how important report/mute/block flows would feel at v1. The v2 patch queue has all of them, but I could have shipped day-one with a first-class safety layer instead of a bolted-on one. This is a product-scoping mistake I'd catch earlier next time in discovery interviews.
Related deep-dives on Voice: 29 Bugs, One Week — the pre-ship triage playbook. The Optimistic UI Bet — why I deferred realtime and how I'll know I was wrong.
Read next: NewsScope / VERA — the research side of the same skillset: first-author paper on schema-grounded claim extraction.