Build Savio
KINDAreplaces $39/mosaves $468/yrback to the verdict
A feedback aggregation tool where the value is not counting requests but knowing who asked: the same request from forty trial users and from your three largest customers are different signals, and a tool that only counts cannot tell them apart.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the server and the import workers.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree locally
Why Conversations, requests and account attribution with real relationships.
Get it Docker or your package manager. open ↗
Verify
psql connects and full text search returns a result - have readyfree with an existing system
Why Extraction quality, clustering and the difference between adjacent and identical requests only appear at volume and in real language.
Get it Export from your support system, including the threads that contain no request at all, which are most of them.
Verify
Five hundred conversations are imported including many with no request - have readyfree
Why The whole premise is that who asked matters more than how many, which requires knowing something about the accounts beyond their names.
Get it Export accounts with plan, seats and value from wherever that lives, and decide how it stays current.
Verify
Accounts import with segment and value attributes - decidefree
Why The reporting is built around segments, and choosing them after seeing the data means choosing whichever segmentation supports the conclusion you already had.
Get it Decide the segments that matter to you and write down why, before generating any theme reports.
Verify
A written note names the segments and the reasoning
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- accounts(id, external_ref, name, plan, seats, value_minor, health, attributes_json) - people(id, account_id, email_hash, name, role) - conversations(id, source, external_id, person_id, subject, body, received_at, imported_at) - requests(id, conversation_id, quote, char_start, char_end, extracted_at, confirmed_by) - themes(id, title, description, created_at, merged_into_id) · the deduplicated grouping - request_themes(request_id, theme_id, confidence, confirmed_by, at) - decisions(theme_id, outcome, note, decided_by, at, communicated_at) · what happened and whether anyone was told - A request keeps the quote and its position in the conversation, because a paraphrased request loses the specificity that made it actionable.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_URLsecret | required | postgres://localhost:5432/feedback | Customer conversations, which contain personal data and often frustration. |
SUPPORT_API_KEYsecret | optional | a read-only key | Read only. Absent means manual import, which is a supported path rather than a fallback. |
EXTRACTION_MODEL | required | local | Runs over customer conversations, which is a reason to keep it local. |
COUNT_ONLY_RANKING | required | never | Present as a constant. A bare count looks objective and hands the roadmap to whoever writes in most. |
REQUIRE_HUMAN_CONFIRMATION | required | always | For extraction and clustering both. Wrong merges are not recoverable. |
CONVERSATION_RETENTION_DAYS | required | 1095 | Customer conversations are personal data with a stated retention. |
The build, in order
Getting conversations in, both ways
Feedback arrives from a connector or by paste, equally well.
Connectors break and feedback keeps arriving.
Files
src/import.tsNot on content. The same message reimported is the common case.
Files
src/dedupe.tsA summary loses the sentence that made the request actionable.
Files
src/conversations.ts
done when · tick each as it passeswatch out- Building only around a connector means the tool stops working entirely when the support system changes its API, and the feedback keeps arriving in the meantime. Make manual import a first-class path from the start rather than a fallback nobody tested.
Finding the request inside the conversation
A support thread yields the specific sentence that is a feature request.
The request is usually one sentence in a longer exchange.
Files
src/extract.tsForcing one per thread is where the specificity goes.
Files
src/extract.tsExtraction quality varies and a wrong request pollutes a theme.
Files
src/review.ts
done when · tick each as it passeswatch out- Treating a whole support conversation as one feature request loses the specificity that made it useful, since a thread about a billing problem often contains one sentence that is the actual request. Extract at sentence level with the quote kept, and let a conversation produce several requests or none.
The same request, twenty wordings
Duplicate requests group without losing distinct ones.
Adjacent but distinct asks merge wrongly and irreversibly otherwise.
Files
src/themes.tsThe theme summary is not a substitute for what someone actually said.
Files
src/themes.tsYou will merge some wrongly and notice a month later.
Files
src/themes.tstests/unmerge.test.ts
done when · tick each as it passeswatch out- Automatically clustering requests merges two adjacent but distinct asks into one theme, and once the individual requests are represented only by the theme's summary the distinction is gone and cannot be recovered. Propose, confirm, keep the originals, and allow unmerging.
Who asked, not how many
A theme shows the accounts behind it, not just a count.
Who asked is the signal. How many is the noise.
Files
src/attribution.tsForty trial users and three large customers are different findings.
Files
src/report.tsIts absence is the design. A bare count looks objective and is not.
Files
tests/no-count-ranking.test.ts
done when · tick each as it passeswatch out- Ranking themes by request count hands the roadmap to whoever contacts support most, which is frequently trial users and rarely your largest customers, and the ranking looks entirely objective. Show the segment breakdown and the account value beside the count, and refuse to provide a bare count ordering.
Closing the loopproduct builder
When something ships, the people who asked for it find out.
Not from a mailing list. The people who asked are recorded.
Files
src/loop.tsUnreported deliveries are the normal failure and are invisible.
Files
src/decisions.tsDeclining silently is worse than declining.
Files
src/loop.ts
done when · tick each as it passeswatch out- Recording that a theme was delivered without producing the list of people who asked means nobody is told, and the customer who raised it eighteen months ago never learns, which is the most common failure of every feedback process. Generate the list from the requests and track whether the communication happened.
That is the whole plan for Savio. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Being the support system.
- Public roadmaps and voting.
- Sentiment scoring.
- Automatic prioritisation.
- CRM and support integrations, customer revenue context, workflow, and collaboration
- large participant panel
- recording and transcription infrastructure
- advanced research repository
- enterprise governance
- Linking a theme to the work that delivered it, which is what makes the loop closeable automatically
- Requests from sales calls and churn interviews, which are the highest value and least captured
- A view of what a specific account has asked for, which is the question account managers actually have
Need the files? The project pack on the verdict page hands your agent the whole brief · more user research.