I built an anonymous, public, message board “Overheard At” that can be used in any conference. It encourages sharing of the funny, controversial or just plain weird things people say in hallways.
First, some background: This week I attended GFF 2025. In case you haven’t heard of it - GFF is India’s largest fintech conference. It takes place once a year, and pulls the attendance of who’s who of fintech, finservices and well… anyone who’s involved with touching money in anyway in India.

Anyway, this weekend, just ahead of GFF, I built a tiny lightweight service I’ve wanted to use at every stuffy and formal conference I’ve ever attended: an anonymous, public, message board for the funny, controversial or just plain weird things people say in hallways.
It’s intentionally simple: a single feed-page, an “Add” CTA + Modal, and quick sort controls so you can browse the newest/ oldest posts. No login, no profiles, no my grandmother told me a story type intro for SEO farming.
What it does
- Let’s a user post some text anonymously with a 320-character limit.
- No moderation by a human, some content moderation of the input text.
- Upon successful posting, you can see your message on the feed instantly. A refresh button pulls the latest; new posts show up at the top.
- Like/dislike exists - but doesn’t get “saved” - bug to be fixed for later.
- Mobile-first/friendly.
- One page, clean spacing, neat UI (thanks shadcn).

The stack:
- Next.js for a single page + API routes.
- Supabase for storage / SQL power.
- Shadcn components for the UI.
How it works:
At its heart, the app runs on Next.js (App Router) and Supabase. The backend lives entirely inside one API route, /api/submit. When someone submits a quote, it passes through a series of thoughtful filters — what I like to call “polite gatekeeping for dummies.”
Filtering out profanity and spam - Zod, libraries:
Zod validates the payload. The text must be between 5 and 320 characters. Optional fields like “who” and “where” get trimmed and sanitized automatically. If someone forgets to fill the main field, they get a friendly “This field is mandatory” message.
Next, there’s a honeypot check to quietly (try to) catch bots. There’s an invisible field (nickname_check) that humans never see, but spam scripts almost always fill (says the internet). If that field contains anything, the code discards the post.
After passing this basic check, comes a quality check to filter out low quality posts — pure numbers, only punctuation, or only emoji spam - all get filtered out. If a message passes those, it still has to dodge two more defenses:
A rate-limit (max five submissions per hour per IP, hashed for privacy).
A profanity filter, built with both English and Hindi libraries — leo-profanity and profanity-hindi.
That’s it - if you pass through these 5 hoops - your post is in!
Approved posts - Supabase
Messages that survive this process get inserted into Supabase with the status ‘approved’.
In the future, I might add moderation (and I did set up a table and statuses but decided I didn’t want to build it right away), but for now, the app trusts its filters.
There is another Supabase table to store logs for every event: validation failures, rate-limit hits, duplicate posts, and errors. This way, SQL queries can run inside Supabase and give a birds eye view of what’s happening under the hood.
Deployment was via Vercel
This was my first proper deployment, and I ran into multiple build errors that I had to fix using ChatGPT’s assistance. Most were ESLint “warnings”, that I had to silence/ clear up.

voila!
Such a feeling of accomplishment. It was the end of Day 1 of the conference by the time I got the web-app deployed, so I didn’t have enough time to socialise it.
horny folks and regulator complaints
When OverheardAT went out into the world, I honstly assumed I’d hear real juicy goss about revenue metrics and raises (and honestly what was I thinking), there appeared a string of posts about hot girls, hot VCs and the odd crypto bro complaining about forbidden words!. Maybe we’ll get the real gossip going next year!
