앱스토어 리젝 대응기 — 거절 메일을 받고 한 일
Your app has been rejected. 그 한 줄에 처음엔 심장이 내려앉았다. 세 번의 리젝을 거치며 거절 메일이 어떻게 체크리스트가 됐는지, 사유별 대응과 리뷰어와의 소통법을 그대로 적은 회고.
Your app has been rejected.
그 한 줄에 처음엔 심장이 내려앉았다. App Store Connect에 빨간 점이 뜨고, 메일 제목에 rejected가 박혀 있던 그날 밤, 나는 무엇을 잘못했는지도 모른 채 노트북을 덮었다 다시 열었다.
GentleDo의 첫 심사는 2일 만에 통과했다. 그래서 더 방심했다. 출시 이후 업데이트를 올리면서 나는 세 번 리젝을 맞았다. 그리고 이상하게도, 세 번째쯤 되니 그 메일이 더는 무섭지 않았다. 심장이 내려앉던 한 줄이, 어느새 체크리스트가 됐다.
오늘은 그 세 번의 거절과, 각각에 내가 한 일을 그대로 적는다.
어떤 사유로 리젝됐나 — 흔한 케이스 3개
세 번의 리젝은 신기하게도 전부 다른 가이드라인 조항이었다. Apple의 App Store Review Guidelines를 그제서야 처음부터 끝까지 읽었다. 그동안 나는 코드만 봤지, 이 문서를 본 적이 없었다.
| 회차 | 리젝 사유 (Guideline) | 한 줄 원인 | 내가 한 대응 |
|---|---|---|---|
| 1차 | 2.3.3 — 메타데이터 부정확 | 스크린샷이 구버전 UI라 실제 화면과 불일치 | 스크린샷 5장 전부 최신 빌드로 재촬영·교체 |
| 2차 | 5.1.1 — 권한 사유 불충분 | 알림 권한 요청 텍스트가 너무 일반적 | NSUserNotification 설명을 기능 맥락으로 다시 작성 |
| 3차 | 2.1 — 기능 동작 미확인 | 리뷰어 기기에서 특정 화면이 빈 채로 떴다 | 빈 상태(empty state) 처리 + 재현 영상 첨부 |
표로 적고 나니 허무할 만큼 사소한 것들이다. 세 번 다 "기능이 나쁘다"가 아니라 "설명이 부족하다" 쪽이었다. 심사는 내 앱의 품질을 보는 게 아니라, 내가 얼마나 정직하고 명확하게 설명했는가를 본다는 걸 그제야 알았다.
각 대응 — 메타데이터·권한 설명·가이드라인
1차, 메타데이터. 가장 쉬웠다. 출시 후 UI를 한 번 갈아엎었는데 스크린샷을 깜빡했다. 리뷰어 입장에선 스토어에 올라온 그림과 실제 앱이 다른 것이고, 그건 곧 사용자 기만이다. 최신 빌드로 5장을 다시 찍어 올렸다. 30분 걸렸다.
2차, 권한 설명. 여기서 진짜 배웠다. 나는 알림 권한 요청 문구를 "알림을 받으시겠습니까?" 수준으로 막연하게 적어뒀다. 리뷰어는 "이 권한이 왜 필요한지 사용자가 알 수 없다"고 했다. 그래서 Info.plist의 usage description을 *"태스크 시작·마감 시간에 맞춰 알림을 보내기 위해 사용합니다"*처럼 기능 맥락으로 다시 썼다. 권한은 받는 게 목적이 아니라, 왜 받는지를 설명하는 게 목적이었다.
3차, 기능 동작. 가장 까다로웠다. 내 기기·시뮬레이터에선 멀쩡한데 리뷰어 기기에서만 특정 화면이 비어 보였다. Flutter 패키지 버전 하나로 크래시를 겪었던 때처럼, 내 환경에서 재현 안 되는 버그가 제일 무섭다. 데이터가 없을 때의 empty state를 제대로 그려넣고, Resolution Center에 재현 영상을 첨부해 "이렇게 동작합니다"를 보여줬다.

리뷰어와 소통하는 법
세 번을 겪으며 가장 크게 바뀐 건 리뷰어를 적으로 보지 않게 된 것이다.
처음엔 리젝 메일을 받으면 억울했다. "왜 이걸 문제 삼지?" 싶었다. 그런데 Resolution Center에서 또박또박 답을 쓰다 보니 알았다. 리뷰어는 내 앱을 처음 보는 사용자다. 그가 막힌 지점은 곧 진짜 사용자가 막힐 지점이다.
그래서 답변 방식을 바꿨다. 변명하지 않고, 무엇을 어떻게 고쳤는지만 적었다. "지적해주신 X 화면은 데이터가 없을 때 빈 상태로 보였습니다. empty state를 추가했고, 재현 영상을 첨부합니다." 짧고 사실만. 이렇게 쓰니 다음 심사가 더 빨리 통과됐다.
다음 제출에서 미리 막은 것
세 번의 시체가 쌓이니 제출 전 셀프 체크리스트가 생겼다. 코드를 다 짜고 나면, 빌드를 올리기 전에 이걸 본다.
- 스크린샷이 지금 빌드의 화면과 같은가
- 모든 권한 요청 문구가 왜 필요한지를 한 문장으로 말하는가
- 데이터가 0개일 때 모든 화면이 빈 채로 안 깨지는가
- 계정·로그인이 필요하면 리뷰어용 테스트 계정을 메모에 적었는가
- 리뷰 노트에 핵심 기능을 한 줄로 설명했는가
6개 프로젝트를 말아먹으며 배운 것과 똑같은 구조다. 실패는 체크리스트로 번역될 때만 자산이 된다. 그냥 아픈 채로 두면 다음에 또 같은 곳에서 넘어진다.
다음 계획
다음 업데이트는 cross-day 태스크 지원이다. 기능이 커진 만큼 심사도 한 번에 통과하긴 어려울 거라 생각한다. 다만 이번엔 리젝을 전제로 제출한다. 통과하면 좋고, 막히면 위 체크리스트에 한 줄을 더한다.
리젝은 실패가 아니라 아직 설명이 부족하다는 신호다. 처음 그 한 줄에 심장이 내려앉았던 나에게, 지금의 나는 이렇게 말해주고 싶다. "그건 네 앱을 거절한 게 아니라, 조금 더 친절해지라는 메모였다."
이 글은 GentleDo 빌더 저널의 한 편이다. 위에 링크한 첫 심사 통과기·Claude Code 개발기·부업 1년 실패기와 함께 읽으면 같은 길의 앞뒤가 보인다.
관련 글
1인 빌더의 하루 — GentleLab·AIGrit·babipanote 3개를 운영하는 법
GentleLab 시리즈(앱 3개)·AIGrit 블로그·babipanote 허브 3-브랜드를 동시에 운영하는 1인 빌더의 실제 하루 루틴. 05:30~23:00 시간 블록, 제품별 주간 시간 배분, 도구 스택 22개, 번아웃 관리 규칙까지 공개.
1인 빌더 분기 회고 — 3개월간 무엇이 바뀌었나
1인 빌더의 분기 회고. 분기 초 목표 대비 실제, 잘된 것 3가지·안 된 것 3가지, 이번 분기에 버리기로 한 것, 다음 분기 1순위까지 솔직하게 기록한 저널.
첫 유료 사용자 10명 — 다운로드는 허영 지표였다
다운로드 수는 허영 지표였다. 무료 사용자는 쌓이는데 결제가 0이던 시기에 무엇을 바꿨고, 처음 결제 버튼을 누른 10명이 누구였는지 — 첫 매출의 순간을 솔직하게 기록한 글.