A feedback channel is easy to launch: add an email address, enable GitHub Issues, or link a form.

The difficult part starts after the first message arrives.

Without an explicit workflow, maintainers end up checking several disconnected inboxes. Reporters do not know where to ask questions, private messages cannot be searched by other contributors, and issues sit in an ambiguous state until everyone assumes somebody else handled them.

The solution is not another notification destination. It is a small routing and triage system with one visible entry point, clear ownership, and a limited set of states.

This tutorial builds that system around GitHub, but the same model works with GitLab, a ticket tracker, or a shared support queue.

Start with a routing contract

Before configuring tools, decide where each kind of message belongs.

A practical routing table might look like this:

Message type Destination Visibility
Reproducible bug GitHub Issue Public
Feature proposal GitHub Issue or Discussion Public
Usage question GitHub Discussion Public
Security vulnerability Security policy instructions Private
Account, billing, or personal data Contact channel Private
General feedback that can become actionable Contact channel, then promoted to an issue with permission Private first

This separation matters. Public channels make reusable knowledge searchable, while private channels provide an escape hatch for information that should not be posted in an issue.

Document the contract in SUPPORT.md:

# Support

## Bugs and feature requests

Open a GitHub issue. Search existing issues first and include a minimal reproduction when reporting a bug.

## Questions

Use GitHub Discussions for setup help and open-ended questions.

## Private messages

Use our private contact page for account details, personal information, or feedback you do not want to post publicly.

## Security vulnerabilities

Do not open a public issue. Follow the instructions in SECURITY.md.

## Review cadence

New issues are reviewed on Tuesdays and Fridays. This is a review target, not a guaranteed resolution time.

Enter fullscreen mode Exit fullscreen mode

Link this file from the README so contributors do not need to discover it by browsing the repository.

## Help and feedback

