Jason Stutman
All projects

Maxwell Cyberlogic · 2024–present

OfficePoll

Anonymous workplace peer feedback platform.

officepoll.com

What it is

A free anonymous peer feedback platform for professionals. Users sign up via LinkedIn OAuth, share a unique link with colleagues, and collect honest ratings and written feedback across six professional categories. The platform anonymizes everything through a multi-layer pipeline that includes permanent deletion of original text — then synthesizes a unified career report once enough reviews come in.

70+ database migrations. 74 API routes. 60+ React components. Next.js 16 App Router, Supabase Postgres with hardened RLS, NextAuth.js with LinkedIn OAuth, GPT-5.4 across every AI surface. Vitest coverage on critical paths, Sentry observability, Vercel hosting.

The anonymization pipeline

The flagship feature, and the entire product's trust contract. Every submission passes through a single-pass GPT-5.4 call (JSON mode) that combines two objectives in one prompt:

  1. PII scrubbing — remove all personally identifiable information: names, project names, dates, locations, team identifiers, anything that could narrow down who wrote it or when.
  2. Stylometric voice neutralization — rewrite into a standardized professional voice. Sentence length, vocabulary, punctuation habits, emphasis patterns — all normalized. The meaning survives. The writing fingerprint does not.

Response shape is structured JSON: { status, anonymized_text, pii_items_redacted, reason }. The status field returns INSUFFICIENT_INPUT when feedback is too specific to anonymize safely — the pipeline rejects the submission and asks the reviewer to rewrite.

Operational hardening: pre-flight check rejects submissions under 20 characters before spending an LLM call. Concurrency capped at 8 concurrent calls via a semaphore in llm-client.tsso the pipeline doesn't self-rate-limit under load. Retry logic: up to 4 attempts with exponential backoff, respects Retry-After headers.

And the part that makes it actually anonymous: original text is deleted permanently at the storage boundary. Not soft-deleted. Not archived. Gone within milliseconds of processing. Only the anonymized version persists in feedback_submissions.anonymized_comment, with original_deleted_at timestamping the deletion as proof. The system is designed so de-anonymization is architecturally impossible — even with full database access, the original words no longer exist.

The synthesized report

Reports unlock in tiers based on how many reviews someone has received. The tiering exists because the synthesis engine won't generate narrative insights from too few data points — doing so would be irresponsible.

  • 3+ reviews — basic narrative summary plus aggregate scores across six categories: execution, communication, collaboration, ownership, judgment, mentorship
  • 5+ reviews — full synthesis with top_strengths (3–5, ranked by evidence), growth_areas (2–4, framed as actionable), narrative_summary (second-person), public_narrative (third-person mirror for shareable view), confidence (high/medium/low), plus consensus score showing reviewer agreement
  • 10+ reviews — unlocks blind-spot detection, development lever (the single behavior change with the biggest ripple effect), and the AI Career Coach

Synthesis runs as a single GPT-5.4 call (JSON mode). Includes change_from_last deltas if a previous report exists, so users see whether their scores are trending up or down across feedback cycles.

Reviewer credibility weighting

Not every voice carries equal weight. Reviewers are bucketed into four tiers based on feedback_given_count:

  • New Reviewer (0–2 reviews given): 0.6× weight
  • Contributor (3–9): 1.0×
  • Trusted Reviewer (10–24): 1.2×
  • Top Contributor (25+): 1.4×

Duplicate normalization keeps the math fair: if the same reviewer submits multiple times, their total weight is divided across submissions so each person gets equal influence regardless of how many sessions they ran. Abuse-based penalty multipliers stack on top, computed at synthesis time. The credibility tier of each reviewer is shown in the synthesis prompt itself, so the model can see trustworthiness patterns when it weighs themes.

The AI Career Coach

Unlocked at 10+ reviews. An interactive coaching agent grounded in a 410-line system prompt that draws on 10 evidence-based coaching frameworks:

  1. Feedforward (Goldsmith)
  2. GROW model (Whitmore)
  3. Tiny Habits (BJ Fogg)
  4. Implementation Intentions (Gollwitzer)
  5. Cognitive Behavioral Coaching (Neenan & Palmer)
  6. Johari Window (Luft & Ingham)
  7. Immunity to Change (Kegan & Lahey)
  8. Stages of Change (Prochaska)
  9. Feedback Intervention Theory (Kluger & DeNisi)
  10. Assessment-Challenge-Support (Center for Creative Leadership)

Each conversation is a phase-tracked GROW session — Goal, Reality, Options, Will — with explicit phase progression visible to the user. State lives in a coach_sessions table tracking phase, exchange count, and full history. A topic state machine enforces structure: one topic at a time, up to 4 exchanges per phase, 12 per topic.

