콘텐츠로 이동

모닝 브리핑 — 출근 전 3분 뉴스 앱을 만든 이유와 과정

왜 만들었나

아침 출근 전, 장이 열리기 전에 알고 싶은 것은 많지 않습니다. 밤사이 뉴욕증시는 어땠는지, 환율과 금리는 움직였는지, 내가 관심 있는 분야에 큰 소식이 있었는지 정도입니다.

그런데 이걸 확인하려면 앱을 여러 개 오가야 했습니다.

  • 포털 메인에는 내가 관심 없는 기사가 더 많습니다
  • 같은 사건을 언론사마다 따로 써서, 비슷한 제목을 몇 번씩 지나쳐야 합니다
  • 제목만으로는 무슨 일인지 모르고, 본문을 열면 광고와 관련 기사 사이에서 길을 잃습니다
  • 그러다 보면 3분이 30분이 됩니다

그래서 반대로 만들어 보기로 했습니다. 내가 고른 토픽의 어제~오늘 소식을 사건 단위로 묶고, 한두 문장으로 요약해서, 아침 7시에 한 화면으로 받아 보자.

"출근 전 3분, 시장에 영향을 주는 소식부터" 다 못 읽어도 가장 중요한 것은 놓치지 않게, 더 궁금하면 언론사 원문으로 바로 이어지게.

2026-10-21에 앱스토어에 출시했고, 플레이스토어는 2026-10-23에 비공개 테스트를 제출했습니다.


무엇을 하는 앱인가

1. 토픽을 고르면

Google 계정으로 로그인하고 받고 싶은 토픽을 고릅니다. 지금 토픽은 8개입니다.

토픽 어디서 모으나
경제·시장 연합뉴스·매일경제·조선일보·동아일보·SBS 경제 RSS
시사 / 세상 이야기 연합뉴스·경향신문·조선일보·동아일보·SBS RSS
기업 공시 금융감독원 OpenDART 주요사항보고
IT·AI / 과학기술 / 건강 분야별 언론사 RSS
복지·정책 직접 운영하는 On Pocket News의 감수된 정책 기사

받을 요일(매일·평일만·주말만)도 고를 수 있습니다.

2. 아침 7시에 알림이 오고

푸시 알림에는 "경제·시장 5 · 시사 4 — 오늘의 브리핑"처럼 내 토픽별 건수가 적혀 있고, 누르면 오늘의 브리핑이 열립니다.

오늘의 브리핑 화면 — 실제 앱 화면(앱에 내장된 2026-10-07 샘플 브리핑)

맨 위 "오늘의 핵심 3"을 먼저 읽고, 아래 토픽 섹션은 훑어봅니다. 요약 속 숫자는 굵게 표시됩니다.

3. 한 항목은 세 줄로 끝납니다

  • 제목: 언론사 원래 제목
  • 요약: 제목에 없는 핵심(누가, 무엇을, 왜, 수치)을 1~2문장, 120자 이내로
  • 출처·시각: 모든 항목에 언론사와 원문 링크가 붙어 있습니다. 누르면 언론사 원문 페이지가 열립니다

화면을 만들며 일부러 뺀 것도 있습니다. 주가 등락을 ▲▼나 빨강·파랑으로 표시하지 않았습니다. 색과 화살표는 투자 판단처럼 읽히기 때문입니다. 요약 문장에서도 "호재·악재", 주가 전망 같은 표현을 쓰지 않게 했습니다.

읽은 항목은 흐리게 바뀌고 진행률이 올라갑니다. 관심 없는 소식은 "덜 보기"로 치우고, 다시 볼 기사는 스크랩해 날짜별로 모아 둡니다.

스크랩 화면 — 실제 앱 화면(앱에 내장된 샘플 데이터)


어떻게 만들었나

구조 — 서버 없이, 배치와 Firestore로

구성 기술 역할
배치 Kotlin + Spring Boot + Spring Batch 매일 06:00 수집 → 사건 묶기 → 순위 → 요약 → 검수 → 게시
알림 같은 배치의 별도 Job 매일 07:00 사용자별 푸시(FCM)
저장소 Firestore 날짜별 브리핑, 사용자 관심사·기기 토큰
앱 Flutter (iOS·Android) Firestore를 직접 읽고, 보안 규칙으로 권한 제어
요약 Claude 토픽 단위 요약과 검수

처음 설계에는 사용자 요청을 받는 API 서버가 있었습니다. 그런데 사용자가 열 명 남짓인 앱에 상시 서버와 클라우드 DB를 두면 고정비가 들고, 서버를 집 노트북에서 돌리면 밖에 있는 휴대폰이 접속할 수 없습니다. 푸시 알림에 브리핑 전체를 실어 보내기에는 용량 한도(4KB)가 작습니다.

