웹사이트나 앱용 피드백 폼5분 분량
셀 수 있고, 순위를 매기고, 답할 수 있는 기능 요청 모으기
해결책 대신 문제를 묻고, 요청을 테마별로 저장하고, 행이 아닌 사람 수를 세고, 출시되면 이메일을 보내는 기능 요청 폼입니다.
원하는 기능이 아니라 무엇을 하려는지, 지금은 어떻게 하는지를 물으세요. 출시되면 알릴 수 있도록 이메일은 필수로 받으세요. 각 요청을 웹훅으로 테이블에 저장하고, 요청에 테마를 붙이고, 막혀 있는 서로 다른 사람의 수로 테마의 순위를 매기세요. 테마가 출시되면 요청한 모두에게 이메일을 보내세요.
기능 요청이 다른 점
버그 리포트는 고치면 끝납니다. 기능 요청은 데이터 포인트입니다. 같은 빈틈을 저마다 다른 말로 설명하는 여러 사람 중 한 명입니다. 여기서 세 가지가 따라 나옵니다.
- 사람들은 해결책을 말합니다. "CSV 버튼을 추가해 주세요"는 "회계사가 지난달 인보이스를 필요로 해요"라는 뜻일 수 있습니다. 하려는 일을 물으면 요청받은 것보다 나은 해결책을 고를 수 있습니다.
- 같은 요구가 여러 번 들어옵니다. Ikiform은 모든 POST를 저장하고 반복을 합치지 않으며, 두 사람이 같은 말을 쓰는 경우도 드뭅니다. 요청은 직접 테마별로 묶고 서로 다른 사람 수를 세세요.
- 가치는 후속 연락에 있습니다. "요청하신 기능을 만들었습니다"라고 알려 주는 것이 애초에 물어보는 가장 좋은 이유입니다. 그러려면 모든 요청에 이메일이 있어야 하므로 이 폼은 이메일을 필수로 받습니다.
- 누군가 하려는 일을 설명하고, 영역과 영향도를 고르고, 이메일을 남깁니다.
- 웹훅 릴레이가 요청을 데이터베이스에 제출 ID당 한 번 삽입합니다.
- 매주 새 요청에 직접, 또는 패턴으로 테마를 붙입니다.
- 기존 테마에 맞으면 그 테마를 원하는 사람이 한 명 늘어납니다.
- 맞지 않으면 새 테마가 시작됩니다.
- 테마가 출시되면 그 테마의 모든 이메일을 골라 메시지 하나를 보냅니다.
문제를 묻기
미리보기
Ikiform의 검증기를 브라우저에서 실행합니다. 아무것도 전송되지 않습니다.
problem은 최소 30자라서 "다크 모드 좀요"가 이유가 담긴 문장으로 바뀝니다.| 필드 | 필요한 이유 |
|---|---|
problem | 본인의 말로 쓴 하려는 일. 단연 가장 유용한 필드 |
workaround | 얼마나 불편한지 알려 줍니다. "스프레드시트에 손으로 복사해요"는 강한 신호입니다 |
area | 대략적인 1차 분류. 전체가 아니라 결제 안에서 태그를 붙일 수 있습니다 |
impact | 막힌 고객과 궁금한 고객을 구분합니다. 사람들이 판단할 수 있는 결과로 표현했습니다 |
email | 필수. 없으면 결과를 알리거나 질문할 수 없습니다 |
우선순위나 투표 필드는 빼세요. 누구나 자기 요청을 높은 우선순위로 표시하며, 테마별 사람 수가 어떤 버튼보다 나은 투표입니다.
폼
이 폼은 스크립트가 필요 없습니다. POST하고 리디렉션됩니다. Redirect after submit을 다음에 무슨 일이 일어나는지 알려 주는 페이지로 설정하세요. 프레임워크에서 페이지에 머무는 버전이 필요하면 필러의 폼 코드를 가져와 이 필드들로 바꾸세요.
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
<label for="problem">What are you trying to do?</label>
<textarea id="problem" name="problem" rows="5" required minlength="30"
placeholder="Send last month's invoices to our accountant in one go"></textarea>
<label for="workaround">How do you do it today?</label>
<textarea id="workaround" name="workaround" rows="3"></textarea>
<label for="area">Which part of the product?</label>
<select id="area" name="area" required>
<option value="billing">Billing and invoices</option>
<option value="reports">Reports</option>
<option value="team">Team and permissions</option>
<option value="integrations">Integrations and API</option>
<option value="other">Something else</option>
</select>
<fieldset>
<legend>How much does it matter?</legend>
<label><input type="radio" name="impact" value="blocking" required /> It stops us using the product for something</label>
<label><input type="radio" name="impact" value="slow" /> It costs us time every week</label>
<label><input type="radio" name="impact" value="nice" /> It would be nice</label>
</fieldset>
<label for="email">Email</label>
<input id="email" name="email" type="email" required />
<input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
<button>Send request</button>
</form>웹훅으로 요청 저장하기
처음 수십 건까지는 대시보드와 CSV 내보내기로 충분합니다. 매주 테마를 붙이고 사람 수를 세기 시작하면 테이블이 더 편합니다. Postgres에 테이블을 만드세요.
create table feature_requests (
submission_id text primary key,
area text not null,
impact text not null,
problem text not null,
workaround text,
email text not null,
theme text,
created_at timestamptz not null
);그런 다음 Ikiform에서 이 라우트를 가리키는 웹훅 엔드포인트를 추가하세요. pg 패키지만 있으면 어느 Node 호스트에서나 동작하며, 자체 서버에서 같은 패턴을 더 자세히 다룹니다.
import { verifyWebhook, WebhookVerificationError } from "@ikiform/sdk/webhooks";
import { Pool } from "pg";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const INSERT = `insert into feature_requests (submission_id, area, impact, problem, workaround, email, created_at)
values ($1, $2, $3, $4, $5, lower($6), $7)
on conflict (submission_id) do nothing`;
export async function POST(request: Request) {
const payload = await request.text();
let event;
try {
event = await verifyWebhook({
payload,
headers: request.headers,
secret: process.env.IKIFORM_WEBHOOK_SECRET!,
});
} catch (error) {
if (error instanceof WebhookVerificationError) return new Response(error.reason, { status: 400 });
throw error;
}
if (event.type !== "submission.created") return new Response(null, { status: 204 });
const { problem, workaround, area, impact, email } = event.data.fields;
await pool.query(INSERT, [
event.data.submission_id,
area,
impact,
problem,
workaround ?? null,
email,
event.data.created_at,
]);
return new Response(null, { status: 204 });
}verifyWebhook은 엔드포인트의whsec_시크릿으로 서명되지 않은 요청을 모두 거부합니다.on conflict (submission_id) do nothing덕분에 재시도해도 안전합니다. Ikiform은 실패한 전달을 같은 제출로 다시 보내고, 두 번째 insert는 아무 일도 하지 않습니다.- 데이터베이스 오류가 나면 예외가 발생하고 라우트가
500으로 응답하며, Ikiform이 나중에 재시도합니다. 데이터베이스가 재시작되는 동안에도 요청을 잃지 않습니다. - 이메일은 들어올 때 소문자로 바꾸므로 서로 다른 사람 수를 셀 수 있습니다.
태그와 순위
새 요청에 태그 붙이기
일주일에 한 번 태그 없는 행을 읽고 각각에 테마를 붙이세요. 뻔한 것은 패턴으로 처리하고 나머지는 직접 읽으세요. 흥미로운 요청은 패턴에 맞지 않습니다.
update feature_requests
set theme = 'invoice-export'
where theme is null
and area = 'billing'
and problem ilike any (array['%export%', '%accountant%', '%download%']);테마 순위 매기기
아래 쿼리는 막혔다고 답한 요청 수, 그다음 요청한 서로 다른 사람 수로 테마의 순위를 매깁니다.
select coalesce(theme, 'untagged') as theme,
count(*) as requests,
count(distinct email) as people,
count(*) filter (where impact = 'blocking') as blocking
from feature_requests
group by 1
order by blocking desc, people desc;- 무엇이든 설계하기 전에 상위 세 테마의
problem텍스트를 전부 읽으세요. - 우회 방법을 보세요. 작은 변경 하나로 가장 큰 불편이 사라지는 경우가 많습니다.
- 순위는 계획과 견주어 판단하세요. 순위는 수요를 보여 줄 뿐, 무엇을 만드는 게 맞는지 알려 주지 않습니다.
결과 알리기
테마가 출시되면 그것을 요청한 모든 사람을 가져오세요.
select distinct email
from feature_requests
where theme = 'invoice-export';이메일 도구나 자신의 받은편지함에서 무엇이 출시되었는지 말하고 링크를 담은 짧은 이메일 한 통을 보내세요. Ikiform은 폼을 작성한 사람에게 이메일을 보내지 않습니다. 알림 이메일은 나와 팀에게만 가므로 자동 응답, 확인 이메일, 더블 옵트인이 없습니다. 그런 이메일은 웹훅으로 트리거해 이메일 도구에서 보내세요. 여기서도 마찬가지로 출시 이메일은 Ikiform이 아니라 내가 보냅니다.
작동 확인하기
- 10자짜리 문제로 요청을 제출하세요. Ikiform이
problem을 지목하는422로 응답합니다. - 실제 요청을 제출하세요. Submissions와
feature_requests의 행으로 나타납니다. - Webhooks에서 전달을 다시 보내세요. 테이블에는 그 제출의 행이 여전히 하나뿐입니다.
- 같은 이메일을 대소문자만 바꿔 두 번 제출하세요. 순위 쿼리는 한 명으로 셉니다.
주의할 점
- Canny 같은 공개 피드백 보드는 투표와 공개 상태 페이지를 더합니다. 고객끼리 서로의 요청을 보길 원한다면 그쪽이 더 나은 도구이고, 이 구성은 요청을 비공개로 유지합니다.
- 링크가 여러 개 담긴 요청은 스팸으로 점수가 매겨질 수 있습니다. 양이 적은 동안에는 매주 Spam 필터를 확인하세요.
- 나중에
area옵션을 추가하면 먼저 정의에 추가하세요. 목록에 없는 값은 거부됩니다. - 필요한 것만 저장하세요. 이메일이 담긴 요청 테이블은 개인정보입니다. 개인정보 처리방침에 밝히고 더 이상 필요 없는 행은 삭제하세요.
문제 해결
| 문제 | 해결 방법 |
|---|---|
| 테이블에 행이 없음 | Webhooks에서 엔드포인트를 여세요. 400은 시크릿이 틀렸다는 뜻이고, 데이터베이스 오류와 함께 500이면 insert가 실패해 Ikiform이 재시도 중이라는 뜻입니다. |
422 validation_failed | POST가 폼의 필드 정의와 맞지 않습니다. error.fields에 각 필드와 이유가 나옵니다. 입력의 name을 필드 ID와 맞추세요. |
| 제출은 대시보드에 있는데 이메일이나 웹훅이 오지 않았습니다 | 스팸으로 저장되었습니다. 스팸은 아무것도 보내지 않습니다. Spam 필터에서 열어 이유를 확인하고, 스팸이 아님으로 표시하면 이메일과 웹훅이 발송됩니다. |
질문
무엇을 하려는지, 지금은 어떻게 하는지, 제품의 어느 부분과 관련되는지, 얼마나 중요한지, 그리고 이메일입니다. 기능이 아니라 문제를 물으면 더 나은 해결책을 찾을 여지가 생깁니다.
중복은 당연히 생긴다고 보고 행이 아니라 사람을 세세요. 각 요청에 테마를 붙이고 서로 다른 이메일 수로 테마의 순위를 매기세요. Ikiform은 모든 제출을 저장하고 반복을 합치지 않습니다.
폼만으로는 안 됩니다. 폼은 요청을 비공개로 유지하고, Canny 같은 공개 보드가 투표를 더합니다. 테마별로 서로 다른 사람 수를 세면 비공개로 같은 효과를 얻습니다.
각 요청과 함께 이메일을 보관하고, 출시된 테마의 모든 이메일을 골라 이메일 도구에서 메시지 하나를 보내세요. Ikiform이 제출자에게 직접 이메일을 보내지는 않습니다.
막히는 단계가 있거나 내 상황에 맞는 가이드가 필요하다면 이메일을 보내세요: hello@ikiform.com.