트렌드 리포트 1단계 구현 가이드 — 24시간 안에 첫 푸시
Notion(저장·검수·열람) + Google Apps Script(자동 수집·LLM 분류) + Telegram 봇(푸시). 코드 한 번만 붙여넣으면 굴러가는 0원형 구성. 스마트폰 = Notion 앱 + Telegram 앱 두 개로 끝납니다. 이 단계가 안정되면 같은 콘텐츠 흐름 위에 PWA를 올릴 수 있어요.
이 단계의 목표. 콘텐츠 발행 흐름을 먼저 검증합니다. "하루 5~7건의 큐레이션을 매일 받고, 매달 1편의 리포트를 받는 일상"이 본인에게 실제로 잘 맞는지를 1~2주 안에 확인. 잘 맞으면 PWA, 안 맞으면 흐름 자체를 조정.
00
Prerequisites
준비물 (체크리스트)
Notion 계정
무료. 모바일 앱 설치 (iOS/Android).
Google 계정
Apps Script 무료 사용. Gmail/Google Drive 계정 그대로.
Telegram 계정
무료. 모바일 앱 설치.
Anthropic API Key
Claude Haiku 4.5 사용. 월 약 $5~15. console.anthropic.com
Notion Internal Integration Token
비용 요약. Claude Haiku는 매우 저렴합니다. 하루 30~50건 분류 + 월·분기 리포트 초안 생성을 합쳐도 한 달 $5~15 사이. 그 외 모두 무료.
01
Step 1 · Notion
Step 1 — Notion DB 만들기 (10분)
Notion에 새 페이지 → "Table" 데이터베이스 생성. 이름은 "Trend Inbox"로. 아래 컬럼 그대로 만들어 주세요.
| 컬럼 | 타입 | 용도 |
|---|---|---|
| Title | Title | 기사 제목 |
| URL | URL | 원문 링크 |
| Source | Select | 출처 (GWI / Vogue Korea / Elle 등) |
| Category | Select | 리트릿 / F&B / 공간 / 문화·체험 / 기타 |
| Score | Number | 중요도 1~10 (LLM 자동 점수) |
| Summary | Text | 1줄 요약 (LLM 자동) |
| Status | Select | candidate / published / archived |
| Comment | Text | 본인 한 줄 코멘트 (검수 시 추가) |
| FetchedAt | Date | 수집 일시 |
| PublishedAt | Date | 발행 일시 (있을 때) |
Notion 통합 토큰 얻기
- notion.so/my-integrations → "New integration" → 이름 "Trend Bot" → 워크스페이스 선택 → 생성.
- "Internal Integration Token" 복사 (
secret_…). 안전하게 보관. - 방금 만든 Trend Inbox 페이지 → 우측 상단 ⋯ → "Add connections" → "Trend Bot" 추가.
- DB의 URL에서
?v=…앞 32자 hex가 Database ID. 메모해 두세요.
02
Step 2 · Telegram
Step 2 — Telegram 봇 만들기 (5분)
- Telegram에서 @BotFather 검색 → /newbot → 이름 입력 (예: "Sqnc Trend Bot").
- 봇 토큰 받기 (
123456:ABC-DEF…). 보관. - 방금 만든 봇과의 채팅을 시작 (/start).
- 본인의 Chat ID 얻기: 브라우저에서
https://api.telegram.org/bot<토큰>/getUpdates열기 →"chat":{"id": 12345678}의 숫자 복사.
토큰 관리. Bot Token, Notion Token, Anthropic API Key는 절대 깃허브·메신저·문서에 평문으로 두지 마세요. Apps Script의 PropertiesService에 저장하는 게 안전합니다 (다음 단계에서 처리).
03
Step 3 · Apps Script
Step 3 — Google Apps Script 자동 수집 (30분)
- script.google.com → "New project".
- 이름: "Trend Collector".
- 좌측 ⚙ "Project Settings" → 아래 "Script Properties"에 다음 4개 추가:
NOTION_TOKEN— Notion 통합 토큰NOTION_DB_ID— Trend Inbox DB IDANTHROPIC_API_KEY— Claude API 키TELEGRAM_TOKEN·TELEGRAM_CHAT_ID— 봇 토큰·Chat ID
RSS 소스 (Code.gs 상단)
여기에 본인이 받고 싶은 매체를 넣습니다. 시작용 10개 추천.
Code.gs — RSS sources
const SOURCES = [ { name: 'GWI', url: 'https://globalwellnessinstitute.org/feed/' }, { name: 'GWS Trends', url: 'https://www.globalwellnesssummit.com/feed/' }, { name: 'Vogue Korea', url: 'https://www.vogue.co.kr/feed/' }, { name: 'Elle Korea', url: 'https://www.elle.co.kr/rss/all.xml' }, { name: 'W Korea', url: 'https://www.wkorea.com/feed/' }, { name: 'Bazaar Korea',url: 'https://www.harpersbazaar.co.kr/feed/' }, { name: 'Well+Good', url: 'https://www.wellandgood.com/feed/' }, { name: 'Goop', url: 'https://goop.com/feed/' }, { name: 'Longevity', url: 'https://longevity.technology/feed/' }, { name: '롱블랙', url: 'https://www.longblack.co/feed' }, ];
핵심 함수 — fetchAndClassify()
매일 새벽 1회 실행되어 RSS 수집 → Claude 분류 → Notion 적재까지 한 번에 처리. 길지만 한 번만 붙여넣으면 됩니다.
Code.gs — main
const P = PropertiesService.getScriptProperties(); const NOTION = { token: P.getProperty('NOTION_TOKEN'), db: P.getProperty('NOTION_DB_ID') }; const CLAUDE = { key: P.getProperty('ANTHROPIC_API_KEY'), model: 'claude-haiku-4-5-20251001' }; const TG = { token: P.getProperty('TELEGRAM_TOKEN'), chat: P.getProperty('TELEGRAM_CHAT_ID') }; function fetchAndClassify() { const seen = new Set(loadRecentUrls()); // 중복 방지 const picked = []; for (const src of SOURCES) { try { const xml = UrlFetchApp.fetch(src.url, { muteHttpExceptions: true }).getContentText(); const doc = XmlService.parse(xml); const items = doc.getRootElement().getDescendants() .map(e => e.asElement && e.asElement()).filter(Boolean) .filter(e => e.getName() === 'item' || e.getName() === 'entry'); for (const it of items.slice(0, 5)) { const title = (it.getChildText('title') || '').trim(); const link = it.getChildText('link') || (it.getChild('link')?.getAttribute('href')?.getValue() || ''); if (!title || !link || seen.has(link)) continue; picked.push({ title, url: link, source: src.name }); } } catch (e) { Logger.log('fail ' + src.name + ' ' + e); } } // Claude로 분류 + 점수 + 요약 (배치) const classified = classifyBatch(picked); for (const r of classified) addToNotion(r); Logger.log('fetched ' + classified.length); } function classifyBatch(items) { if (!items.length) return []; const prompt = `다음 기사들을 SQNC(웰니스 큐레이션) 관점에서 분류해주세요. 카테고리: 리트릿 / F&B / 공간 / 문화·체험 / 기타 score는 SQNC 독자에게 얼마나 흥미로운지 1~10. JSON 배열만 반환: [{"title":"...", "category":"리트릿", "score":7, "summary":"한 줄"}] 기사들: ` + items.map((x, i) => `${i+1}. ${x.title}`).join('\n'); const res = UrlFetchApp.fetch('https://api.anthropic.com/v1/messages', { method: 'post', contentType: 'application/json', headers: { 'x-api-key': CLAUDE.key, 'anthropic-version': '2023-06-01' }, payload: JSON.stringify({ model: CLAUDE.model, max_tokens: 2000, messages: [{ role: 'user', content: prompt }] }), muteHttpExceptions: true, }); const json = JSON.parse(res.getContentText()); const text = json.content?.[0]?.text || '[]'; const arr = JSON.parse(text.match(/\[[\s\S]*\]/)?.[0] || '[]'); return items.map((it, i) => ({ ...it, ...(arr[i] || {}) })); } function addToNotion(r) { UrlFetchApp.fetch('https://api.notion.com/v1/pages', { method: 'post', contentType: 'application/json', headers: { 'Authorization': 'Bearer ' + NOTION.token, 'Notion-Version': '2022-06-28' }, payload: JSON.stringify({ parent: { database_id: NOTION.db }, properties: { Title: { title: [{ text: { content: r.title.slice(0, 200) } }] }, URL: { url: r.url }, Source: { select: { name: r.source } }, Category: { select: { name: r.category || '기타' } }, Score: { number: r.score || 5 }, Summary: { rich_text: [{ text: { content: (r.summary || '').slice(0, 1900) } }] }, Status: { select: { name: 'candidate' } }, FetchedAt:{ date: { start: new Date().toISOString() } }, } }), muteHttpExceptions: true, }); } function loadRecentUrls() { // 최근 7일 적재된 URL 가져와 중복 방지 const res = UrlFetchApp.fetch(`https://api.notion.com/v1/databases/${NOTION.db}/query`, { method: 'post', contentType: 'application/json', headers: { 'Authorization': 'Bearer ' + NOTION.token, 'Notion-Version': '2022-06-28' }, payload: JSON.stringify({ page_size: 100, sorts: [{ property: 'FetchedAt', direction: 'descending' }] }), muteHttpExceptions: true, }); const j = JSON.parse(res.getContentText()); return (j.results || []).map(p => p.properties?.URL?.url).filter(Boolean); }
매일 다이제스트 푸시 — pushDailyDigest()
아침 8시에 점수 상위 7건만 묶어 한 메시지로 보냅니다.
Code.gs — daily digest
function pushDailyDigest() { const since = new Date(Date.now() - 24*3600*1000).toISOString(); const res = UrlFetchApp.fetch(`https://api.notion.com/v1/databases/${NOTION.db}/query`, { method: 'post', contentType: 'application/json', headers: { 'Authorization': 'Bearer ' + NOTION.token, 'Notion-Version': '2022-06-28' }, payload: JSON.stringify({ filter: { and: [ { property: 'Status', select: { equals: 'candidate' } }, { property: 'FetchedAt', date: { on_or_after: since } }, { property: 'Score', number: { greater_than_or_equal_to: 6 } }, ]}, sorts: [{ property: 'Score', direction: 'descending' }], page_size: 7, }), }); const rows = JSON.parse(res.getContentText()).results || []; if (!rows.length) return; const lines = rows.map((p, i) => { const t = p.properties?.Title?.title?.[0]?.plain_text || ''; const u = p.properties?.URL?.url || ''; const c = p.properties?.Category?.select?.name || ''; return `${i+1}. [${c}] ${t}\n${u}`; }); const text = `☀️ 오늘의 큐레이션 후보 (${rows.length})\n\n` + lines.join('\n\n'); sendTelegram(text); } function sendTelegram(text) { UrlFetchApp.fetch(`https://api.telegram.org/bot${TG.token}/sendMessage`, { method: 'post', payload: { chat_id: TG.chat, text: text, disable_web_page_preview: 'true' }, muteHttpExceptions: true, }); }
월간·분기 리포트 초안 — generateReport()
매월 1일 / 분기 첫날 자동 실행. 기간의 published 항목을 모아 Claude에게 요약·예측 초안을 부탁하고 Telegram에 전달.
Code.gs — periodic report
function generateMonthlyReport() { generateReport(30, '월간'); } function generateQuarterlyReport() { generateReport(90, '분기'); } function generateReport(days, label) { const since = new Date(Date.now() - days*24*3600*1000).toISOString(); const res = UrlFetchApp.fetch(`https://api.notion.com/v1/databases/${NOTION.db}/query`, { method: 'post', contentType: 'application/json', headers: { 'Authorization': 'Bearer ' + NOTION.token, 'Notion-Version': '2022-06-28' }, payload: JSON.stringify({ filter: { and: [ { property: 'Status', select: { equals: 'published' } }, { property: 'FetchedAt', date: { on_or_after: since } }, ]}, page_size: 100, }), }); const rows = JSON.parse(res.getContentText()).results || []; const bullets = rows.map(p => `- [${p.properties?.Category?.select?.name || ''}] ${p.properties?.Title?.title?.[0]?.plain_text || ''} — ${p.properties?.Summary?.rich_text?.[0]?.plain_text || ''}`).join('\n'); const prompt = `SQNC(한국 웰니스 큐레이션) 관점에서 지난 ${label}의 핵심 흐름을 정리하고 다음 ${label}을 예측해주세요. 지난 기간 큐레이션 목록: ${bullets} 다음 형식으로: ## 핵심 흐름 5 1. ... ## 카테고리별 변화 (리트릿/F&B/공간/문화·체험) - ... ## 다음 ${label} 예측 3 1. ...`; const r = UrlFetchApp.fetch('https://api.anthropic.com/v1/messages', { method: 'post', contentType: 'application/json', headers: { 'x-api-key': CLAUDE.key, 'anthropic-version': '2023-06-01' }, payload: JSON.stringify({ model: 'claude-sonnet-4-6', max_tokens: 3000, messages: [{ role:'user', content: prompt }] }), }); const text = JSON.parse(r.getContentText()).content?.[0]?.text || '(생성 실패)'; sendTelegram(`📊 ${label} 리포트 초안\n\n` + text); }
04
Step 4 · Triggers
Step 4 — 자동 실행 트리거 (5분)
Apps Script 좌측 시계 아이콘(Triggers) → "Add Trigger" 4개 등록.
| 함수 | 유형 | 주기 | 시간대 |
|---|---|---|---|
| fetchAndClassify | Time-driven | 매일 | 새벽 6시 |
| pushDailyDigest | Time-driven | 매일 | 오전 8시 |
| generateMonthlyReport | Time-driven | 매월 1일 | 오전 9시 |
| generateQuarterlyReport | Time-driven | 분기 첫날 | 오전 9:30 (수동 토글) |
분기 트리거. Apps Script는 "매월 1일"까지만 기본 옵션이라, 분기 첫날만 실행하려면 generateMonthlyReport 안에서
new Date().getMonth() % 3 === 0일 때만 분기 함수도 같이 호출하도록 한 줄 추가하세요.
05
Daily Routine
매일 운영 흐름 (15분)
06:00
자동 수집
Apps Script가 RSS 10곳에서 신규 글을 모아 Claude로 분류·점수·요약 후 Notion에 적재. 본인은 아무것도 안 함.
08:00
Telegram 다이제스트 도착
"오늘의 큐레이션 후보 7건" 메시지. 출퇴근길 이동 중 1차 훑기.
아무 때나
Notion 모바일에서 검수
Status를 candidate → published로 바꾸고 한 줄 코멘트. 이게 "발행". 5~7건 검수에 10~15분.
매월 1일
월간 리포트 초안 도착
Telegram에 마크다운 초안 도착. 그대로 둘지 / 다듬어 SQNC 사내에 공유할지 결정.
06
First Push
24~48시간 안에 첫 푸시까지
오늘 (Day 0) — 60~90분
01
계정·키 4개 발급 (30분)
Notion / Google / Telegram / Anthropic — 위 Prerequisites 그대로.
02
Notion DB 만들고 토큰 연결 (15분)
Step 1 그대로. 컬럼 타입 정확히 맞추기.
03
Apps Script 코드 붙여넣기 + Properties 4개 등록 (30분)
Step 3 코드 그대로. 저장 후 fetchAndClassify 1회 수동 실행해서 Notion에 행이 들어오는지 확인.
내일 (Day 1) — 30분
04
트리거 4개 등록 + 첫 다이제스트 발송
pushDailyDigest를 한 번 수동 실행해서 Telegram에 도착하는지 확인. 도착하면 1단계 완성.
05
RSS 소스 본인 취향대로 가감
처음 며칠 받아보고 자주 등장하는 출처는 강화, 노이즈는 제거.
06
스코어 임계값 조정
pushDailyDigest의
greater_than_or_equal_to: 6을 본인 푸시 양에 맞게 6~8 사이로 튜닝.
07
When to Upgrade
PWA로 갈 시점 — 신호 4가지
✓ 흐름이 굳었을 때
2~4주 매일 검수가 자연스러운 일과가 됨. 흐름이 본인에게 맞다는 검증 완료.
✓ 검색·아카이브가 답답할 때
Notion 모바일 검색이 느려 답답해짐 → 전용 PWA에서 카테고리·날짜 필터 + 풀텍스트 검색으로 해결.
✓ 회사 자산화 의사결정
"이걸 SQNC 멤버한테도 보여주자"라는 결정이 서면 → 다중 사용자·회원 발급 필요 → PWA + Supabase.
✓ 디자인 일관성 욕구
Notion 룩이 SQNC 브랜드와 안 맞아 답답할 때 → 현재 디자인 시스템 그대로 PWA에 이식.
그 시점에 다시 만들 산출물
- PWA MVP 4주 구현 계획 (화면·일정·외주 RFP).
- Notion 데이터 마이그레이션 스크립트 (모든 published 항목을 PWA DB로).
- 본인 디자인 시스템 토큰을 그대로 가져온 PWA 골격.
08
Cost · Risks
예상 비용·리스크
0원
Notion · Apps Script · Telegram
$10
월 Claude API (예상 중간값)
60분
초기 셋업 (Day 0~1 합산)
15분
하루 검수 시간
리스크 — RSS 깨짐. 일부 매체는 RSS 피드를 비공개·축약·중단합니다. 첫 1주는 매일 Logger 확인하고, 깨지는 소스는 제거하거나 Google Alerts 이메일 → Gmail 라벨 → Apps Script로 우회.
리스크 — Claude 출력 형식 깨짐. classifyBatch는 JSON 배열을 강제하지만 가끔 깨질 수 있음. 1~2주 운영하며 실패 빈도 높으면 함수 안에 try/catch + 재시도 로직 한 단계 추가.
다음 단계 제안
실제로 Day 0 절차 진행하다가 막히는 단계가 있으면 그 단계 이름만 말씀해주세요 — 해당 단계만 줌인한 보조 가이드(스크린샷 묘사 포함)를 만들어 드립니다. 모든 단계가 잘 굴러가서 2~4주 뒤 PWA 단계로 가실 때면 같은 톤으로 PWA MVP 4주 계획서를 다시 만들어 드릴게요.