그래서 무거운 일은 하루 한 번 도는 배치가 하고, 사용자에게 보이는 데이터는 Firestore 한 곳에 둔다로 바꿨습니다. 앱은 서버를 거치지 않고 Firestore를 직접 읽습니다. 고정비는 사실상 0원이고, 비용은 요약에 쓰는 LLM 사용량뿐입니다.

하루치 브리핑이 만들어지는 과정

토픽 하나는 매일 아침 이런 순서를 거칩니다.

  1. 수집: 토픽의 RSS를 모읍니다. 기본은 최근 24시간입니다
  2. 사건 묶기: 언론사마다 따로 쓴 같은 사건을 제목 유사도로 하나로 묶습니다
  3. 순위: 최신성, 몇 곳이 다뤘는지, 시장 관련 키워드를 점수로 매겨 상위 15개만 남깁니다
  4. 요약: Claude가 상위 사건을 한두 문장으로 요약합니다
  5. 검수: 먼저 코드가 형식(글자 수, 개수, 존재하는 사건인지)을 보고, 통과하면 검수 역할의 Claude가 내용을 채점합니다
  6. 게시: Firestore에 그날 브리핑으로 올립니다

실제 기록 하나를 보면, 2026-10-07 경제·시장 토픽은 기사 397건을 모아 사건 336개로 묶었고, 그중 상위 15개를 요약해 5줄을 게시했습니다. 7단계, 한 번에 검수를 통과했습니다.

지킨 원칙 네 가지

① LLM은 토픽 단위에서만 부른다

요약은 사용자마다 따로 만들지 않습니다. 토픽별로 하루 한 번 만들고 모든 사용자가 같은 요약을 읽습니다. 누가 어떤 토픽을 보고 어떤 순서로 보여 줄지는 앱이 코드로 계산합니다. 그래서 사용자가 열 명이든 천 명이든 LLM 비용은 같습니다.

② 모든 반복에는 상한이 있다

AI에게 일을 맡길 때 가장 무서운 것은 끝나지 않는 반복입니다. 이 배치에는 반복이 두 군데 있습니다.

  • 커버리지 루프: 사건이 너무 적으면 수집 기간을 36시간, 48시간으로 넓혀 다시 모읍니다. 최대 2번
  • 품질 루프: 검수에서 떨어지면 지적 사항을 넣어 다시 요약합니다. 최대 2번

두 번 다 실패하면 요약을 포기하고 대표 기사의 제목과 설명을 그대로 보여 줍니다(대체 경로). 여기에 전체 실행이 20단계를 넘으면 강제로 실패 처리하는 안전장치와 토큰 예산이 한 겹 더 있습니다. 요약이 없는 날은 있어도, 브리핑이 안 오는 날은 없게 하려는 설계입니다.

③ AI의 "다 됐다"를 믿지 않는다

요약을 쓴 AI가 스스로 잘 썼다고 판정하게 두지 않았습니다. 검수는 별도 역할이 하고, 기준은 네 가지입니다.

  • 요약의 모든 사실이 원래 기사에 근거하는가 (없는 수치·인과관계를 지어내지 않았나)
  • "충격", "드디어" 같은 감정 표현이나 한쪽으로 치우친 단어가 없는가
  • 경제·공시의 투자 판단 표현, 건강의 치료 권고 표현이 없는가
  • 낚시성·가십성 사건을 고르지 않았는가

그리고 코드로 확인할 수 있는 것(글자 수, 개수, 형식)은 AI에게 묻기 전에 코드가 먼저 걸러 냅니다. 싸고 확실한 검사부터 하는 것입니다.

④ 기사 본문은 저장하지 않는다

원문은 요약을 만드는 순간에만 읽고 어디에도 남기지 않습니다. 저장하는 것은 제목, 짧은 설명, 출처, 링크, 발행 시각뿐입니다. 원문을 가져올 때는 사이트의 robots.txt를 지키고, 막혀 있으면 제목과 설명만으로 요약합니다.

같은 이유로 소스도 골랐습니다. 처음에는 포털 뉴스 검색 API를 쓰려 했지만, 약관이 AI 입력과 가공 노출을 금지하고 있어 뺐습니다. Google News RSS는 원문 주소 대신 중간 주소를 줘서 "항상 원문 링크를 함께"라는 원칙을 지킬 수 없어 뺐습니다. 지금은 언론사가 직접 제공하는 RSS를 구독합니다.

💡 원스의 인사이트: 루프를 설계한다는 것

