Back to Blog
Case StudyCase & Studies

Designing Feedback Box: From Idea to Internal Product

This is the story of how a messy feedback process turned into a full-blown internal platform.

15 min read
Dec 4, 2024

Every large organization says the same thing:

"We want feedback from the team."

And then what happens?

Ideas get lost in email threads

"We should fix this..." messages disappear in Teams chats

Nobody knows what's approved, what's in progress, and what died in a meeting

That was the reality.

As a Senior Manager leading QA/Engineering, I was seeing the same pattern:

1

Smart people sharing good ideas

2

No single place to capture them

3

No workflow to review them

4

No visibility into decisions

So I stopped complaining about the process and built a product.

That product became Feedback Box (Voxa) — an internal platform for capturing, triaging, voting on, and approving ideas with auditability, governance, and clarity.

This is how it went from "we should have a form" to a fully structured internal product.

1

Starting Point: A Messy, Informal Feedback Culture

Here's the thing: the problem was not "we don't get feedback".

We had too much feedback, in the wrong format, through the wrong channels.

Patterns I saw:

Ideas buried inside meeting notes

"Quick asks" lost in Teams

No distinction between genuine product ideas, one-off complaints, and big structural improvements

And because nothing was structured:

No consistent intake

No system of record

No way to track lifecycle

No way to show leadership a clean pipeline

So I defined the core question:

"What would it look like if feedback flowed through the organization like a proper product backlog, with ownership, SLAs, and data?"

That question became the starting spec.

2

Turning Vague Complaints Into a Product Concept

Before I wrote a single line of code, I treated this like a real product.

2.1. Mapping the real actors

I identified the actual roles in the system, not just job titles:

Contributor

anyone who can submit feedback

Reviewer / Screener

validates: is this real, actionable feedback?

Recommender

senior leaders whose endorsement matters

Approver

people who can say "we are doing this"

Administrator

manages roles, titles, departments, and access

Then I mapped their questions:

?

Where do I submit an idea?

?

What happened to my suggestion?

?

Who approved or rejected this?

?

Why was it rejected?

?

Which ideas are actually in progress?

If a feature didn't answer one of these questions, it was noise.

2.2. Non-negotiable constraints

From there, I set some hard constraints:

No anonymous chaos

anonymous supported initially, but system must work if turned off

Role-based access

no magic admin emails in env vars; everything as data

Single source of truth

one DB, one feedback record, full history

Auditable decisions

every approval, rejection, recommendation needs traceability

SSO-ready

architecture ready for Microsoft SSO from day one

From that, the product snapped into shape:

Key Insight:

Voxa is not a form. It's a governed pipeline for feedback.

3

System Architecture at a Glance

I designed the system like something I would be happy to defend in an architecture review.

3.1. High-level view

Frontend

Next.js (App Router) + TypeScript + Tailwind CSS + Headless UI

Backend

Node.js + Express + Sequelize

Database

PostgreSQL (Supabase for dev → internal/Postgres for prod)

Auth

Email/password with future SSO hook points

Hosting

Internal Windows Server, reverse proxy (clean URL)

Email

Nodemailer with strongly typed templates

Storage

Cloudflare Images / Azure Blob for profile photos

The important part: I separated concerns as if this were a product we might one day externalize.

3.2. Backend layering

The backend follows a simple but strict layering:

1
Routes / Controllers

HTTP layer, validation, and response shaping

2
Services

business logic, workflows, role/permission checks

3
Repositories (Sequelize models)

handle persistence and queries

4
Shared utilities

error handling, JWT, email, logging

No business logic in controllers.

No raw SQL in random files.

Everything that matters to the business lives in services, which is where the "Director-level" decisions are embedded:

?

Who can approve?

?

Who can recommend?

?

What happens when feedback is rejected?

?

Who gets notified?

4

Designing Roles, Permissions, and Workflows People Actually Follow

Most internal tools die because the permission model is bolted on later.

I started there.

4.1. Explicit role and permission model

Instead of hardcoding checks like:

if (user.email === process.env.SUPER_ADMIN) { ... }

…I designed a proper RBAC (Role-Based Access Control) system:

R
Roles
System SuperUser
System Admin
System Master
System Manager
System Default
P
Permissions (examples)
VIEW_ALL_FEEDBACK
SUBMIT_FEEDBACK
ADD_VOTE
ADD_COMMENT
EDIT_OWN_COMMENT
DELETE_OWN_COMMENT
DELETE_OTHERS_COMMENT
MANAGE_USERS
ASSIGN_ROLES
RECOMMEND_FEEDBACK
APPROVE_FEEDBACK

