Lazy Developer8 min

피드백만으로는 부족해서 설문까지 만들었다

2026년 4월 · 귀찮은개발자 EP.09

2026년 4월 · 귀찮은개발자 EP.09

FeedMission에 피드백이 쌓이기 시작하면서 두 가지가 부족해졌다. 첫째, 피드백은 사용자가 자발적으로 보내는 거라 내가 물어보고 싶은 건 물어볼 수가 없었다. “현재 만족도가 어떤가?” 같은 구조화된 질문을 던질 방법이 없었다. 둘째, 피드백이 50개가 넘어가니까 관리가 안 됐다. 어떤 건 확인했고 어떤 건 안 했는지, 같은 내용이 세 번 들어왔는데 따로 놀고 있었다.

설문 기능을 만들고, 피드백 관리를 강화했다. 상태 관리, 병합, CSV 내보내기, Slack 알림까지. 하루에 60파일 6,352줄이 추가됐다. 이 글은 그 과정의 기록이다.

빠르게 보기

– 설문(Survey) 기능: 텍스트·객관식·별점 질문 조합 → 공개 설문 페이지 → 응답 집계 + AI 요약
– 설문 빌더: 4가지 템플릿(만족도/기능 우선순위/사용자 의견/커스텀) + 만료 기간 설정
– 피드백 상태: OPEN → IN_PROGRESS → CLOSED 워크플로우
– 피드백 병합: 같은 요청을 하나로 합치고 투표 수 합산
– CSV 내보내기: 설문 응답 + Formula Injection 방어
– Slack 알림: 새 피드백 시 Incoming Webhook으로 자동 전송
– 설문은 PRO 플랜 전용 — AI 요약까지 포함

피드백과 설문은 다른 거다

피드백은 사용자가 자발적으로 보내는 거다. “다크모드 넣어주세요”, “로그인이 안 돼요” 같은 것. 사용자 주도다. 개발자는 받기만 한다.

설문은 개발자가 물어보는 거다. “현재 앱 만족도가 1~5점 중 몇 점인가?”, “다음에 추가했으면 하는 기능이 뭔가?” 같은 구조화된 질문. 개발자 주도다. 피드백은 “뭐가 불만인지” 알려주고, 설문은 “전체적으로 어떤지” 알려준다. 둘 다 필요하다.

Canny에도 설문은 없다. Typeform이나 Google Forms를 별도로 쓰면 데이터가 분산된다. 피드백과 설문이 한 곳에 있으면, “이 설문 결과에서 나온 요청이 피드백에도 많이 들어오고 있다”는 연결이 보인다.

설문 모델을 설계했다

Claude한테 요구사항을 정리해서 던졌다. “텍스트, 객관식, 별점 세 가지 질문 타입을 조합할 수 있는 설문. 공개 URL로 공유 가능. 응답 집계와 AI 요약.”

DB 모델이 4개 나왔다.

// prisma/schema.prisma — 설문 모델 4개

Survey — 설문 자체 (제목, 상태, 만료일, AI 요약)
SurveyQuestion — 질문 (TEXT / CHOICE / RATING + 순서)
SurveyResponse — 응답자 1명 = 1 Response
SurveyAnswer — 질문 1개에 대한 답변 1개

// 설문 상태
DRAFTACTIVECLOSED

// 질문 타입
TEXT — 자유 텍스트 (최대 5,000자)
CHOICE — 객관식 (선택지 배열)
RATING — 별점 1~5

설문을 DRAFT로 만들고, 준비되면 ACTIVE로 바꾸면 공개 URL이 열린다. 만료 기간을 설정하면 기간이 지났을 때 자동으로 CLOSED가 된다. 응답이 들어온 설문은 질문을 수정할 수 없게 막았다. 데이터 일관성 때문이다.

FeedMission 설문 대시보드 — 설문 목록, 상태 배지, 남은 기간 표시
설문 대시보드 — Active, Closed, Draft 상태와 액션 버튼들 / GoCodeLab

설문 빌더 — 목적부터 고른다

설문을 만들 때 빈 화면부터 시작하면 뭘 물어봐야 할지 모른다. 그래서 목적 선택을 먼저 넣었다. 4가지 템플릿이다.

설문 템플릿 4가지
만족도 조사별점 1개 + 텍스트 2개 (좋은 점, 개선할 점)기능 우선순위객관식 1개 + 별점 1개 + 텍스트 1개사용자 의견텍스트 3개 (자유 응답 중심)커스텀빈 상태에서 직접 구성

템플릿을 고르면 질문이 미리 채워진다. 거기서 수정하거나 추가하면 된다. 만료 기간도 설정할 수 있다. 제한 없음, 1일, 3일, 7일, 14일, 30일, 커스텀.

FeedMission 설문 빌더 — 별점, 객관식, 텍스트 질문 조합
설문 빌더 — 별점 + 객관식 + 텍스트 질문을 조합 / GoCodeLab

응답 검증 — 사용자가 보내는 건 믿으면 안 된다

