지난 이야기
이전 글에 적었던 TestFlight 개선과 심사 준비를 마친 뒤 승인이 되었다. 출시 자체보다 앱스토어에서 앱 다운로드가 가능해지는, 마켓에 나온 이 단계부터 진짜인 느낌이다.(다른 차원의 어려움이 많다..) 이번 편에는 푸시 없는 로컬 앱 운영 방안 찾기, 업데이트 프로덕션 설정, Supabase 연동, 파트너스 배너 삽입 등 출시 직후부터 이어진 고민과 해결 방법을 모색했던 과정을 담아보았다.
출시 직후 — 푸시 없는 로컬 앱, 말 걸 창구가 없다
스토어 승인 알림을 받고 얼마 되지 않아 "업데이트 사항이 생길 때마다 빌드를 할 순 없는데, 사용자들에게 어떻게 알리지?"라는 질문이 떠올랐다. 개인 아카이빙 앱이기에 푸시 알림은 필수적인 기능으로 보지 않아 MVP 단계에서는 제외하고 출시를 진행했다. 로그인도 서버도 구현하지 않아 사용자가 앱을 켜야만 커뮤니케이션할 수 있고, 중요한 업데이트 소식을 바로 알릴 수 없었다. 이전에 업무할 때는 공지사항이나 배너 등 수정 시 CMS 툴에서 실시간으로 반영했었기에, 로컬 앱에서는 빌드 없이 즉각적인 변경이 어렵다는 점을 늦게 자각했다...
'그렇다면 현 구조에서 빠르게 활용할만한 채널은 무엇이 있을까?' 생각해봤다. 두 가지가 있었다. 앱 실행 시 자동으로 뜨는 업데이트 다이얼로그. 그리고 홈의 인앱 메시지 시스템. 이를 어떻게 써먹을지가 숙제였다.
푸시 없이 업데이트 알리기
upgrader 패키지 프로덕션 설정 확정
곧바로 프로덕션 설정을 조정하고 MessageProvider를 병행하는 운영 루틴을 잡기 시작했다. upgrader 패키지를 통해 앱이 실행될 때마다 애플 앱스토어 API에 자동으로 접속해서 최신 버전 정보를 가져오게 했다. 현재 버전과 앱스토어에 등록된 최신 버전을 비교해서 새로운 버전이 있으면 업데이트 안내 팝업을 띄운다. 예를 들어 내 폰에 설치된 버전이 1.0.0이고 앱스토어 최신 버전이 1.0.1이라면 사용자에게 이를 안내해준다.

앱을 알아서 최신 버전으로 설치해 주는 것은 아니다. 사용자가 팝업에서 "업데이트"를 눌러 App Store로 이동해야 실제 진행된다. iOS의 백그라운드 자동 업데이트 설정이 꺼져 있으면 이 팝업이 사실상 유일한 안내 창구다. 팝업 알림 빈도는 1일로 제한했다. 앱을 켤 때마다 팝업이 뜨게 할까도 고민했지만 사용자 입장에서는 너무 피로할 것 같았다.
안내 화면은 간결하게 구성했다. 자동으로 붙는 릴리즈 노트는 길이가 길어 핵심 메시지를 가려, 보이지 않게 처리했다. 버튼은 두 개의 선택지만 명확하게 보여주는 것이 혼란을 줄이는 방법이라고 판단했다. "업데이트" 버튼을 누르면 즉시 App Store로 이동하여 최신 버전을 설치할 수 있고, "나중에" 버튼을 누르면 다이얼로그가 닫히며 1일 후에 다시 안내받게 된다. 메시지는 빈티지 아이보리 테마의 따뜻한 톤앤매너를 유지하면서도 업데이트 필요성을 명확히 전달하도록 했다.
구버전 지원 한계: upgrader 패키지 부재 문제
업데이트 다이얼로그에도 구조적 한계는 있었다. 이전 버전에 upgrader 패키지가 없으면 자동 팝업을 띄울 수 없다는 점이다. 만약 버전 1.0.0에 upgrader가 없고, 1.0.1에 추가했다면 이전에 앱을 받은 사용자는 새 버전 출시 사실을 알 수 없다. 이 문제는 코드로 직접 해결할 수 없는 아키텍처 레벨의 제약이었다. 이후에는 공지 시스템으로 구버전 사용자도 놓치지 않게 해두었지만, 이 또한 Supabase 연동 이전 버전을 설치한 사람은 푸시가 없어 앱을 켜야만 알림을 받게 된다.
모두 새 버전부터라도 빈틈을 최소화하려는 대응 작업이었다. 로컬 앱의 경우 운영 채널이 제한적이니, 가능한 사용자의 개인 콘텐츠와 내부 루틴만으로도 돌아가는 구조가 유지 부담을 덜어준다는 교훈을 얻었다.
공지함으로 보완하는 업데이트 안내