The coach persona is direct and propositional — offers concrete suggestions instead of open-ended questions, 3–5 sentences per response. The system prompt explicitly forbids warmth performance patterns ("I hear you", "great question") so the agent feels substantive instead of therapeutic.

Topics aren't pre-baked. A separate Coach Synthesiscall (also GPT-5.4) runs alongside the report, producing structured analysis: per-category (score/trend/key_themes/blind_spot_signals), cross-cutting patterns, strongest development lever, emotional tone of feedback, previous-report comparison. The interactive coach reads from that structured analysis to derive 3–4 topics worth exploring, personalized to each user's feedback shape.

Two-tier abuse detection

Anonymous systems get gamed if you let them. Two layers:

Auto-reduce penalties (silent, applied at synthesis)

  • Single target + extreme ratings (avg ≤ 2.0 or ≥ 4.5) + zero social-graph overlap → 0.3× multiplier (probably someone who created an account just to leave one review)
  • Zero variance across 3+ reviews → 0.5× multiplier (the "all 5s" or "all 1s" pattern that signals not paying attention)

Admin-flagged human review

  • Single target + extreme + has social overlap (potential revenge from a known colleague — needs human judgment)
  • Extreme negative pattern across 3+ submissions, 2+ recipients, avg ≤ 1.8, variance < 1.0 (a pattern that looks like a serial griefer)

Social graph overlap considers shared company domain, shared reviewers, shared reviewees, and profile views. Origin fingerprinting uses a one-way SHA-256 hash with a salt — IP addresses are never stored, only the hash, which auto-expires after 30 days. The fingerprint can cluster suspicious patterns without ever being reversible to a real IP.

Moderation states in the users table: active / ghosted / banned. Banned users can't sign in. Ghosted users' reviews are silently weighted to 0 at synthesis time — they don't know they've been ghosted, which is the point.

Three ways feedback enters the system

All three paths funnel through the same /api/feedback/submit endpoint and the same anonymization pipeline. The difference is how reviewers find a person to review.

  1. Profile link /p/[slug]— the visitor follows someone's shared link
  2. Tokenized round /r/[token] — the visitor follows a tokenized feedback-round link (used for time-bound feedback collection)
  3. People search /give — hybrid internal full-text search + Serper.dev (site:linkedin.com/in/) with verified-user collapse so the reviewer never learns whether the target is registered or not

Rate limiting: 5 submissions / 60s / IP, 10 searches / 60s / IP.

Escrow for non-registered users

The third feedback path means anyone can leave feedback for anyone via LinkedIn URL — even people who haven't signed up yet. That feedback can't just disappear. A feedback_escrow table holds anonymized feedback for non-users:

  • Anonymization runs before escrow — only the cleaned version is stored, original text is already gone
  • 3-month cooldown per reviewer per recipient
  • 90-day expiry with a cleanupExpiredEscrow() cron
  • Permanent opt-out check against notification_optouts — silently succeeds without ever revealing whether the target opted out
  • Atomic claim via 030_claim_escrow_atomic.sql — when a user signs up, their LinkedIn URL is matched against escrow and feedback is transferred in a single SQL transaction (no double-claim race)

Mini polls + community polls

Mini polls — quick, targeted feedback

Users pin a single question to their profile — 1-to-5 scale or multiple choice — that any visitor can answer in five seconds. Up to 3 active mini polls at a time, picked from a curated 50+ question library or written from scratch. Results appear after 3 responses and update in real time. Lighter-weight feedback for the space between full review cycles.

Community polls — industry-wide sentiment

Auto-generated weekly via GPT-5.4 based on three inputs: trending categories, underrepresented categories, and current workplace news headlines. Pure structured-generation prompt work. Plus user-created polls — any logged-in user can post a workplace question and the entire user base can vote. Results segmented by industry and department. Comments and upvotes layered on top, with a cron job running comment moderation every 30 minutes.

Authentication & onboarding

NextAuth.js with LinkedIn OAuth (openid profile email scopes). Each user gets a profile_slug generated as firstname-lastname-RANDOM. Profile slug is stored in the JWT for fast redirects. Name change detection via oauth_first_name / oauth_last_name flags in admin_flags — when LinkedIn reports a different name, the system flags the drift without crashing. Banned-user lockout happens in the signin callback before session creation.

Email & drip orchestration

Resend-based transactional and drip email system, with React Email templates for every lifecycle moment:

  • Welcome (immediate post-signup)
  • Milestone unlocks (3 / 5 / 10 reviews received)
  • Reengagement (stalled users)
  • Weekly digest
  • Drip automations, admin-toggleable per-key: Share your profile, Feedback tips, It's safe, Invite peers

