DEV Community

Cover image for How I Built a Truly Anonymous Polling Tool — And Why "Anonymous" Usually Isn't
Mohammed Ashraf
Mohammed Ashraf

Posted on • Originally published at anonpolls.com

How I Built a Truly Anonymous Polling Tool — And Why "Anonymous" Usually Isn't

I built AnonPolls — a free
anonymous polling tool where neither the creator nor
the voter needs an account.

This post covers why I built it, the technical decisions
behind "true" anonymity, and what I learned growing it
to 1,500+ votes in 3 months as a solo founder.

The problem with "anonymous" surveys

Most polling tools that claim anonymity have a catch.

Google Forms — if your organisation has "Restrict to
[domain] users" enabled, the form owner can see individual
responses tied to Google accounts. "Anonymous" is a UI
label, not a technical guarantee.

Microsoft Teams polls — via Microsoft Forms, tenant
admins can view individual responses depending on your
organisation's settings. This is documented in Microsoft's
own support pages.

SurveyMonkey free tier — shows individual responses
to the survey creator.

The pattern: tools call themselves anonymous because
voters don't see each other's responses. But the creator
or an admin can still identify individuals.

What "truly anonymous" actually means technically

For AnonPolls, I made two architectural decisions:

1. No accounts — for anyone

Most tools require the creator to have an account. That
account is linked to every poll they create. Even if
voters are anonymous to each other, the creator's identity
is attached to the data.

AnonPolls requires no account for the creator either.
No email, no OAuth, no session tied to an identity.

2. IP hashing, not IP storage

For vote deduplication (preventing someone voting twice),
I need to track something. But storing raw IPs creates a
privacy risk — even "anonymized" IPs can sometimes be
reverse-engineered.

Instead I use HMAC-SHA256 hashing on the IP before storage:

import crypto from 'crypto';

function anonymizeIp(ip: string): string {
  const secret = process.env.IP_HASH_SECRET;
  return 'anon.' + crypto
    .createHmac('sha256', secret)
    .update(ip)
    .digest('hex')
    .substring(0, 12);
}
Enter fullscreen mode Exit fullscreen mode

The hash is one-way. Even if someone accessed the database,
they couldn't reverse the hash back to an IP address.

The result: even the poll creator — even me as the
platform operator — cannot identify who voted for what.

Stack

  • Frontend: React 18 + TypeScript + Vite + Tailwind + Radix UI + shadcn
  • Backend: Node.js + Express + TypeScript
  • Database: Neon serverless Postgres + Drizzle ORM
  • AI: Google Gemini API for poll generation
  • Hosting: Hostinger (Node.js + LiteSpeed)

What I learned about distribution

The product was live for 2 months before I paid serious
attention to distribution. Some things I learned:

Fix engagement metrics before promoting

My "activation rate" (polls getting at least 1 vote)
was 38%. I was driving traffic into a leaky funnel.

The single biggest fix: vote-first display — hiding
poll results until the user votes. Activation rate jumped
to 58% in one week. Avg votes per poll went from 1.4 to 2.3.

Only after fixing that did I start on SEO and community
outreach.

AI search is already real

I added llms.txt, WebApplication + FAQPage JSON-LD
schemas, and BlogPosting schema to blog posts. ChatGPT
and Microsoft Copilot started sending sessions within
one week.

Not a lot of traffic yet — but faster than I expected.

Long-tail keywords competitors ignore

"anonymous poll microsoft teams" — no major competitor
has a dedicated page for this. I wrote a blog post
targeting it and built a landing page. Both indexed
within a week.

Same for "anonymous poll whatsapp", "slido alternative
free", "strawpoll alternative".

Current numbers (month 3)

  • 676 polls created
  • 1,564 votes cast
  • 90+ countries
  • Avg votes per poll: 2.3
  • Activation rate: 58%
  • AI agent traffic: ChatGPT + Copilot sending sessions

What's next

Building creator retention tracking (repeat creators
by anonymized IP hash), preparing a Pro tier, and
continuing the content + SEO engine.

If you're building something similar or have questions
about the anonymity implementation, happy to discuss
in the comments.

Try AnonPolls →

Top comments (3)

Collapse
 
privacyfish profile image
Privacy.Fish

Sounds cool, especially the distinction between “anonymous to other respondents” and “anonymous from the creator/operator”. That gap is where a lot of products get fuzzy.

One caveat I’d be explicit about: HMACing IPs is much better than storing raw IPs, but it is still a stable pseudonymous identifier as long as the HMAC key and time window stay the same. The operator can still test candidate IPs, and a database + secret leak makes historical vote linkage possible. A few mitigations that help: rotate the HMAC secret per poll or per short time bucket, keep only the dedupe window you actually need, and document exactly what “anonymous” excludes.

For a poll product, the hard tradeoff is Sybil resistance vs privacy. The more you prevent duplicate voting, the more some fingerprint has to exist somewhere. I like that you’re making that tradeoff visible instead of hiding it behind a privacy label.

Collapse
 
mdashraf profile image
Mohammed Ashraf

really appreciate this, you've described the tradeoff
better than i did in the article.

the per-poll HMAC rotation is something i've been
thinking about — haven't implemented it yet but it's
the right direction. currently the window is 24h which
helps but you're right that key + db leak is still a
risk.

the honest answer is for most of our use cases (team
decisions, classroom polls, community votes) the threat
model is "prevent obvious double voting" not "resist
a determined adversary." but i should probably say
that more explicitly in the article.

will update the post with a clearer disclaimer on what
anonymous excludes. thanks for pushing on this.

Collapse
 
privacyfish profile image
Privacy.Fish

You're most welcome :)