앱 내 공지사항 시스템의 역할을 하는 '메시지함'은 홈화면의 🔔 아이콘으로, 기존에도 있었다. 실제 메시지함에는 개선 사항, 온보딩 가이드 같은 운영 메시지가 올라가 있었다. 읽지 않은 메시지가 있다면 레드닷으로 표시한다. 로컬 기반이던 메시지함은 이 시점에 Supabase를 연동해 원격 테이블에서 공지를 불러오도록 세팅했고, 실패 시 로컬 콘텐츠를 폴백으로 표시하게 했다.
중요한 건, 단순히 새 빌드 안내용으로 연동한 건 아니라는 점이다. 원격 DB인 Supabase로 메시지함을 관리하기 때문에 빌드 없이 언제든 공지를 추가할 수 있다. 푸시 알림 없이도 신규 기능 가이드, 이벤트 공지, 서비스 점검 안내 등 사용자와 소통할 최소한의 채널이 생긴 것이다.
빌드 없이 홈 콘텐츠 교체
로컬 앱에 Supabase를 도입한 이유

App Store 출시 직후 가장 답답했던 부분은 콘텐츠 수정이었다. 홈 화면에 보이는 요리 지식이나 추천 콘텐츠 섹션을 변경하려면 다음 과정을 거쳐야 했다.
로컬 JSON 파일 수정 → Flutter 프로젝트 다시 빌드 → App Store에 재제출 → 심사 대기 (1~2일) → 승인 후 배포 → 사용자 업데이트 필요최소 이틀이 걸리는 구조였다. 그래서 메시지함에서 구현한 구조를 그대로 적용했다. 이제 홈화면에서도 빌드·심사 없이 콘텐츠를 즉시 수정 가능하다.
Supabase 연동 과정: 테이블 설계와 타입 맞춤
Supabase를 붙이면서 로컬 JSON 구조와 Supabase 테이블 스키마를 일치시키는 데에 가장 주의를 기울였다. 로컬 JSON 스키마와 Supabase 테이블 스키마를 1:1로 맞춰야 동기화가 안전하게 수행되기 때문이다.
- 컬럼명: 키값인
id,title,description,category등 - 타입 매핑: JSON의 String → TEXT, 날짜 문자열 → DATE 등
- Nullable 처리: 로컬에서 Optional인 필드는 Supabase에서도 Nullable
Supabase에서 가져온 List<Map<String, dynamic>> 결과를 앱 코드에서 변환 없이 그대로 사용할 수 있었다. 원격/로컬 전환 시 추가 매핑 로직이 필요 없어 코드가 단순해졌다.
문제: 도입 직후 릴리즈 빌드에서 썸네일 전부 깨짐
Supabase를 처음 연동하고 빌드했을 때 "콘텐츠 큐레이션" 섹션 이미지가 전부 깨져 있었다. Storage 버킷 구조가 원인이었다. 처음 이미지를 업로드할 때 movies/, books/ 같은 카테고리 폴더를 만들어 정리했는데, 이 경로 부분이 URL에서 빠지며 해당 이미지를 찾지 못한 것이다.
해결: 버킷 루트로 이미지 이동 + 테이블 필드 일괄 업데이트
Supabase Storage에 접속해 해당 테이블 내 image 필드를 각기 매핑되는 스토리지 URL로 일괄 수정하니 문제가 해결되었다.
이 과정에서 Supabase 도입의 가치를 실감했다. 경로를 정리하고 테이블 필드만 변경해 몇 분 만에 복구됐다. 코드 수정이나 재빌드 없이 운영 단에서 바로 고칠 수 있었다.
캐시 TTL 문제와 운영 가이드
다만 주의사항으로, Supabase 데이터는 앱에서 1시간 동안 캐싱되기 때문에 수정 시 즉시 반영은 어렵다. 앱을 완전히 종료했다가 재실행해야 업데이트되며 이외에는 지연이 불가피함을 참고차 문서에 남겨두었다.
쿠팡 파트너스 배너: UX를 지키며 수익화 실험 기반 확보