And then the glue tables:

User ↔ UserRole ↔ Role

Role ↔ RolePermission ↔ Permission

This allows:

Multiple roles per user

Easy permission toggling via UI (SuperUser page)

Frontend to render features based on permission strings

Once this was in place, the UI could simply ask:

user.permissions.includes('ADD_VOTE')

No magic. No duplication.

Director lens:

This is not just "auth". It's governance. It lets leadership decide how strict the system should be without asking for code changes.

4.2. Feedback lifecycle workflow

Then I designed the lifecycle:

1
Submitted

User fills the feedback drawer, chooses priority, privacy, and submits

System stores it as status = 'pending'

2
Screening / Moderation

Reviewers see all pending feedback in a dedicated Admin view

They can Accept, Reject, or Request Clarification (future)

On reject, a reason is mandatory and emailed back to the submitter

3
Recommendation

For accepted feedback, users with RECOMMEND_FEEDBACK permission can mark: Recommended or Not recommended

The UI shows a visual recommender list with icons and state

4
Approval

Approvers see a Feedback Approval dashboard that only shows items: accepted with all required recommendations

Approve → status = 'approved' or 'completed' (configurable)

5
Completion / Archive

Once implemented or decided, feedback moves to Completed

The main dashboard switches between Active Feedback and Completed Feedback

The result is an opinionated, transparent pipeline.

5

Frontend Decisions: Why Drawers, Not Popups

The frontend was not an afterthought. I designed it very intentionally.

5.1. Why Tailwind and Headless UI

I chose:

Next.js App Router

modern routing, server components where appropriate, and good DX

Tailwind CSS

consistent design language, easy to mirror complex mockups, and fast iteration

Headless UI

accessible primitives for dialogs, drawers, and dropdowns without fighting CSS

This allowed me to:

Build pixel-perfect layouts

Keep behavior and styling in sync

Move fast while keeping a clean component structure

5.2. The feedback drawer pattern

I didn't want a tiny modal in the middle of the screen.

I designed a slide-in drawer for creating and viewing feedback:

Full-height on desktop, mobile-optimized

Purple header with title and close

Single-column form

Priority dropdown

Privacy toggle

Large description area

Reasons for drawer over popup:

People often paste long, thoughtful feedback

They might reference other apps while typing

The drawer feels like a "workspace", not a pop quiz

Dismiss behavior is clear and deliberate

The drawer also includes:

Real-time validation

Cancel handling logic for unsaved changes

Success modal on submit

5.3. Consistent visual language across pages

I kept one consistent visual design system:

1

Registration form (single-column, modern layout)

2

Dashboard with profile card + feedback list

3

Admin tables (Pending Users, User Management, Titles, Permissions)

4

Drawers for: Viewing feedback, Approving feedback, Approving users, Editing users

Everything follows the same principles:

Clear typography

Generous spacing

Obvious actions

No "clever" UI that confuses people

6

Backend Structure: Tables, Relationships, Approvals, Notifications

Now the fun part.

6.1. Core entities

At the heart of the system:

U
User

name, email, password hash

titleId, departmentId, managerId

profilePhotoUrl

approved flag

resetToken / resetTokenExpiry

R
Role / Permission

As described above, plus join tables

F
Feedback

title, description, priority

privacy (public / private)

status (pending, accepted, approved, completed, rejected)

submittedById, timestamps

F
FeedbackComment

feedbackId, userId, content

soft-delete flags or hard delete based on policy

F
FeedbackVote

feedbackId, userId

unique constraint to prevent double-voting

F
FeedbackRecommendation

feedbackId, recommenderId

status (recommended, not_recommended)

timestamp

A
AuditLog

entityType, entityId, action

actorId, payload snapshot

(planned / partially implemented)

T
Title / Department

Simple reference tables

surfaced in UI for registration, admin management, analytics

6.2. Approval and recommendation logic in code

The service layer encodes the rules:

Only users with RECOMMEND_FEEDBACK can create/update recommendations

Only users with APPROVE_FEEDBACK can approve

A feedback item only appears on the Approval page if status is accepted and all required recommenders have acted

Rejection requires:

Mandatory reason

Email to submitter with original title, reason, and next steps

6.3. Email notification system

I set up a simple but structured email layer:

Nodemailer transport

configured via env variables

Central emailTemplates.js

new feedback submitted, feedback accepted, feedback rejected, new comment/vote, feedback approved

Parameterized templates

{{userName}}, {{feedbackTitle}}, {{feedbackLink}}, {{reason}}

7

How QA Thinking Shaped Every Decision