공개 설문이니까 아무나 응답을 보낼 수 있다. 검증이 필수다.

// app/api/survey/[surveyId]/respond/route.ts

RATING: 1~5 정수만 허용
TEXT: 최대 5,000자
CHOICE: 정의된 선택지 중 하나만

// Rate Limit: IP당 분당 10회
// 만료된 설문: 자동으로 CLOSED 처리
// 필수 질문: 빈 답변이면 400

Rate Limit도 넣었다. IP당 분당 10회. 봇이 대량 응답을 보내는 걸 막기 위해서다. 만료 시간이 지난 설문에 응답이 들어오면 자동으로 CLOSED로 바꾸고 “설문이 마감되었습니다”를 반환한다.

결과 집계 + AI 요약

응답이 모이면 집계한다. 질문 타입별로 다르게 처리한다.

RATING: 평균 점수 + 1~5점별 분포 (막대 그래프)
CHOICE: 선택지별 응답 수 + 백분율 (막대 그래프)
TEXT: 처음 50개 답변 나열

여기에 Claude Haiku를 연결했다. “이 설문 결과를 요약해줘” 버튼을 누르면, 질문별 응답 데이터를 정리해서 Claude한테 보낸다. 별점 평균, 객관식 분포, 텍스트 응답 패턴까지 포함해서 한국어로 요약해준다. 요약은 survey.aiSummary 필드에 저장된다. 재생성도 가능하다.

CSV 내보내기도 넣었다. ?format=csv 파라미터로 다운로드할 수 있다. 여기서 하나 신경 쓴 게 있다. CSV Formula Injection 방어. 셀 값이 =, +, -, @로 시작하면 Excel이 수식으로 해석한다. 악의적인 응답이 CSV를 여는 사람의 컴퓨터에서 실행될 수 있다. 이런 값 앞에 '를 붙여서 텍스트로 처리하게 했다.

피드백 상태 관리 — 이제 뭘 확인했는지 안다

피드백이 50개가 넘으면 “이거 봤는지 안 봤는지”부터 헷갈린다. 상태를 넣었다.

OPENIN_PROGRESSCLOSED

// 상태 필터 + 검색
GET /api/feedback?status=OPEN&search=다크모드

// 상태 변경
PUT /api/feedback { id, status: “IN_PROGRESS” }

피드백 목록에서 상태별로 필터링할 수 있고, 제목/본문 검색도 된다. 상태를 바꾸면 API에서 유효한 값인지 검증한다. 간단한 기능인데, 있고 없고의 차이가 크다.

피드백 병합 — 같은 요청을 하나로

EP.05에서 AI 클러스터링으로 비슷한 피드백을 묶었다. 근데 “완전히 같은 요청”은 묶는 것보다 합치는 게 맞다. “다크모드 넣어주세요”가 세 번 들어왔으면, 세 개를 남기는 게 아니라 하나로 합치고 투표 수를 합산하는 거다.

// prisma/schema.prisma — 자기참조 관계
model Feedback {
  mergedIntoId  String? // 어디로 병합됐는지
  mergedInto   Feedback? // 대상 피드백
  mergedFrom   Feedback[] // 이 피드백에 병합된 것들
}

병합된 피드백은 목록에서 안 보인다. mergedIntoId: null인 것만 조회한다. 대상 피드백에는 “3개 피드백이 병합됨” 같은 카운트가 표시된다.

Slack 알림 — 피드백이 오면 바로 안다

이메일 알림은 EP.06에서 만들었다. 근데 이메일은 확인까지 시간이 걸린다. 팀에서 Slack을 쓴다면 피드백이 들어오는 즉시 채널에 올라오는 게 낫다.

// lib/slack.ts — Incoming Webhook
export async function sendSlackNotification(webhookUrl, payload)

// SSRF 방어: hooks.slack.com만 허용
const url = new URL(webhookUrl)
if (!url.hostname.endsWith(‘hooks.slack.com’)) return false

Slack Incoming Webhook URL을 프로젝트 설정에 넣으면, 새 피드백이 들어올 때마다 메시지가 온다. 제목, 타입(Feature/Bug/Other), 작성자, 대시보드 링크가 포함된다. EP.06에서 만든 이메일 발송과 같은 after() 블록 안에서 실행된다.

웹훅 URL을 사용자가 입력하니까 SSRF(Server-Side Request Forgery) 방어를 넣었다. hooks.slack.com 도메인이 아니면 요청을 보내지 않는다. 내부 네트워크로 요청이 가는 걸 막기 위해서다.

사용자한테 설문을 어떻게 보여줄지 고민했다

설문을 만들었는데 사용자한테 어떻게 보여줘야 할까. 이메일로 링크를 보내는 방법도 있고, 위젯 안에 넣는 방법도 있다. 근데 가장 자연스러운 건 이미 사용자가 오는 공개 보드에 넣는 거였다.