왜 AdMob/전면 팝업/네이티브 광고 대신 쿠팡 파트너스 배너인가
- 강제 노출형 광고(전면, 팝업, 네이티브 피드 삽입 등)는 감정 기반 레시피 앱의 따뜻한 UX를 해친다.
- 빈티지 아이보리 톤에 맞춰 90×120px 미니 배너만 노출해 디자인 일관성을 유지했다.
- 홈 화면에서 "재료 필요 시 바로 구매"라는 맥락을 주며, 무작위 팝업처럼 사용자 흐름을 끊지 않는다.
- Apple App Store 심사에서 핵심 기능을 방해하는 광고는 리젝 사유가 될 수 있어 정책 리스크를 피했다.
- "UX를 지키며 수익화 실험 기반 확보"라는 전략 아래 성장에 집중하고 조용히 클릭 가능한 실험만 남겼다.
로켓 프레시를 선택한 이유
작은 수익화 실험을 고민했을 때 앞서 언급한 전면/팝업/네이티브 광고들은 모두 앱 톤과 충돌한다고 생각했다. 그래서 레시피 앱이라는 맥락을 살리면서도 UX를 지킬 수 있는 쿠팡 파트너스 로켓프레시(신선식품 배송 서비스) 다이내믹 배너를 넣어보기로 했다.
- 맥락 적합성: 레시피 앱 사용자이기에 식재료 구매로 자연스러운 랜딩 유도
- UX 침해 최소화: 광고처럼 튀지 않고 콘텐츠로 녹아들 수 있음
일반 광고 네트워크(AdMob, 애드핏, 카울리 등)는 노출·클릭 단위로 즉시 수익이 잡히지만 UX 침해 강도가 높은 전면·팝업·네이티브 광고 비중이 높다. 대신 쿠팡 파트너스는 구매 성사 시 약 3% 수수료만 지급된다. 단기 수익성은 낮지만 감정 기반 앱의 톤을 지키면서도 "재료 구매"라는 명확한 액션과 연결되어 실제 가치 있는 클릭만 남길 수 있다고 판단했다.
컬리는 SNS 인플루언서 전용
식료품 플랫폼인 '컬리'도 레시피 앱과 맥락이 맞아 어필리에이트 연동을 고려했다. 하지만 쿠팡 파트너스는 가입만으로 누구나 시작 가능한 반면, 컬리 큐레이터(컬리 제휴 마케팅)는 SNS 채널 기반 인플루언서 제휴 프로그램으로 앱 내 배너 연동은 대상이 아니었다. 게다가 유튜브 구독자 1천 명, 인스타그램/스레드/틱톡 팔로워 1천 명, 블로그 월 순방문자 1만 명 같은 최소 팔로워 요구사항도 있어 선택지가 될 수 없었다.
배너 디자인: 가이드 준수 + 배치 실험

