Collect feedback inside your app, not your inbox
5 min read

Collect feedback inside your app, not your inbox

Feedback by email loses context and never reaches the right place. How I collect feature requests and bug reports inside the app with a small widget that knows who sent them.

For a long time, feedback on my projects arrived the way it does for most solo developers: an email here, a DM there, a vague "it doesn't work" from someone I couldn't reach again. Some of it was gold. Most of it got lost before I had time to look at it properly.

The fix wasn't working harder through my inbox. It was moving the conversation into the app itself, where the user already is when something goes wrong or an idea strikes. This post is how I set that up with a small feedback widget, and the few settings that make the reports actually useful.

Why email and contact forms let you down

When feedback comes in through a generic channel, the same problems show up again and again:

  1. No context. "The button doesn't work." Which button? On which page? In which browser?

  2. No identity. An anonymous form tells me something happened, but not who to ask a follow-up question.

  3. No home. An email isn't a feature request until I copy it somewhere. If I don't, it's gone.

  4. No way back. Even if I build the thing, the person who asked rarely finds out.

None of that is the user's fault. They told me in the place I gave them. The place was just wrong.

Put the button where the problem happens

A feedback button inside the app catches people at the right moment: right after something broke, or while they're thinking "I wish this could...". Pullwish has an embeddable widget for exactly that, @pullwish/feature-reporter. It renders a floating button that opens a form with two tabs, one for feature requests and one for bug reports, and submissions go straight into my Pullwish project.

Setting it up is a few lines:

import { FeatureReporter } from "@pullwish/feature-reporter";

const reporter = new FeatureReporter({
  apiKey: process.env.NEXT_PUBLIC_PULLWISH_API_KEY!,
  projectId: process.env.NEXT_PUBLIC_PROJECT_ID!,
  position: "bottom-right",
  color: "blue",
});
reporter.init();

The widget renders itself into the page, so in a React or Next.js app you mount it once from a small client component and call destroy() when it unmounts. On the server, init() does nothing, so it's safe to import there.

The API key you give it needs the features:write permission for feature requests and bugs:write for bug reports. You create it under Settings → API Keys in the dashboard. Since the key lives in the browser, I make a separate one for the widget with just those two permissions instead of reusing a key that can do more.

Know who sent it

The single most useful line in my setup is this one, called right after the user signs in:

reporter.setUser(user.id, user.email, user.name);

Now every submission carries who sent it. When the email is known, the widget hides the email field, so signed-in users don't have to type it. For people who aren't signed in, the form keeps an optional email field with the hint "Get notified when we ship this", which is a nice way to get permission to follow up.

On top of that, the widget collects some context automatically with each report: the page URL, the user agent, the time and the screen resolution. For a bug report, that alone answers half the questions I used to send back by email.

Ask better questions with the labels

Every text string in the widget can be changed through labels. The defaults are sensible. The bug description placeholder, for example, is "What happened? What did you expect to happen?", which is close to the question I'd ask anyway.

I tune a few of them per product:

  • Match your product's language. A friendly "Report a problem" fits some apps better than "Bug Report".

  • Turn off what you don't need. On a marketing site I only want ideas, so I set enableBugReports: false and pick the lightbulb icon.

  • Point people to the board. With showDashboardLink: true, the form gets a link to your public project board, so people can see what's already requested and vote on it instead of writing a duplicate.

If you want to do something after a submission, such as showing your own thank-you message or tracking it in your analytics, onSubmit gets the type and the submitted data, and onError tells you when a submission fails.

What happens after someone hits Submit

This is where it pays off. Feature requests land on the project board next to the votes, comments and statuses I already work with, and bug reports go straight to Incoming as user-reported errors, ready for triage. There's no copying from my inbox and nothing to forget.

From there, it's the same routine I described in A changelog and roadmap your users actually read: give every request a status, answer it (even if the answer is no), and write the changelog entry for the person who asked. And because I set the user on every submission, I know who that person is.

What it costs

The feedback board, voting, public roadmap and changelog are on the free Hobby plan. The widget is an SDK, so it needs SDK and API access, which starts on Indie at €19/month. New accounts get Indie free for 14 days, with no card required.

The short version

Feedback gets better when you ask for it in the right place. Put a button in your app, tell it who the user is, ask the question you actually want answered, and make sure every submission ends up somewhere you'll act on it.

You can see how requests, bug reports, the roadmap and the changelog fit together in the live demo from the menu on pullwish.com. No sign-up needed.

AI Disclaimer: This article was drafted with AI assistance from my own notes on how I use Pullwish. I reviewed, edited and fact-checked it against the product before publishing.