Stateless HMAC unsubscribe tokens — SHA-256 of the user ID with a secret. One-click unsubscribe via the List-Unsubscribe header self-verifies without a DB lookup. Blocklist enforcement against email_blocklist for bounced and complained addresses. Per-key automation toggles cached for 1 minute so admins can disable individual drip flows without redeploys. Send logging to email_send_log with Resend message IDs for deliverability tracking. Webhook listener at /api/webhooks/resend for bounce/complaint/delivery events.

Vercel Cron orchestrates the rhythm: weekly community-poll generation (Mondays 7am UTC), comment moderation (every 30 min), campaign send batching, drip pacing, weekly digest.

Skill stamps + open graph

Endorsement badge system — GET/POST /api/stamps and POST /api/stamps/endorse — for peer endorsement beyond freeform feedback.

Dynamic OG images at /api/og?name=...&title=... for shareable profile previews on LinkedIn / Slack / Twitter. Plus scorecard OG images at /api/og/scorecard?overall=4.2&execution=4.1... — users with public profiles can post their feedback scores as a shareable image card.

Marketing site & blog

The full marketing surface — hero copy, product sections, anonymization explainers, four-layer trust narrative, signup flow. Plus a long-form blog covering feedback culture, anonymity, coaching, and workplace dynamics. Posts range from contrarian trust pieces to research-backed problem essays to category-pivot POV arguments. A live B2B/SaaS writing surface, graded against organic reach, time on page, and signup conversion.

Sample posts

Three are hosted in full on this site. Three more link directly to the live OfficePoll blog.

Foundational explainer

What Is OfficePoll?

The piece every B2B product needs but most do badly — a clear, complete walkthrough of what the thing is, how it works, who it's for, and what makes it different. Every claim has a mechanism behind it.

Read the full post

Research-backed problem essay

The Blind Spots Holding Back Your Career (That No One Will Tell You About)

A long-form argument grounded in Tasha Eurich, Dunning-Kruger, the Johari Window, and the MUM effect. Builds the case that everyone has career blind spots — and lets the research, not the product, do the persuading.

Read the full post

Behavioral case essay

Why You Should Actively Seek Anonymous Feedback (Even If It Scares You)

Builds a behavioral case using the Zenger Folkman 71-percentile-point study, Stone & Heen's three feedback triggers, Carol Dweck on growth mindset, and Kim Scott's Radical Candor. Ends with a six-point playbook.

Read the full post

Contrarian trust narrative

We Delete Your Original Words. Here's Why That's the Point.

Argues that retention-based privacy policies are structurally weaker than deletion-based architectures. Schneier's toxic-asset framing plus the AOL, Netflix, and Signal precedents. The article does the credibility work; the product is the conclusion.

Read on OfficePoll

Research-backed problem essay

Why Performance Reviews Fail

Builds the case through external evidence — Buckingham, Gallup, Project Aristotle, Adobe and Deloitte and GE moving off annual reviews. Mentions the product only in the closer. Evidence does the selling.

Read on OfficePoll

Category-pivot POV essay

Why OfficePoll Is on a Mission to Replace the Professional Referral

A direct attack on a hiring ritual. Uses the Schmidt-Hunter validity coefficients to argue references are .26 validity vs. peer assessment at .69. Picks a fight and lands the product as the resolution.

Read on OfficePoll

The engineering layer

  • Next.js 16 (App Router), React 19, TypeScript 5.9
  • Supabase Postgres with hardened RLS on all sensitive tables, 70+ migrations
  • NextAuth.js with LinkedIn OAuth, JWT-based sessions, banned-user lockout in the signin callback
  • OpenAI GPT-5.4 across every AI surface (anonymization, synthesis, coach, polls)
  • Resend for transactional + drip email with stateless HMAC unsubscribe and webhook-driven deliverability tracking
  • Serper.dev for hybrid people search (Google results filtered to LinkedIn profiles)
  • Vercel Cron orchestrating poll generation, moderation, drip pacing, weekly digest
  • Sentry monitoring on every API route and edge function
  • Vitest coverage on critical paths: anonymizer, synthesizer, coach-prompt, email, drip, rate-limit
  • 74 API routes: auth, feedback core, user / profile / settings, polls (community + personal), AI Coach, email / notifications, skill stamps, companies, cron jobs, admin (30+), internal cleanup, webhooks
  • 60+ React components, base-ui + shadcn/ui, Tailwind CSS 4, Tiptap for rich text, react-email for transactional templates