쿠팡 파트너스는 지정된 배너 디자인을 그대로 써야 하고 색상·문구를 바꾸면 안 된다. 배너 자체는 공식 디자인을 사용하되, 앱 톤앤매너를 해치지 않으면서 정책을 지키도록 배치·사이즈를 바꿔가며 여러 번 테스트했다. 최종적으로 왼쪽에는 아이콘과 CTA 카피("새벽배송 장보기")를 두고, 오른쪽 90×120px 영역에 WebView로 다이내믹 태그를 삽입하는 구성이 가장 덜 튀었다. 배경색만 투명한 당근 컬러를 깔아 다른 섹션과 구분하면서도 앱 테마와 어울리게 했다.
WebView 네비게이션: 쿠팡 추적 유지하기
쿠팡 가이드에 맞춰 공식 다이내믹 배너를 넣었다. 클릭하면 WebView가 먼저 짧게 뜨고 이후 네비게이션 핸들러가 매 요청의 URL 스킴을 보고 분기하는 로직이다. 시작은 대부분 http(s)://로 들어오고, 리디렉션 응답에서 쿠팡 서버가 내려주는 링크가 coupang://(쿠팡 앱 딥링크)나 market://(앱스토어, 플레이스토어 링크)로 바뀔 수 있다. 설치 여부를 서버가 판단하는 건 아니고, 응답에 담긴 스킴을 그대로 OS에 넘긴다. OS는 설치된 앱이 있으면 열고, 없으면 마켓이나 웹으로 자연스럽게 넘긴다.
딥링크(Deep Link): 앱의 특정 화면으로 바로 이동시키는 URL. 앱이 설치되어 있으면 해당 화면으로 직접 랜딩하고, 없으면 웹이나 앱스토어로 폴백된다.
디퍼드 딥링크(Deferred Deep Link): 앱 미설치 상태에서 링크를 클릭했을 때, 설치 후 첫 실행 시 원래 의도한 화면으로 이동시키는 기술. 설치 전 캠페인 파라미터를 보존했다가 설치 후 복원한다. 마케팅 유입 채널 트래킹에 활용되며 Firebase/Branch/AppsFlyer 같은 SDK가 필요하다. (이건 기존에 실무하면서 "앱 설치 트래킹" 목적으로 익숙했던 용어인데, 이번 WebView 로직을 회고하면 디퍼드 딥링크의 내부 동작 방식까지 구체적으로 찾아보게 됐다.)
쿠팡 파트너스 배너는 일반 딥링크만 사용한다.
- 쿠팡 앱 전용 주소(coupang://, market://)
- 이런 스킴은 "쿠팡 앱 또는 앱 설치 유도하는 마켓을 바로 열라"는 신호라 추가 작업 없이 바로 외부 앱으로 보낸다.
- 일반 웹 주소(http/https)
- 1단계: 앱 안의 WebView에서 배너 HTML을 한 번 연다.
- 2단계: 리디렉션으로 완성된 최종 주소를 읽어 외부 브라우저·앱으로 넘긴다.
배너 스크립트가 쿠팡 서버로 요청을 보내면 서버가 리디렉션을 처리해 trackingCode=AB1234567 같은 파라미터를 주소에 덧붙인 뒤 최종 주소를 외부로 보내는 흐름이다.
사용자가 쿠팡 앱/브라우저를 다녀온 뒤에 Recipesoup 앱 배너가 깨지는 문제도 있었다. 이는 외부로 넘길 때 WebView를 닫아두고, 돌아올 때 초기 배너 HTML을 다시 로드한 뒤에야 화면에 보이게 순서를 조정했더니 안정화됐다. 제공된 배너 디자인을 유지하면서 정책을 지키고, 앱 톤앤매너를 살리기 위해 배치·사이즈만 조정해 최종 상태를 완성했다.

Disclosure 문구: 쿠팡 파트너스 정책 준수
쿠팡 파트너스 정책에 따라 제휴 문구를 명확히 표시해야 했다. 이 문구는 배너 카드 하단 중앙에 항상 노출되도록 했다. 다른 곳에 표기하거나 숨기면 정책 위반이다.
ⓘ 쿠팡 파트너스 활동으로 일정 수수료를 제공받습니다.
배너를 활성 사용 지표로 활용

로켓프레시 배너를 추가한 뒤 매일 배너 노출 수 패턴이 일정하게 유지되는 것을 데이터로 확인했다. 노출 수 외에도 클릭·구매까지 제대로 트래킹되는지도 직접 테스트해봤다. 참고로 쿠팡 파트너스 수수료는 보통 3%이고 구매 품목은 알 수 있지만, 구매자 정보는 넘어오지 않는다.
로컬 앱이라 사용자 데이터를 수집하지 않기에 데이터 측정이 어려웠는데, 매일 비슷하게 찍히는 배너 노출 수를 통해 간접적인 DAU 파악도 가능했다. (아직은 미미한 수치..ㅎㅎ)
마무리하며
이번에 배운 것은 "승인 이후부터 진짜 운영이"시작된다는 사실이다. 각 작업을 한 이유는 명확했다.
첫째, 버전 업데이트 팝업을 설정해 푸시 없이도 사용자에게 메시지를 보내도록 했다. 로컬 앱의 약점은 서버가 없어 사용자에게 직접 말을 걸 방법이 제한적이라는 점이었다. upgrader 패키지로 앱 실행 시 자동 버전 체크와 업데이트 안내를 구현해 최소한의 소통 채널을 확보했다.
둘째, Supabase 연동으로 공지·콘텐츠를 빌드 없이도 실시간 업데이트하도록 설정했다. 빌드-심사-배포로 이틀이 걸리는 구조는 운영 속도를 심각하게 저하시켰다. 필요한 섹션만 원격 우선, 로컬 폴백 구조로 전환하면서 효율적으로 변경사항을 반영하게 되었다.
셋째, 쿠팡 파트너스로 작은 수익화 실험이 시작됐다. 레시피 앱 특성에 맞는 로켓프레시 배너를 통해 UX를 최대한 해치지 않으면서도 수익 가능성을 테스트할 수 있었다. 배너 노출 수로 간접적인 DAU 지표를 추측해보는 것도 좋은 포인트였다.
출시 이후 작업들을 통해 결국 기본을 다시 떠올렸다. 앱의 가치는 제품 그 자체보다 사용자에게 달려있다. "앱 사용자 수를 늘리고 유지하기" 위해 더 노력해야겠다.
다음 에피소드 예고
이 회고 시리즈의 최종편, 마지막 Episode 09에서는 전체 앱 출시 여정을 회고하고, 운영 루틴까지 포함해 어떤 교훈과 v2.0 계획을 세웠는지 정리한다.