- [Report a bug](https://github.com/OWNER/REPOSITORY/issues/new/choose)
- [Ask a question](https://github.com/OWNER/REPOSITORY/discussions)
- [Send a private message](https://example.com/contact)
- [Read the support policy](SUPPORT.md)
- [Report a security issue](SECURITY.md)

Enter fullscreen mode Exit fullscreen mode

Replace every placeholder and test the links while signed out. A route that only works for maintainers is not a usable entry point.

Turn the issue picker into the front door

GitHub's issue template configuration can direct people to the correct destination before they create an issue.

Create .github/ISSUE_TEMPLATE/config.yml:

blank_issues_enabled: false
contact_links:
  - name: Ask a question
    url: https://github.com/OWNER/REPOSITORY/discussions
    about: Get help with setup, configuration, and usage.
  - name: Send a private message
    url: https://example.com/contact
    about: Share account details, personal information, or private feedback.
  - name: Report a security vulnerability
    url: https://github.com/OWNER/REPOSITORY/security/policy
    about: Follow the private security reporting instructions.

Enter fullscreen mode Exit fullscreen mode

Disabling blank issues is useful only when the provided templates cover the common paths. Otherwise, it turns structure into a barrier.

Next, add .github/ISSUE_TEMPLATE/bug.yml:

name: Bug report
description: Report a reproducible problem
title: '[Bug]: '
labels:
  - 'status: needs-triage'
body:
  - type: markdown
    attributes:
      value: |
        Thanks for reporting a problem. Remove secrets and personal data before submitting.

  - type: textarea
    id: description
    attributes:
      label: What happened?
      description: Describe the actual and expected behavior.
    validations:
      required: true

  - type: textarea
    id: reproduction
    attributes:
      label: Minimal reproduction
      description: Provide steps, a small repository, or a code sample.
    validations:
      required: true

  - type: input
    id: version
    attributes:
      label: Version
      placeholder: 'v2.4.1'
    validations:
      required: true

  - type: textarea
    id: environment
    attributes:
      label: Environment
      description: Include the operating system, runtime, browser, and relevant dependencies.

  - type: checkboxes
    id: checks
    attributes:
      label: Submission checks
      options:
        - label: I searched existing issues.
          required: true
        - label: I removed secrets and personal data.
          required: true

Enter fullscreen mode Exit fullscreen mode

The form asks for evidence needed to reproduce a bug, but it does not ask reporters to diagnose the cause. Diagnosis remains the maintainer's job.

Use a deliberately small state machine

Labels should answer what happens next, not describe every possible attribute of an issue.

Start with these states:

  • status: needs-triage — nobody has made a routing decision yet.
  • status: needs-info — the reporter must provide something specific.
  • status: accepted — the problem or proposal is valid, but it may not be scheduled.
  • status: duplicate — another issue is the canonical thread.
  • status: out-of-scope — the request does not fit the project's boundaries.

Every open issue should have exactly one status label. Technology, component, and priority labels can be added separately, but they should not replace the workflow state.

A triage pass then becomes a repeatable checklist:

  1. Open the status: needs-triage queue.
  2. Remove secrets or personal information immediately if any were posted.
  3. Check for duplicates.
  4. Confirm that the report belongs in this repository.
  5. Attempt reproduction when appropriate.
  6. Apply one status label.
  7. Write the next action in a comment.
  8. Assign an owner only when somebody has actually accepted responsibility.

For example, a useful needs-info response is specific:

Thanks for the report. We need the smallest configuration that reproduces this and the exact runtime version. Please add those details within 14 days; otherwise, we will close the issue and reopen it when the information is available.

Enter fullscreen mode Exit fullscreen mode

That is more actionable than simply writing “need more details.”

Choose a cadence instead of pretending to offer instant support

A support workflow needs a named owner and a review schedule. It does not necessarily need continuous monitoring.

For a small project, two scheduled triage passes per week may be enough. Put the current maintainer or rotation in a private team document, and publish only the review cadence in SUPPORT.md.

During each pass, inspect at least:

is:issue is:open label:"status: needs-triage"
is:issue is:open label:"status: needs-info" updated:<2026-07-10
is:issue is:open no:assignee label:"status: accepted"

Enter fullscreen mode Exit fullscreen mode

Replace the hard-coded date in the second query with the cutoff for your response window. Saved GitHub searches or project views can make these queues easier to revisit.

Measure the health of the workflow rather than the volume of messages:

  • Age of the oldest untriaged item
  • Number of items without a workflow state
  • Number of accepted issues without an owner
  • Private messages waiting for a routing decision

These are operational signals, not performance targets. They tell you where the process has stopped moving.

Keep the private channel from becoming a second backlog

A private channel should be an intake route, not the permanent system of record for ordinary product work.

Apply this rule to every private message:

  1. Reply privately if it contains account details, personal data, or another sensitive subject.
  2. Redirect to an existing public thread if the answer is already documented.
  3. Promote it to a public issue if it identifies generally useful work and the sender agrees.
  4. Close it explicitly if no action will be taken.

When promoting feedback, rewrite it rather than copying the original message. Remove identifying details and describe the observable problem:

## Problem
Users configuring the client without a default region receive an unclear error.

## Expected outcome
The configuration validator should identify the missing field and point to the relevant documentation.

## Source
Private feedback, shared publicly with permission and anonymized.

Enter fullscreen mode Exit fullscreen mode

The public issue becomes the canonical thread. Future updates belong there rather than being split between an inbox and GitHub.

Common failure modes

Every route says “contact us”

This sends bugs, questions, security reports, and private details into the same queue. Show users a routing choice before they compose a message.

Automation creates the appearance of support

An instant bot response does not mean a human has reviewed the report. If you automate acknowledgements, state the actual review cadence and avoid implying that work has been scheduled.

Labels accumulate without changing decisions

If an issue has 12 descriptive labels but no next action, the taxonomy is not helping. Require one workflow state and keep optional labels limited.

Private feedback never returns to the project

Useful reports can disappear inside direct messages. Add “promote, redirect, reply, or close” to every private-channel triage pass.

The maintainer silently becomes unavailable

A shared inbox still fails if everyone assumes someone else is watching it. Assign a current owner or rotation, even if the team has only two people.

Security reports use the general contact route

Keep vulnerability instructions in SECURITY.md and enable GitHub's private vulnerability reporting when it fits the project. Do not ask reporters to send exploit details through an ordinary public form.

A hosted contact-page implementation

If you do not want to build and operate the private intake page, Knocket is one implementation example: create a shareable contact page, place its URL in the README and contact_links configuration above, and keep GitHub as the canonical location for public work. Visitors do not need an account to start a conversation; messages can also be routed to Telegram, where a quoted reply can be delivered back to the visitor.

The workflow rules remain the same regardless of the contact-page provider: appoint an owner, review the queue on a schedule, protect sensitive information, and promote suitable feedback into public issues with permission.

A maintenance checklist

Once a month, verify the system itself:

  • Open every README and issue-picker link in a private browser window.
  • Confirm that SUPPORT.md describes the current review cadence.
  • Check that the security route is distinct from general contact.
  • Review untriaged and unassigned queues.
  • Remove obsolete labels and templates.
  • Sample private conversations to confirm each received a routing decision.
  • Update the named internal owner or rotation.

A good feedback channel is not defined by how many ways people can reach you. It is defined by whether every message reaches a decision.

Discussion

How does your project separate public issues from private feedback? Do you use a maintainer rotation, a scheduled triage session, or another mechanism to prevent reports from becoming an abandoned inbox?


Disclosure: I work on Knocket, so treat it as one implementation example rather than a neutral recommendation.