Work / Project 02
Python · Flask · Celery · Docker · PostgreSQL · Azure DevOps
A self-hosted service for sending anonymous audio messages with auto-expiry — without relying on WhatsApp, Telegram, or any account. Deployed to a DigitalOcean VM via a full Azure DevOps CI/CD pipeline.
Every major messaging platform ties a voice message to an identity. WhatsApp links it to your phone number. Telegram to your account. Even "anonymous" apps route through infrastructure that logs sender metadata by design.
The idea was simple: what if you could record and send a voice message to someone, and they genuinely had no way of knowing who sent it? No account, no phone number, no persistent session — just a message ID the recipient can use to retrieve and listen once, then gone.
AnonVoice started as a service prototype — an exercise in building a complete, production-grade stack around a minimal idea. The privacy angle was real, but so was the engineering goal: ship something with a proper pipeline, not just a local demo.
No app to install. The sender records directly in the browser, gets a message ID, and shares only that code. The recipient types it in to retrieve the audio — nothing else crosses the wire.
Auto-expiry built in from day one: 1, 5, or 10 minutes. Celery handles the scheduled cleanup in the background so no message lingers longer than its sender intended.
Senders can opt-in to a notification when their message is played back. Adds a lightweight feedback loop without breaking anonymity — the recipient still knows nothing about the sender.
No login form, no user table. The admin panel lives at a secret UUID path. Rudimentary by design — the goal was operational visibility without adding auth infrastructure to a prototype.
Every push to the main branch triggers the Azure DevOps pipeline automatically. The pipeline runs checks first — unit tests and configuration validation — and only proceeds to deployment if all gates pass. No manual SSH, no hand-rolled deploy scripts.
The target is a DigitalOcean VM running Docker Compose: the Flask app, Celery worker, PostgreSQL, and Apache2 as the reverse proxy all in containers. The pipeline pulls the new image, brings the stack down cleanly, and brings it back up.
Manual deploys accumulate risk silently. Wiring Azure DevOps early — even before the app was feature-complete — meant every subsequent change was safer and faster to ship.
Dropping in a task queue just for auto-expiry feels like overkill until you consider the alternative: a cron job that races the web process. Celery made the expiry logic testable and observable.
Running Flask, Celery, PostgreSQL, and Apache2 as separate containers on one machine kept the services isolated without introducing Kubernetes overhead. Simple to reason about, simple to restart.
The UUID admin route is intentionally minimal. For a prototype with no user data at risk, shipping a full auth layer would have cost more than it protected. Knowing where to draw that line is part of the job.