From customer feedback to a shipped reply - closing the loop in one workspace
Customer feedback in Rezee lands in the same workspace as the issues, code and releases that answer it. How a sentence from a customer becomes a theme, an issue with demand on it, and one email when the work ships.
Oct 19, 2026 · 9 min read · Kash Gohil · LinkedIn · X
A customer writes in to say your run logs printed their deploy token. Somebody reads it, agrees it is bad, and files an issue. Three weeks later the fix ships. The customer never hears about it.
Nobody decides to skip that last step. It fails for a structural reason. The feedback tool knows who asked, the tracker knows what shipped, and the two do not know each other. So the reply depends on a person who remembers.
Rezee holds both ends. Feedback, issues, pull requests and releases are in one workspace, so the loop can close by itself: feedback, then a theme, then an issue, then a release, then the customer is told. This post follows one piece of feedback through that loop, and says what each step does and why we built it that way.
Where feedback comes in
There are four ways in, and each one files the same kind of record.
A form with a link. An admin designs it in Settings > Feedback forms, beside a live preview. A question is a choice, a rating out of 5 or 10, a short or long answer, an email, or a yes-or-no. A follow-up question shows only after a given answer, for example only when the category is "Bug". The form asks one question at a time, and Enter moves to the next one. It takes the workspace's own accent, colours, radius, typeface and logo, with Rezee's look as the default. The public page is at /f/<slug> and needs no account. Answers are rate limited per address and per form, and a hidden field catches scripts.
An API call. One POST /api/workspaces/:ws/feedback with a token from somebody who may file feedback. The settings page gives you the call to copy:
curl -X POST https://rezee.app/api/workspaces/acme/feedback \
-H "authorization: Bearer $REZEE_TOKEN" \
-H "content-type: application/json" \
-d '{"body": "Export hangs on big workspaces", "externalId": "zd-4211",
"customer": {"email": "ana@example.com"}}'The externalId is your own id for the item. Send the same one twice and Rezee files it once, so a help desk script can retry without fear. A customer can carry an account with its plan, seats and monthly value. Agents have the same path through the feedback_file tool.
A widget in your own app. An admin makes a publishable key and lists the origins that may use it. Two script tags put a launcher on your pages. The form opens in a frame on Rezee's origin, so your content security policy needs only frame-src and script-src for that origin. The feedback pane shows the page the customer sent it from.
Identity is the careful part. You call Rezee("identify", {...}) to say who is signed in, but a page can claim anything. So a claim alone only fills in the email question. Rezee ties feedback to a customer and their account only when your server signed the identity with the key's secret, as an HMAC-SHA256 over its fields in sorted order. Our server SDK, @rezee/feedback, does this with identityHash. The secret is sealed at rest and shown once.
A widget form filled in under a second and a half, or one with more than five links, gets a polite success and goes nowhere, so a script learns nothing. A workspace takes at most a thousand widget items a day.
Who can read it
Feedback carries your customers' own words and addresses. So it does not go to everybody who can see the backlog.
Each piece files one Intake item, the same queue that holds chat messages and failed runs waiting for a decision. The Intake item carries only a short title. Anything in it that looks like an email address or a phone number is replaced with [email] or [number]. The body stays with the feedback, behind a feedback:read permission. Owners and admins hold it by default. An admin can give it to any role as "Read customer feedback". Plan > Feedback is visible only to people who hold it.
An admin also sets how long feedback is kept: from 30 days to two years, or until somebody deletes it. After that the words and addresses are deleted and the Intake titles stay. A customer's record can be exported whole or deleted with every piece they sent, from the feedback pane.
Reading it for what it asks
A model reads each new piece of feedback, twenty at a time, for four things. What it asks for, as a short phrase like fix_csv_export or redact_secrets_in_logs. What kind it is: bug, feature request, hard to use, slow, question or praise. How the customer feels, from -1 to 1. And frustration markers from a fixed set, such as escalation_request and abandonment_signal. The answer comes back through one tool and is checked before Rezee keeps it.
The phrase is the important part. With each batch the model gets up to sixty phrases the workspace already uses, and the instruction is to reuse one when the request is the same in other words. Two customers who write "the logs leak my token" and "secrets show up in run output" should land on one phrase. That is what lets Rezee count them.
Three rules come with this. The text goes to a model provider, and the sender's name and address never do, as our subprocessors page says. Each read counts against the workspace's agent credits, because a background model that looks free is a cost nobody sees. And an admin can stop all of it with one switch, "Read new feedback".
From phrases to themes
A theme is a group of phrases that ask for the same thing. Rezee builds themes in two passes.
The first pass needs no model. Two phrases with the same words in another order, or with filler words or a plural, are one request. So fix_csv_export and fix_the_csv_exports merge at once. We kept this pass narrow on purpose. Anything looser than word order is a judgement, and a judgement belongs to the second pass.
In the second pass a model sorts the remaining new phrases. It must prefer an existing theme, and it must not merge two needs only because they touch the same area. "Fix CSV export" and "add PDF export" stay apart. A new theme gets a short label for a heading, like "CSV export failing".
Each theme then gets its numbers. The trend compares the last four weeks with the four before. More than 50% growth is "Rising", a drop of more than 30% is "Easing", and anything between is "Ongoing". Severity is high when the theme is at least 5% of feedback in the last 90 days and the customers in it are unhappy: an average feeling of -0.3 or lower, or 30% or more of its pieces show a customer who gave up or asked for a person. A theme at 2% or less is low.
These thresholds are not ours. We ported them unchanged from agnost, a project that does the same analysis on agent conversations, so the two read alike. They are a starting point, not a result we tuned on feedback.
A theme with at least two pieces gets an insight: a headline and a recommendation. A single mention, or only praise, is listed apart. The insight is written again only when the trend or severity changes, or the theme grows by a quarter and by at least three pieces. A model that writes a new paragraph on every poll costs credits and teaches readers to ignore it.
From a theme to an issue
Each theme in Plan > Feedback has two buttons: "Draft an issue" and "Draft a spec". A theme that is not low severity gets an issue drafted once, without anybody asking.
A draft does not become work by itself. It goes into Intake as one item, and only people who may read feedback see it. You read it whole there: what customers report, the evidence, a suggested fix, and when it is done. A spec draft has the usual sections of a spec. The draft takes its priority from the theme's severity. Nothing is filed until a person accepts it.
When a person accepts, Rezee files one issue and links every piece of feedback in the theme to it. The pieces' own Intake items, which nobody decided yet, merge into the issue in that person's name. The queue then stops asking about work that is already filed. A spec draft also files its spec doc beside the issue.
We could have let the agent file the issue. We did not, for the reason we give in the approval gate post: an agent that makes commitments in silence is an agent that a team stops trusting. Drafting is cheap. Accepting is the decision, and a person makes it.
Demand on the issue
Once feedback is linked to an issue, the issue shows "Customer demand": how many customers asked, from how many accounts, and what those accounts pay in a month. Under that is every piece of feedback, with the customer, their account and plan. A feedback writer can take one piece off the issue if it was linked by mistake, and the numbers change at once.
Demand is also a signal, an order and a column in Views. "Open issues, most customers first" is a saved view, not a spreadsheet export. Ties go to the accounts that pay more.
Take the demo workspace. Its Intake queue holds "Run logs show a token in the clone step" from support. Link that report and two more like it to REZ-42, and the issue shows three customers and their accounts. kobeni's pull request web#131, which masks secret values in the runner, sits on the same page.
The reply
This is the step the loop exists for. In Settings > Feedback forms, "Tell customers when it ships" is off until an admin turns it on. Then each customer linked to an issue gets one email when the issue reaches Done, or ships in a release or a deployment. One per customer per issue, never more.
The email quotes the customer, not the issue. Your issue title is internal language, and the customer did not write it. So the email reads like this:
On October 2, 2026 you told Acme:
Your run logs printed our deploy token.
The change you asked for has shipped. Thank you for telling us.
Under it is a link to "See your request", a page that shows Received, Planned, In progress or Shipped from the issues the request is linked to. The form's thank-you screen links to the same page, so a customer can follow along from the start. Every email has a one-click unsubscribe.
Two choices here are deliberate. Only work that ships after you turn notices on sends anything. Without that rule, turning it on would mail customers about all the old work at once. And release notes get a "Customer requests answered" section with the themes and a count of how many asked. They never name who asked.
A notice from an address the customer does not know is easy to ignore. So on Startup and up, an admin can add the workspace's own sending domain, and Rezee shows each DNS record to copy. If a verified domain stops verifying, notices fall back to Rezee's address and admins get an Inbox notice. A hard bounce or a spam complaint stops the workspace mailing that address again, and the feedback pane says why: "Stopped, their address bounced".
What is not here yet
There is no public board where customers vote on requests. Feedback is private to the workspace. There are no Zendesk, Intercom or Help Scout connectors yet. Today a help desk sends tickets through the API with ticket: true. There is no email address that files feedback. And themes need some volume: with a handful of messages a month, you get a tidy list, not a pattern.
What you do get is the part that is hard to do across two tools. The customer, the issue, the pull request and the release are rows in one workspace. So "tell the people who asked" is a query that runs when the work ships, not a task somebody keeps on a sticky note.