공개 보드(/p/[slug])에 이미 Feedback, Roadmap, Changelog 탭이 있다. 여기에 Survey 탭을 추가했다. 근데 항상 보이면 안 된다. 활성 설문이 있을 때만 탭이 나타난다. 설문이 없거나 다 마감됐으면 탭이 사라진다. 사용자 입장에서 “지금 참여할 수 있는 설문이 있구나”를 탭 존재만으로 알 수 있다.

탭 옆에 작은 초록색 점을 넣었다. 활성 설문이 있다는 시각적 표시다. “뭔가 새로운 게 있다”는 느낌을 주면서도 강압적이지 않다. 팝업이나 배너로 “설문 참여해주세요!”를 띄우면 거슬리니까.

FeedMission 공개 설문 페이지 — Survey 탭에 초록 점 표시, 별점+객관식+텍스트 폼
공개 보드에서 Survey 탭 — 활성 설문이 있을 때만 탭과 초록 점이 나타난다 / GoCodeLab

설문 URL을 직접 공유할 수도 있다. 대시보드에서 링크 복사 버튼을 누르면 /p/[slug]/survey/[surveyId] URL이 클립보드에 들어간다. 이메일이나 SNS로 보내면 된다. 서브도메인 라우팅도 넣어서 myapp.feedmission.com 형태로도 접근할 수 있다.

위젯 안에서도 설문을 받는다

공개 보드 탭으로 설문을 보여주는 건 괜찮다. 근데 사용자가 공개 보드를 방문해야 한다는 전제가 있다. EP.08에서 만든 위젯은 사용자의 앱 안에 이미 붙어 있다. 위젯을 열었을 때 “지금 참여할 수 있는 설문이 있다”를 알려주면, 별도 페이지 방문 없이 바로 응답할 수 있다.

위젯 패널 상단에 활성 설문이 있으면 작은 배너를 넣기로 했다. “설문에 참여해주세요” 한 줄과 탭하면 설문 폼이 위젯 안에서 열리는 구조다. 피드백 폼과 설문 폼이 같은 위젯 안에서 전환된다. 사용자 입장에서는 “위젯 하나에서 피드백도 보내고 설문도 응답한다”가 된다.

이 기능은 지금 작업 중이다. widget.js에 설문 상태 체크와 폼 렌더링을 추가하고, iOS/Android SDK에서도 같은 경험을 제공하려면 각 플랫폼별로 손을 봐야 한다. 완성되면 위젯 하나로 피드백 수집, 설문 응답, 공개 보드 링크까지 전부 처리할 수 있게 된다.

설문은 PRO 전용으로 했다

EP.07에서 플랜 매트릭스를 만들었다. 설문 기능은 PRO 플랜에만 넣었다. FREE와 STARTER에서는 쓸 수 없다.

이유는 비용이다. 설문 결과 AI 요약에 Claude API를 호출한다. 설문 응답이 많아지면 호출 횟수도 늘어난다. 무료 플랜에서 열어두면 비용 관리가 안 된다. EP.07에서 말한 “80%를 무료로, 핵심 유료 기능으로 전환”의 연장선이다. 피드백 수집과 관리는 모든 플랜에서 되고, 설문은 PRO에서만 된다.

하루에 6,352줄

오늘 추가된 코드를 정리하면 이렇다.

60
변경된 파일
6,352
추가된 줄
4
신규 DB 모델
12
커밋

설문 기능이 2,143줄로 가장 크고, 피드백 강화가 482줄, 팀 멤버가 387줄, 투표/구독이 362줄. EP.04에서 52분 만에 MVP를 만든 뒤로 가장 많은 코드가 하루에 추가된 날이었다.

코드 리뷰도 3회차를 돌렸다. 93/100점. 보안 취약점 수정과 코드 품질 개선이 마지막 두 커밋에 들어있다. EP.06에서 잡은 패턴(프론트엔드 노출 방지, 인증 강화)을 여기서도 적용했다.

FeedMission이 “피드백 수집 도구”에서 “피드백 + 설문 + 팀 협업 플랫폼”으로 확장되고 있다. 다음엔 뭐가 귀찮아질지 아직 모르겠다. 귀찮아지면 또 만들면 된다.

자주 묻는 질문

피드백과 설문의 차이가 뭔가요?
피드백은 사용자가 자발적으로 보내는 거다. 설문은 개발자가 구조화된 질문을 던지는 거다. 둘 다 제품 개선에 필요하다.
설문 결과를 AI가 요약해주나요?
Claude Haiku가 요약한다. 별점 평균, 객관식 분포, 텍스트 응답 패턴까지 포함해서 한국어로 정리해준다.
CSV 내보내기에서 Formula Injection이 뭔가요?
CSV 셀이 =, +, -, @로 시작하면 Excel이 수식으로 해석한다. 악의적인 응답이 실행될 수 있다. 이런 값 앞에 ‘를 붙여서 텍스트로 처리한다.
Slack 알림은 어떻게 설정하나요?
Slack Incoming Webhook URL을 프로젝트 설정에 넣으면 된다. 새 피드백마다 자동으로 채널에 메시지가 올라온다.

관련 글