물어보기 전에 챙긴다
EP 01에서 페퍼가 해야 할 두 가지 중 첫 번째.
"선제적으로 챙겨준다. 내가 물어보기 전에 먼저."
Vault(EP 14)가 "시키면 한다"의 절반이었다면, 이번엔 나머지 절반 — 안 시켜도 챙기는 쪽이다.
Brief — 아침에 먼저 오는 것
Brief 탭이 아침에 보여주는 것:
- 날씨 — 오늘.
- 캘린더 — 연동된 모든 계정을 통합. primary와 secondary를 병렬로 조회하고, iCalUID로 중복 제거.
- Gmail 분석 — 이게 핵심이다. 그냥 "메일 N개"가 아니라, LLM이 필터해서 진짜 답장이 필요한 것만.
EP 01에서 이렇게 썼다. "답장 못 한 메일 중에서 실제로 내가 답장 해야 되고 액션해야 되는 것은 무엇인지." 그걸 구현한 게 Priority Senders + LLM 필터다. 중요한 발신자를 등록해두고(CRUD), 그 사람들 메일을 우선으로, LLM이 "이건 액션 필요"를 걸러낸다.
브리핑은 매번 새로 만들면 느리고 비싸다. 그래서 KST 오전 6시 스냅샷으로 캐시하고, 당겨서 새로고침하면 강제 갱신한다.
근데 탭은 열어야 보인다
여기서 진짜 "선제적"의 벽에 부딪혔다. 브리핑이 아무리 좋아도, 앱을 열어서 Brief 탭에 들어가야 보인다. 그건 챙김이 아니라 여전히 내가 가는 거다.
그래서 데일리 푸시 2종을 만들었다. 평일만.
- 아침 9시 —
☀️ 오늘의 브리핑이 도착했어요→ 누르면 Brief 탭 - 오후 2시 —
✅ 오늘의 Action Item 체크→ 누르면 Action 탭
이게 EP 01의 *"물어보는 게 아니라 챙김 받는 것"*이 실제로 매일 아침 9시에 폰에 뜨는 형태다.
스케줄러를 뭘로 돌릴까 — 이게 진짜 결정이었다
푸시 2종을 정시에 보내려면 스케줄러가 필요하다. 당연히 Vercel Cron을 쓰려고 했다. 이미 API가 Vercel에 있으니까.
막혔다. 두 가지 이유로.
- Vercel Hobby는 크론 최대 2개. 이미 drive-sync(EP 14)와 chat-cleanup으로 둘 다 썼다. 자리가 없다.
- Vercel Hobby 크론은 best-effort. 정확히 9시에 온다는 보장이 없다. 브리핑이 9시 40분에 오면 브리핑이 아니다.
그래서 Supabase pg_cron으로 갔다. 아키텍처는 이렇게 됐다:
pg_cron (Postgres 안, 정시 트리거) → pg_net으로 Vercel route를 호출 (CRON_SECRET 헤더) → route가 push_tokens 전체 조회 → Expo push 배치 발송
발송 로직 자체는 Vercel(apps/api)에 그대로 두고, pg_cron은 트리거만 당긴다. 크론 표현식은 UTC라 0 0 * * 1-5(9am KST), 0 5 * * 1-5(2pm KST). 시크릿은 Supabase Vault에서 읽어 SQL 파일에 하드코딩 안 했다.
"어디서 시간을 재느냐"가 이렇게 큰 결정일 줄 몰랐다. 비개발자한테는 "그냥 9시에 보내"가 한 줄인데, 실제로는 정시성·개수 한도·시크릿 관리가 다 엮인 아키텍처 선택이었다.
그리고 콜드스타트 딥링크 버그
푸시는 잘 갔다. 근데 눌러도 탭 이동이 안 됐다.
이상한 건, 채팅 푸시는 눌렀을 때 방으로 잘 이동했다. 브리핑·액션 푸시만 탭 이동이 안 됐다.
차이는 앱 상태였다. 채팅 푸시는 보통 앱을 쓰는 중에 오지만, 브리핑은 앱이 완전히 꺼진 상태(아침 9시)에서 온다. 즉 콜드스타트 경로만 깨져 있었다.
원인은 어이없는 단위 불일치였다.
iOS notification.date → 초 (timeIntervalSince1970) Date.now() → 밀리초
콜드스타트에서 "이 푸시가 방금 온 건가"를 판단하는 2분 recency 가드가 있는데, 초와 밀리초를 그대로 빼니 차이가 항상 2분을 초과했다. 그래서 딥링크를 항상 무시했다.
const ts = raw < 1e12 ? raw * 1000 : raw // 초면 ms로 환산
한 줄. 이걸 고치니 채팅방 콜드스타트 딥링크까지 같이 고쳐졌다.
EP 04에서 구글이 Information Agents(24시간 백그라운드 에이전트)를 발표했을 때, 내가 그날 오전에 설계한 monitoring/proactive worker 구조랑 똑같아서 "방향이 맞다"고 좋아했다. 방향은 맞았다. 근데 실제로 만들어보니 진짜 어려운 건 에이전트 개념이 아니라 — 스케줄러 정시성과, 초/밀리초 한 줄이었다.
선제적으로 챙긴다는 건, 개념이 아니라 아침 9시에 정확히 뜨는 알림이다.