Feedback Box wasn't just "built". It was engineered like a system that would be audited.

7.1. Data integrity by design

Examples:

Unique constraints

One vote per user per feedback

One active reset token per user

Foreign keys

You can't have a feedback assigned to a non-existent user

Status transitions

You can't approve feedback that was never accepted

You can't recommend feedback that doesn't exist

By encoding this in the DB and service layer, we reduce:

"Impossible" UI states

Manual clean-up

Data drift between environments

7.2. Edge-case-driven UI

I designed and coded for actual failure modes:

1
User tries to log in but is not approved

No ambiguous error. A styled modal tells them: You're registered, You're pending approval, What to expect next

2
User without ADD_VOTE permission

Vote button simply doesn't render. No "you are not authorized" toast on click; just a clean experience

3
Comments

"Edit" only appears if you have EDIT_OWN_COMMENT or DELETE_OTHERS_COMMENT when appropriate. Delete action opens a confirmation modal. Backend still enforces rules

7.3. Testability

Because of my QA background, I made sure the system is easy to test:

Clear, consistent API shapes

Predictable status codes and error formats

Simple headers for auth (JWT)

Deterministic role/permission behavior

The whole system is designed so that:

You can automate end-to-end flows

You can regression test role behavior

You can simulate edge cases without hacks

8

Registration, Security, and Identity

This part was critical, because the platform deals with internal people and sometimes sensitive commentary.

8.1. Controlled registration

Registration captures:

Full name

Email

Title

Department

Manager (with avatar)

Profile photo upload (Cloudflare Images)

Password (hashed, never stored in plain text)

Once submitted:

User record created with approved = false

Admins with MANAGE_USERS permission see them under Pending Users

Approval happens via a UserApprovalDrawer, where roles can be assigned

8.2. Login and pending state

On login:

1

Credentials validated

2

If user approved: JWT generated and stored → Redirect to dashboard

3

If not approved: No dashboard access → Clear error via modal dialog

This makes the approval gate a first-class citizen, not a side flag.

8.3. Forgot / Reset password

I implemented:

POST /forgot-password

Generates secure resetToken

Sets resetTokenExpiry

Emails the user a time-bound link

POST /reset-password

Validates token

Checks expiry

Updates password and clears token

This protects:

Users who forget credentials

System from endless admin manual resets

Security standards for internal tooling

9

Admin UX: Managing a Living System, Not a Static App

Admins often get the worst UI. I went the opposite direction.

9.1. Pending Users and User Management

Two admin views:

1
Pending Users

Table of users waiting for approval

"Approve" opens a drawer: Shows name, title, email, manager / Lets admin assign one or more roles

Primary action: Approve & Assign Roles

2
User Management

All approved users

Shows: Name, Email, Title, Manager, Roles (chips)

"Edit" opens an EditUserDrawer: Update profile fields / Update roles / Change title/department from dropdowns

9.2. Titles and Permissions as first-class objects

I created simple but powerful admin pages:

Title Management

Table with: Title name, Total users per title

Inline editing

Add Title button

Delete with confirmation

Permissions Overview

Cards for each permission: Icon, Name, Friendly description

This does two things:

Makes the permission model understandable to non-technical leaders

Makes it realistic to evolve the system over time without rewriting code

10

From "Tool" to "Internal Product"

Feedback Box (Voxa) is not just a coded solution. It's an internal product with:

Clear value

One place for feedback

Transparent lifecycle

Clear roles and ownership

Governance

Role and permission model

Approval and recommendation workflows

Auditable decisions

Technical discipline

Clean architecture

Strong DB design

Secure auth and password flow

Consistent UI patterns

Scaling path

SSO-ready

Possible to extend to multiple business units

Possible to evolve into a broader Ideas / Improvements Platform

Core Insight:

If you strip away the UI, Feedback Box is a governance engine: who can say what, who can decide what, and how those decisions are recorded, communicated, and acted on.

Final Thoughts

On paper, Feedback Box is "just" an internal feedback system.

In reality, it's a case study in:

Turning messy, informal behavior into a structured product

Baking roles, permissions, and workflows into the core model

Applying QA thinking to architecture, not just testing

Designing UI that respects people's time and mental load

This is the kind of work I enjoy most:

Sit in the middle of business, engineering, and operations

See the mess clearly

Design the system around it

Build it end-to-end - from DB schema to microcopy in the drawer header

That's what Feedback Box (Voxa) represents for me: not a side project, but a concrete example of how I think as an engineering leader.

Enjoyed This Article?

I write about building reliable systems, leading teams, and shipping products that matter.