음성봇을 스텁부터 만든 이유 — 테스트로 설계를 먼저 굳히다

시리즈 · 음성봇 직접 만들기 — 실시간 음성봇의 뼈대를 바닥부터 만든 8편 기록입니다. 1. 끼어들기(barge-in) — 재생 중인 TTS를 실시간으로 끊는 법 2. "말이 끝났다"는 판단 — VAD 엔드포인터 3. 엔진을 코드 수정 없이 바꾸는 인터페이스 설계 4. 로컬 LLM 대화 두뇌와 도구 호출 루프 5. 매 턴 지연시간을 재는 이유 6. 브라우저 마이크에서 서버까지 — 오디오 프로토콜 7. 로컬 Whisper STT 연결하기 8. 스텁부터 만든 이유 — 테스트 우선 설계 (완결) — 지금 보는 글 돌아보며 시작하는 마지막 편 이 시리즈를 시작할 때부터 정해 둔 규칙이 하나 있었습니다. 진짜 음성 인식 모델과 진짜 합성 모델이 준비되기 전까지는, 그 둘을 흉내만 내는 가짜 엔진으로 전체 구조를 먼저 돌려본다는 규칙입니다. 여덟 편을 거치며 끼어들기 처리, 상태 흐름, 프로토콜, 엔진 인터페이스를 하나씩 다뤘는데, 그 모든 것이 사실 이 가짜 엔진 위에서 검증됐습니다. 지금 코드를 다시 열어 보면 그때는 별생각 없이 세운 규칙이 결과적으로 시리즈 전체의 진행 속도를 결정했다는 게 눈에 들어옵니다. 이번 편에서는 왜 굳이 가짜부터 만들었는지, 그리고 그 선택이 시리즈 전체에 어떤 영향을 남겼는지 정리하면서 이 시리즈를 마무리하려 합니다. 실제 모델을 기다리지 않기로 한 이유 음성봇의 핵심 로직은 사실 음성 인식이나 음성 합성 자체가 아닙니다. 사용자가 말을 시작했는지 끝냈는지 판단하는 상태머신, 합성 중에 끼어들기가 들어왔을 때 스트림을 멈추는 동시성 처리, 이벤트가 순서대로 오가는 흐름 제어 같은 것들이 훨씬 까다롭습니다. 반면 실제 인식·합성 모델은 무겁고 설치가 까다롭고 실행 속도도 느립니다. 이 두 가지를 한꺼번에 붙잡고 있으면 어려운 동시성 버그를 실제 모델의 로딩 시간과 뒤섞어 디버깅하게 됩니다. 모델이 느려서 반응이 늦은 건지, 상태머신이 잘못 짜여서 늦은 건지 구분이 안 되는 상황을 미...

반려 한 건을 더 넘고서야 승인이 났다 — 개인 앱, 마지막 구간의 기록

제출했고, 또 한 번 걸렸고, 그리고 이번엔 넘었습니다. 지난 편 끝에서 남겨뒀던 두 가지 가능성 — 바로 승인이거나 다시 걸리거나 — 중 결국 후자로 흘러갔고, 거기서 한 걸음 더 나아가 승인까지 받아냈습니다.

반려 사유가 한 건으로 줄었다

사업자등록과 리브랜딩, 구독 가격 결정을 끝내고 빌드를 올렸습니다. 이번 심사에서는 이전에 걸렸던 항목들 — 아이콘, 구독 설정, 샌드박스 에러, 계정 삭제 기능 — 이 전부 통과했습니다. 반려는 딱 한 건이었습니다. 결제 화면 표기 문제였습니다.

연간 구독 카드에 "월 환산가"를 함께 보여주고 있었는데, 이 월 환산가가 실제 청구되는 연간 금액보다 화면에서 더 크게 강조돼 있었습니다. 사용자가 얼마를 실제로 결제하는지 헷갈릴 수 있다는 게 반려 사유였습니다. 기능이나 정책 위반이 아니라 화면 하나의 시각적 위계 문제였습니다.

숫자 하나를 고치는 데도 이유가 필요했다

고치는 자체는 어렵지 않았습니다. 연간 결제 카드에서 실제 청구액을 크게, 월 환산가는 작게 보조 표기로 내렸습니다. 버튼 문구도 "연 단위 청구 금액"이 먼저 보이도록 바꿨습니다.