이 앱의 요약 파이프라인은 "AI에게 시키고, 결과를 검증하고, 틀리면 이유를 알려 주고 다시 시키고, 그래도 안 되면 정해진 대안으로 끝낸다"의 반복입니다. 만들면서 가장 시간을 많이 쓴 곳은 요약 프롬프트가 아니라 언제 끝낼지, 무엇을 보고 틀렸다고 판정할지였습니다. AI가 똑똑해질수록 일을 잘 시키는 기술보다 결과를 거부할 장치를 잘 만드는 기술이 더 중요해진다고 느꼈습니다.


2주 동안 바뀐 결정들

계획대로 간 것보다 중간에 바꾼 것이 더 많았습니다.

날짜 바꾼 것 이유
2026-10-05 시작: 작업 명세, 개발 규칙, 도메인 모델 최종 장면(아침 7시 푸시 → 탭 → 브리핑)에서 거꾸로 단계를 나눔
2026-10-06~07 백엔드 Java → Kotlin 같은 동작을 Kotlin 관용구(data class, nullable 타입)로 옮기는 방식으로 진행
2026-10-07 앱 Android 네이티브 → Flutter 테스터 중에 아이폰 사용자가 있어 iOS·Android 동시 지원
2026-10-07 포털 검색 API·Google News 제외, 언론사 RSS 직접 구독 약관과 원문 링크 원칙
2026-10-08 API 서버 → 배치 + Firestore 고정비, 집 밖 접속, 푸시 용량 한도
2026-10-12 배치를 Spring Batch Job으로 토픽 하나가 실패하면 그 토픽부터 다시 시작, 같은 날짜 중복 실행 방지
2026-10-13 매일 06:00 자동 실행 시작 여러 프로젝트의 배치를 한곳에서 돌리는 개인 배치 허브에 등록
2026-10-16 복지·정책 토픽, 푸시 알림, 지난 브리핑 On Pocket News의 감수된 기사를 피드로 받아 연동
2026-10-16~18 아이폰 테스트 빌드(TestFlight), 앱 아이콘, 스크랩, Apple 로그인 구현 출시 준비(앱스토어는 Google 로그인만 있는 앱에 Apple 로그인을 함께 요구)
2026-10-21 앱스토어 출시(0.1.0) 첫 업데이트(0.1.1)로 지난 브리핑 뒤로 가기·주제 칩·30일 보관 추가
2026-10-23 플레이스토어 비공개 테스트 제출 패키지 dev.wonslab.morningbrief, 업로드 키 서명

AI와 함께 만든다는 것

이 앱도 Claude Code와 함께 만들고 있습니다. 이번에는 시작할 때부터 프로젝트 규칙 파일(CLAUDE.md)에 개발 루프를 적어 두었습니다.

  • 작업 하나마다 계획 → 구현 → 검증(빌드·테스트를 실제로 실행) → 실패하면 원인을 한 줄 기록하고 다시
  • 같은 작업에서 5번 고쳐도 안 되면 멈추고 보고
  • 테스트를 통과시키려고 테스트를 약화·삭제하지 않기
  • 단계가 끝날 때마다 멈추고 승인받기

앱의 요약 파이프라인에 넣은 원칙(검증, 상한, 멈추고 보고)을 개발 과정에도 똑같이 적용한 셈입니다. 기록이 남으니 막혔던 지점도 분명하게 보였습니다. 예를 들어 아이폰에서 원문 읽기 화면이 15초 동안 멈추는 문제는, 광고 차단 규칙에 쓴 정규식 문법(|)을 iOS가 지원하지 않아 차단 규칙 자체가 만들어지지 못한 것이 원인이었습니다. 도메인별 규칙으로 나눠 해결했고, 이 원인 한 줄이 진행 기록에 그대로 남아 있습니다.


앞으로

  • 스토어 출시: 개인정보처리방침 게시, 뉴스 이용 약관·라이선스 검토를 마친 뒤 앱스토어·플레이스토어에 올릴 예정입니다
  • 클라우드 이전: 지금은 집 노트북이 새벽에 배치를 돌립니다. 안정되면 같은 프로그램을 클라우드의 예약 실행으로 옮기고, 요약도 API 방식으로 바꿉니다
  • 개인화: "덜 보기"와 클릭 기록으로 토픽 안의 순서를 사용자별로 조정합니다. 이 계산도 LLM 없이 코드로 합니다
  • 받는 시각 선택: 지금은 07:00 고정입니다. 시각을 고를 수 있게 할지 검토하고 있습니다

아침 3분이 30분이 되지 않게, 꼭 필요한 만큼만 전하는 앱으로 다듬어 가겠습니다.

참고 자료 및 출처