다만 이게 왜 반려 사유가 되는지 이해하고 고치는 것과, 그냥 화면만 바꿔서 다시 올리는 것은 달랐습니다. 심사 가이드라인은 "사용자가 실제로 얼마를 내는지 오해하게 만들면 안 된다"는 원칙이고, 월 환산가 자체를 보여주는 건 문제가 아니었습니다. 어떤 숫자가 시각적으로 더 크게, 더 먼저 보이느냐가 문제였습니다. 이 구분을 명확히 하고 나서야 다른 화면(월간 구독 카드 등)에 같은 패턴이 남아있지 않은지도 같이 점검할 수 있었습니다.

재제출하고 이틀을 기다렸다

수정한 빌드를 다시 올렸습니다. 이번엔 이전 반려 이력에 대한 대응 메모도 같이 남겼습니다. 무엇을 어떻게 고쳤는지, 심사관이 다시 확인할 때 바로 알아볼 수 있게 정리한 것입니다.

첫 반려가 있었던 날로부터 이번 재제출까지, 그 사이에 사업자등록과 리브랜딩과 가격 결정이 전부 끼어 있었습니다. 심사 자체는 며칠이지만, 심사를 받을 수 있는 상태를 만드는 데 걸린 시간이 훨씬 길었습니다. 이번 재제출은 그 중 가장 짧은 대기였습니다.

승인 메일을 받았다

이틀 뒤 상태가 "심사 중"에서 "배포 준비 완료"로 바뀌었습니다. 판매 국가 175개국에 대한 활성화 처리가 이어졌고, 이 처리가 끝나야 실제로 스토어에서 검색되고 다운로드할 수 있는 상태가 됩니다.

돌아보면 첫 반려부터 이번 승인까지 여러 겹이 있었습니다. 기능적인 반려(아이콘, 구독 설정, 샌드박스, 계정 삭제)를 넘고, 사업자등록이라는 행정 절차를 넘고, 마지막으로 결제 화면 표기라는 세부 사항을 넘었습니다. 코드를 고치는 시간보다 그 사이사이 기다리고 확인하고 다시 준비하는 시간이 훨씬 길었다는 게, 이번에 가장 크게 체감한 부분입니다. Voice To Notion 개발기·앱 출시기 시리즈의 후속편으로 남기는 실제 개발·마케팅 기록이며, 서비스명과 식별 정보는 다른 편들과 마찬가지로 바꿔서 적었습니다.

자주 묻는 질문

Q. 반려당했을 때 가장 먼저 확인해야 할 게 뭔가요? 반려 사유가 기능 문제인지 표기·정책 문제인지부터 구분하는 게 우선이었습니다. 이번 건처럼 화면 표기 문제는 코드 수정 범위가 작더라도, 가이드라인이 요구하는 "원칙"을 이해하지 못하면 같은 문제가 다른 화면에 남아있을 수 있습니다. 리젝 메일에 적힌 가이드라인 조항 번호를 찾아서 원문을 직접 읽어보는 걸 권합니다.

Q. 재제출하면 처음부터 다시 심사받나요? 반려된 항목 위주로 다시 보되, 전체 빌드가 다시 심사 대상이 되는 건 맞습니다. 그래서 재제출 전에 이전에 이미 통과했던 부분(아이콘, 구독 설정 등)까지 실수로 건드리지 않았는지 확인하는 게 중요했습니다.

{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "반려 한 건을 더 넘고서야 승인이 났다 — 개인 앱, 마지막 구간의 기록",
  "description": "사업자등록과 리브랜딩, 가격 결정을 끝내고 제출한 뒤에도 반려가 한 번 더 있었습니다. 결제 화면 표기 문제 하나를 고치고 다시 제출한 끝에 받은 승인까지의 기록.",
  "datePublished": "2026-07-22",
  "author": {"@type": "Person", "name": "AI 코더 랩 운영자"},
  "articleSection": "Indie App Development",
  "keywords": "앱스토어 승인, 앱스토어 반려 사유, 구독 결제화면 표기, 앱스토어 재제출, 1인 개발자 앱 출시"
}

댓글

이 블로그의 인기 게시물

디스코드 구글캘린더 연동 봇 만들기: 카톡 캡처 OCR 자동 등록 시스템 구축

클로드 코드 비용 줄이는 법 — ollama 로컬 LLM 연동 (M3 맥북)

유튜브 노션 자동 저장 봇 만들기 — Claude CLI MCP + Discord 233일 운영기