음성봇을 스텁부터 만든 이유 — 테스트로 설계를 먼저 굳히다

시리즈 · 음성봇 직접 만들기 — 실시간 음성봇의 뼈대를 바닥부터 만든 8편 기록입니다. 1. 끼어들기(barge-in) — 재생 중인 TTS를 실시간으로 끊는 법 2. "말이 끝났다"는 판단 — VAD 엔드포인터 3. 엔진을 코드 수정 없이 바꾸는 인터페이스 설계 4. 로컬 LLM 대화 두뇌와 도구 호출 루프 5. 매 턴 지연시간을 재는 이유 6. 브라우저 마이크에서 서버까지 — 오디오 프로토콜 7. 로컬 Whisper STT 연결하기 8. 스텁부터 만든 이유 — 테스트 우선 설계 (완결) — 지금 보는 글 돌아보며 시작하는 마지막 편 이 시리즈를 시작할 때부터 정해 둔 규칙이 하나 있었습니다. 진짜 음성 인식 모델과 진짜 합성 모델이 준비되기 전까지는, 그 둘을 흉내만 내는 가짜 엔진으로 전체 구조를 먼저 돌려본다는 규칙입니다. 여덟 편을 거치며 끼어들기 처리, 상태 흐름, 프로토콜, 엔진 인터페이스를 하나씩 다뤘는데, 그 모든 것이 사실 이 가짜 엔진 위에서 검증됐습니다. 지금 코드를 다시 열어 보면 그때는 별생각 없이 세운 규칙이 결과적으로 시리즈 전체의 진행 속도를 결정했다는 게 눈에 들어옵니다. 이번 편에서는 왜 굳이 가짜부터 만들었는지, 그리고 그 선택이 시리즈 전체에 어떤 영향을 남겼는지 정리하면서 이 시리즈를 마무리하려 합니다. 실제 모델을 기다리지 않기로 한 이유 음성봇의 핵심 로직은 사실 음성 인식이나 음성 합성 자체가 아닙니다. 사용자가 말을 시작했는지 끝냈는지 판단하는 상태머신, 합성 중에 끼어들기가 들어왔을 때 스트림을 멈추는 동시성 처리, 이벤트가 순서대로 오가는 흐름 제어 같은 것들이 훨씬 까다롭습니다. 반면 실제 인식·합성 모델은 무겁고 설치가 까다롭고 실행 속도도 느립니다. 이 두 가지를 한꺼번에 붙잡고 있으면 어려운 동시성 버그를 실제 모델의 로딩 시간과 뒤섞어 디버깅하게 됩니다. 모델이 느려서 반응이 늦은 건지, 상태머신이 잘못 짜여서 늦은 건지 구분이 안 되는 상황을 미...

AI 뉴스레터 직접 만들기 — 매일 관심 분야 뉴스만 자동 요약 메일로 (무료)

매일 아침 받은편지함에 도착하는 나만의 AI 뉴스 브리핑 메일
매일 아침 받은편지함에 도착하는 나만의 AI 뉴스 브리핑 (사용자 본인 실제 화면)

ai 뉴스레터 만들기는 자동화에 처음 발을 들이는 사람에게 가장 좋은 첫 프로젝트입니다. 식상할 만큼 흔한 주제지만, 그래서 더 확실합니다. 누구에게나 "매일 정보는 챙겨야 하는데 시간이 없다"는 페인이 있고, 그것을 해결하는 구조가 단순해서 따라 하기도 쉽기 때문입니다. 이 글에서는 매일 아침 내 관심 분야의 뉴스만 골라 메일 한 통으로 받아보는 파이프라인을, 서버도 비용도 없이 만드는 방법을 정리합니다.

먼저 짚고 넘어갈 것이 있습니다. 우리는 "뉴스레터를 하나 더 구독"하려는 게 아닙니다. 오히려 구독을 늘리지 않고도 정보를 챙기는 방법을 만들려는 것입니다. 왜 그게 필요한지부터 보겠습니다.

1. 왜 "나만의 뉴스레터"가 필요한가 — 구독을 늘릴수록 더 못 읽는다

뉴스레터를 25개 구독해도 정작 안 읽는 이유

마이크로소프트의 2025 워크 트렌드 인덱스(Work Trend Index)에 따르면 평균적인 사람은 25개 이상의 뉴스레터를 구독하고, 직장인은 하루 117통의 이메일을 받습니다. 그 결과 메일 정리 서비스 클린이메일(Clean Email)의 조사에서는 41%가 "구독 피로(subscription fatigue)"를 호소했고, 별도 설문에서는 70%가 최근 3개월 안에 메시지 과다를 이유로 최소 3개 이상의 구독을 해지한 것으로 나타났습니다. 문제의 핵심은 흥미롭습니다. 받은편지함을 채우는 주범은 스팸이 아니라, 과거의 내가 직접 동의해서 구독한 신뢰할 만한 뉴스레터들이라는 점입니다. 내 선의의 구독이 쌓여서 나를 압도하는 구조입니다.

압도되면서도 정보는 부족한 역설

더 아이러니한 건, 정보가 넘치는데도 정작 필요한 건 못 챙긴다는 점입니다. 오픈텍스트(OpenText)의 2022년 조사에서 직장인의 80%가 정보 과부하를 겪고 있다고 답했고(2020년 60%에서 증가), 로이터 저널리즘연구소(Reuters Institute)의 디지털 뉴스 리포트에 따르면 전 세계적으로 40%의 사람이 뉴스를 의도적으로 회피한다고 답했습니다. 이는 2017년 29%에서 크게 늘어난, 역대 가장 높은 수치입니다. 뉴스레터 피로는 단순한 귀찮음이 아니라 "읽지 않은 메일에 대한 죄책감, 중요한 걸 놓칠까 하는 불안(FOMO), 그러면서도 정작 압도되어 아무것도 못 보는 역설"이 겹친 상태입니다.

남이 만든 큐레이션은 내 관심사와 다르다

기존 뉴스레터의 더 근본적인 한계는, 편집자가 "대중"을 위해 고른 내용이라는 점입니다. 내가 보고 싶은 건 'A 분야의 어제 핵심 3가지'인데, 받는 건 광고 반·일반론 반입니다. 국내 사용자들도 "넘쳐나는 정보의 옥석을 가리는 피로감"을 뉴스를 멀리하는 가장 큰 이유로 꼽고, 뉴스 앱은 알림만 확인하고 콘텐츠 자체는 잘 안 본다고 합니다.

바꿀 수 없는 건 내용만이 아닙니다. 형식도 고정돼 있습니다. 누구는 배경과 출처까지 자세히 보고 싶고, 누구는 한두 문장으로 핵심만 원합니다. 표로 받고 싶은 사람도, 불릿 한 줄씩 받고 싶은 사람도 있습니다. 하지만 남이 만든 뉴스레터에서는 분량도 출력 방식도 내가 손댈 수 없습니다. 결국 내용도 형식도, 나에게 꼭 맞는 형태는 내가 직접 만들 때만 나옵니다.

그래서 발상을 뒤집습니다. 남의 큐레이션을 구독하지 말고, 내 관심사만 매일 자동으로 큐레이션하는 AI 에이전트를 직접 하나 만든다. 받은편지함에는 단 한 통, 내가 정한 주제의 핵심만. 이것이 "나만의 뉴스레터"입니다.

2. 전체 그림 — 무료 스택으로 만드는 뉴스 파이프라인

구조는 의외로 단순합니다. 네 개의 구성 요소가 순서대로 일합니다.


[매일 아침 정해진 시각]  클라우드가 자동 기동
        |
        v
[1. 수집]  웹검색 API로 내 관심 주제 검색 (분야별 여러 갈래)
        |
        v
[2. 걸러내기]  어제 본 기사는 제외 (중복 제거, 선택)
        |
        v
[3. 요약]  AI가 진짜 중요한 것만 골라 한 줄 요약 + 출처
        |
        v
[4. 발송]  요약을 메일 한 통으로 받은편지함에 전달

구성 요소별로 무료 도구를 하나씩 매칭하면 이렇게 됩니다.

역할 도구 비고
자동 실행기 Cloudflare Workers + Cron 무료, 내 컴퓨터가 꺼져 있어도 매일 실행
뉴스 수집 Tavily(AI 웹검색 API) 무료 월 1,000회 검색
AI 요약 Cloudflare Workers AI 키 불필요, 같은 플랫폼 내 호출
메일 발송 Make.com 웹훅 → Gmail 발신 도메인 인증 불필요

핵심 설계 원칙은 "구성 요소 최소화"입니다. 외부 의존을 최대한 줄이고, 한 부분이 실패해도 나머지는 동작하도록 만드는 것이 매일 안정적으로 도착하는 비결입니다.

Cloudflare Workers → Tavily → Workers AI → Make.com → Gmail로 이어지는 뉴스레터 파이프라인
수집에서 발송까지 이어지는 전체 파이프라인 (사용자 본인 실제 구성)

3. 단계별 구현

3.1 무엇을 추적할지 정하기 (가장 중요)

코드보다 먼저 정해야 할 것은 "내가 매일 무엇을 알고 싶은가"입니다. 이 AI 에이전트의 품질은 80%가 여기서 결정됩니다. 저는 관심 주제를 4개 섹션으로 나누고, 각 섹션마다 검색 쿼리를 하나씩 붙였습니다. 이렇게 설정값을 한 파일에 모아두면, 나중에 관심사가 바뀌어도 이 부분만 고치면 됩니다.


// config.js — 이 AI 에이전트의 운전대. 추적 주제가 바뀌면 여기만 고친다.
export const SECTIONS = [
  { key: "models",   title: "모델·연구",   query: "new LLM model release research breakthrough" },
  { key: "tools",    title: "제품·개발툴", query: "AI developer tools agent SDK product launch" },
  { key: "business", title: "기업·투자",   query: "AI company funding acquisition announcement" },
  { key: "applied",  title: "응용·규제",   query: "machine learning enterprise adoption AI policy" },
];

각 섹션이 곧 독립된 검색 한 갈래입니다. 한 섹션 검색이 실패해도 나머지 섹션은 그대로 발송되도록 분리해 두는 것이 포인트입니다.

3.2 뉴스 수집 — 웹검색 API

뉴스는 Tavily 같은 AI 웹검색 API로 모읍니다. 일반 검색과 달리 "최근 며칠 이내"로 범위를 좁힐 수 있어서, 매일 신선한 결과만 가져오기에 적합합니다. 섹션별로 쿼리를 돌려 제목·요약 스니펫·원문 URL을 받아옵니다.


async function searchOne(section, apiKey) {
  const res = await fetch("https://api.tavily.com/search", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      api_key: apiKey, query: section.query,
      topic: "news", days: 2, max_results: 5,
    }),
  });
  const data = await res.json();
  return (data.results ?? []).map(r => ({ title: r.title, url: r.url, snippet: r.content }));
}

핵심은 두 개의 옵션입니다. days: 2는 "최근 이틀 안에 나온 기사만" 가져오라는 뜻이라, 매일 돌려도 어제오늘 신선한 결과만 잡힙니다. max_results: 5는 섹션당 5건까지만 받겠다는 상한선으로, 뒤에서 AI가 추려낼 후보를 적당히 좁혀 두는 역할입니다. 반환값은 제목·원문 URL·요약 스니펫 세 가지로만 정리해, 다음 요약 단계가 다루기 쉬운 형태로 넘깁니다.

Tavily 대시보드에서 발급한 무료 웹검색 API 키
Tavily 대시보드에서 발급한 무료 API 키 (사용자 본인 실제 화면)

3.3 AI 요약 — 핵심만 골라내기

수집한 기사를 그대로 보내면 그건 또 다른 정보 과부하입니다. 그래서 AI에게 "진짜 중요한 것만 2~3건 골라, 한 줄 요약과 왜 중요한지를 붙여라. 건질 게 없으면 없다고 말하라"고 지시합니다. 억지로 칸을 채우지 않게 하는 것이 노이즈를 막는 핵심입니다.


const SYSTEM = `너는 매일 분야 동향을 추적하는 분석가다.
- 섹션마다 진짜 중요한 것만 2~3건 선별한다. 광고·중복·단순 리캡은 버린다.
- 건당 요약(한 줄)과 왜 중요한지(반 줄)를 쓴다.
- 건질 게 없는 섹션은 비워 둔다. 억지로 채우지 않는다.
- 반드시 정해진 JSON 형식으로만 답한다.`;

1번에서 말한 "형식까지 내 손에 들어온다"가 바로 이 지시문에서 실현됩니다. 이 한 곳만 고치면 "요약은 두 문장까지만, 비판적 시각은 생략"이나 "결과를 표로 정리" 같은 식으로 분량과 출력 형식을 그때그때 바꿀 수 있습니다. 시중 뉴스레터에서는 불가능한, 직접 만들 때만 얻는 자유입니다.

저는 요약 엔진으로 Cloudflare Workers AI(llama-3.3-70b)를 썼습니다. 키 발급도 필요 없고 같은 플랫폼 안에서 호출되어 빠릅니다. 다만 여기까지 오는 데 시행착오가 있었는데, 그 이야기는 4번에서 따로 다루겠습니다.

3.4 메일로 보내기 — 발송은 위임

메일 발송은 직접 구현하지 않고 Make.com 웹훅에 넘기는 방식이 가장 쉽습니다. Make에서 "웹훅 수신 → Gmail로 보내기" 시나리오를 만들어 두면, 우리 AI 에이전트는 요약 결과(받는사람·제목·본문)를 웹훅 주소로 한 번 던지기만 하면 됩니다. 발신 도메인 인증 같은 골치 아픈 과정이 사라지고, 내 Gmail 계정으로 바로 받을 수 있습니다.


await fetch(MAKE_WEBHOOK_URL, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ to: MY_EMAIL, subject: `오늘의 브리핑 ${date}`, html }),
});

보다시피 우리 코드가 하는 일은 "받는사람·제목·본문(HTML) 세 가지를 JSON 한 덩어리로 묶어 웹훅 주소에 던지는 것"이 전부입니다. 실제 Gmail 인증·발송은 Make가 대신 처리하므로, 메일 서버를 직접 다룰 필요가 없습니다. 덕분에 자동화에서 가장 자주 막히는 "발신 인증" 단계를 통째로 건너뜁니다.

Make.com 웹훅 수신에서 Gmail 발송으로 이어지는 시나리오
Make.com의 웹훅 수신에서 Gmail 발송으로 이어지는 시나리오 (사용자 본인 실제 화면)

3.5 매일 자동 실행 — 클라우드에 올리기

마지막으로 이 전체를 Cloudflare에 배포하면, 내 컴퓨터가 꺼져 있어도 매일 정해진 시각에 자동으로 돕니다. 설정 파일에 실행 시각만 정해주면 됩니다.


# wrangler.toml
[triggers]
crons = ["0 22 * * *"]   # UTC 22:00 = 한국시간 07:00

npx wrangler deploy 한 번이면 끝입니다. 이제 매일 아침 7시, 받은편지함에 나만의 뉴스레터가 도착합니다.

4. 자주 겪는 문제 — 실제로 부딪힌 시행착오

4.1 무료 AI의 함정 — "무료"가 다 같은 무료가 아니다

처음엔 Google Gemini 무료 API로 요약을 붙였습니다. 그런데 테스트를 몇 번 돌리자마자 429 quota exceeded가 떴습니다. 확인해 보니 그 모델의 무료 한도가 하루 20회였습니다. 실제 운영은 하루 1회라 충분하지만, 디버깅하며 여러 번 호출하면 금세 소진됩니다.

다음으로 Groq(무료 한도가 넉넉하고 빠름)로 바꿨습니다. 로컬에서 테스트하면 잘 됐는데, 클라우드에 올리니 403 Forbidden이 떴습니다. 원인은 Groq가 Cloudflare Workers의 데이터센터 IP를 차단하는 것이었습니다. 내 컴퓨터에서는 되고 클라우드에서만 막히는, 찾기 어려운 함정입니다.

결국 정답은 Cloudflare Workers AI였습니다. 같은 플랫폼 안에서 호출되니 IP 차단이 있을 수 없고, 키 발급도 필요 없습니다. 교훈은 명확합니다. "무료"라는 단어만 보지 말고, 일일 한도와 호출 환경(어디서 부르는지)을 먼저 확인해야 합니다.

4.2 AI가 JSON을 깔끔하게 안 준다

AI에게 "JSON으로만 답하라"고 해도, 종종 JSON 앞뒤에 "네, 여기 결과입니다" 같은 설명을 붙입니다. 그러면 결과를 코드로 처리하다 파싱 에러가 납니다. 해결은 간단합니다. 응답에서 첫 중괄호부터 마지막 중괄호까지만 잘라내 파싱하면, 모델이 무엇을 덧붙이든 안전합니다.


const start = raw.indexOf("{");
const end = raw.lastIndexOf("}");
const json = raw.slice(start, end + 1);  // 앞뒤 잡텍스트 무시

원리는 단순합니다. JSON 데이터는 항상 첫 { 에서 시작해 마지막 } 로 끝나므로, 그 바깥의 인사말이나 설명은 잘라서 버려도 안전합니다. 이 세 줄이면 모델이 앞뒤에 어떤 말을 붙이든 깨지지 않고 결과를 받아낼 수 있어, 자동화의 안정성이 크게 올라갑니다.

4.3 뉴스가 매일 똑같다 (중복 문제)

며칠 돌려보면 어제 본 기사가 오늘도 또 옵니다. 검색은 최근 며칠을 훑기 때문에 같은 기사가 반복되는 것입니다. 이럴 때는 "이미 보낸 기사 URL"을 Cloudflare KV에 저장해 두고, 다음 날 검색 결과에서 그 URL을 빼는 중복 제거를 선택적으로 켤 수 있습니다. URL 목록만 7일 정도 보관하면 충분합니다. 매일 전체를 다시 훑어보고 싶으면 꺼 두고, 새 기사만 받고 싶으면 켜는 식으로 취향에 맞추면 됩니다. (KV 저장소를 붙이는 한 단계만 추가하면 되는 선택 기능입니다.)

5. 마치며 — 만들고 나서 달라진 것

가장 큰 변화는 받은편지함에 뉴스레터가 한 통뿐이라는 점입니다. 여러 구독을 끊고, 내가 정한 주제의 핵심만 매일 1분 안에 훑습니다. 광고도, 일반론도, 읽지 않은 메일에 대한 죄책감도 없습니다. 월 운영 비용은 사실상 0원입니다(모든 도구의 무료 티어 안에서 동작).

이 프로젝트가 자동화 입문으로 좋은 이유는, "수집에서 가공, 전달"이라는 자동화의 가장 기본 골격을 그대로 담고 있기 때문입니다. 한번 이 구조를 손에 익히면, 뉴스 자리에 무엇을 넣든(채용 공고, 특정 키워드 논문, 경쟁 제품 업데이트) 똑같이 응용할 수 있습니다. 식상하지만, 그래서 가장 확실한 첫 자동화입니다.

다음 편에서는 이 뉴스레터에 "주간 다이제스트"와 "과거 기록 검색"을 붙이는 확장을 다룰 예정입니다.

메타 디스크립션: ai 뉴스레터 만들기로 매일 쏟아지는 뉴스 대신 내 관심 분야 핵심만 메일 한 통으로 받는 자동화 구축법. Cloudflare Workers와 AI 요약으로 서버도 비용도 없이 매일 아침 자동 발송됩니다. 구독 피로 없이 나만의 뉴스 큐레이션을 만드는 첫 자동화입니다.
매일 아침 받은편지함에 도착하는 나만의 AI 뉴스 브리핑 메일
매일 아침 받은편지함에 도착하는 나만의 AI 뉴스 브리핑 (사용자 본인 실제 화면)

ai 뉴스레터 만들기는 자동화에 처음 발을 들이는 사람에게 가장 좋은 첫 프로젝트입니다. 식상할 만큼 흔한 주제지만, 그래서 더 확실합니다. 누구에게나 "매일 정보는 챙겨야 하는데 시간이 없다"는 페인이 있고, 그것을 해결하는 구조가 단순해서 따라 하기도 쉽기 때문입니다. 이 글에서는 매일 아침 내 관심 분야의 뉴스만 골라 메일 한 통으로 받아보는 파이프라인을, 서버도 비용도 없이 만드는 방법을 정리합니다.

먼저 짚고 넘어갈 것이 있습니다. 우리는 "뉴스레터를 하나 더 구독"하려는 게 아닙니다. 오히려 구독을 늘리지 않고도 정보를 챙기는 방법을 만들려는 것입니다. 왜 그게 필요한지부터 보겠습니다.

1. 왜 "나만의 뉴스레터"가 필요한가 — 구독을 늘릴수록 더 못 읽는다

뉴스레터를 25개 구독해도 정작 안 읽는 이유

마이크로소프트의 2025 워크 트렌드 인덱스(Work Trend Index)에 따르면 평균적인 사람은 25개 이상의 뉴스레터를 구독하고, 직장인은 하루 117통의 이메일을 받습니다. 그 결과 메일 정리 서비스 클린이메일(Clean Email)의 조사에서는 41%가 "구독 피로(subscription fatigue)"를 호소했고, 별도 설문에서는 70%가 최근 3개월 안에 메시지 과다를 이유로 최소 3개 이상의 구독을 해지한 것으로 나타났습니다. 문제의 핵심은 흥미롭습니다. 받은편지함을 채우는 주범은 스팸이 아니라, 과거의 내가 직접 동의해서 구독한 신뢰할 만한 뉴스레터들이라는 점입니다. 내 선의의 구독이 쌓여서 나를 압도하는 구조입니다.

압도되면서도 정보는 부족한 역설

더 아이러니한 건, 정보가 넘치는데도 정작 필요한 건 못 챙긴다는 점입니다. 오픈텍스트(OpenText)의 2022년 조사에서 직장인의 80%가 정보 과부하를 겪고 있다고 답했고(2020년 60%에서 증가), 로이터 저널리즘연구소(Reuters Institute)의 디지털 뉴스 리포트에 따르면 전 세계적으로 40%의 사람이 뉴스를 의도적으로 회피한다고 답했습니다. 이는 2017년 29%에서 크게 늘어난, 역대 가장 높은 수치입니다. 뉴스레터 피로는 단순한 귀찮음이 아니라 "읽지 않은 메일에 대한 죄책감, 중요한 걸 놓칠까 하는 불안(FOMO), 그러면서도 정작 압도되어 아무것도 못 보는 역설"이 겹친 상태입니다.

남이 만든 큐레이션은 내 관심사와 다르다

기존 뉴스레터의 더 근본적인 한계는, 편집자가 "대중"을 위해 고른 내용이라는 점입니다. 내가 보고 싶은 건 'A 분야의 어제 핵심 3가지'인데, 받는 건 광고 반·일반론 반입니다. 국내 사용자들도 "넘쳐나는 정보의 옥석을 가리는 피로감"을 뉴스를 멀리하는 가장 큰 이유로 꼽고, 뉴스 앱은 알림만 확인하고 콘텐츠 자체는 잘 안 본다고 합니다.

바꿀 수 없는 건 내용만이 아닙니다. 형식도 고정돼 있습니다. 누구는 배경과 출처까지 자세히 보고 싶고, 누구는 한두 문장으로 핵심만 원합니다. 표로 받고 싶은 사람도, 불릿 한 줄씩 받고 싶은 사람도 있습니다. 하지만 남이 만든 뉴스레터에서는 분량도 출력 방식도 내가 손댈 수 없습니다. 결국 내용도 형식도, 나에게 꼭 맞는 형태는 내가 직접 만들 때만 나옵니다.

그래서 발상을 뒤집습니다. 남의 큐레이션을 구독하지 말고, 내 관심사만 매일 자동으로 큐레이션하는 AI 에이전트를 직접 하나 만든다. 받은편지함에는 단 한 통, 내가 정한 주제의 핵심만. 이것이 "나만의 뉴스레터"입니다.

2. 전체 그림 — 무료 스택으로 만드는 뉴스 파이프라인

구조는 의외로 단순합니다. 네 개의 구성 요소가 순서대로 일합니다.


[매일 아침 정해진 시각]  클라우드가 자동 기동
        |
        v
[1. 수집]  웹검색 API로 내 관심 주제 검색 (분야별 여러 갈래)
        |
        v
[2. 걸러내기]  어제 본 기사는 제외 (중복 제거, 선택)
        |
        v
[3. 요약]  AI가 진짜 중요한 것만 골라 한 줄 요약 + 출처
        |
        v
[4. 발송]  요약을 메일 한 통으로 받은편지함에 전달

구성 요소별로 무료 도구를 하나씩 매칭하면 이렇게 됩니다.

역할 도구 비고
자동 실행기 Cloudflare Workers + Cron 무료, 내 컴퓨터가 꺼져 있어도 매일 실행
뉴스 수집 Tavily(AI 웹검색 API) 무료 월 1,000회 검색
AI 요약 Cloudflare Workers AI 키 불필요, 같은 플랫폼 내 호출
메일 발송 Make.com 웹훅 → Gmail 발신 도메인 인증 불필요

핵심 설계 원칙은 "구성 요소 최소화"입니다. 외부 의존을 최대한 줄이고, 한 부분이 실패해도 나머지는 동작하도록 만드는 것이 매일 안정적으로 도착하는 비결입니다.

Cloudflare Workers → Tavily → Workers AI → Make.com → Gmail로 이어지는 뉴스레터 파이프라인
수집에서 발송까지 이어지는 전체 파이프라인 (사용자 본인 실제 구성)

3. 단계별 구현

3.1 무엇을 추적할지 정하기 (가장 중요)

코드보다 먼저 정해야 할 것은 "내가 매일 무엇을 알고 싶은가"입니다. 이 AI 에이전트의 품질은 80%가 여기서 결정됩니다. 저는 관심 주제를 4개 섹션으로 나누고, 각 섹션마다 검색 쿼리를 하나씩 붙였습니다. 이렇게 설정값을 한 파일에 모아두면, 나중에 관심사가 바뀌어도 이 부분만 고치면 됩니다.


// config.js — 이 AI 에이전트의 운전대. 추적 주제가 바뀌면 여기만 고친다.
export const SECTIONS = [
  { key: "models",   title: "모델·연구",   query: "new LLM model release research breakthrough" },
  { key: "tools",    title: "제품·개발툴", query: "AI developer tools agent SDK product launch" },
  { key: "business", title: "기업·투자",   query: "AI company funding acquisition announcement" },
  { key: "applied",  title: "응용·규제",   query: "machine learning enterprise adoption AI policy" },
];

각 섹션이 곧 독립된 검색 한 갈래입니다. 한 섹션 검색이 실패해도 나머지 섹션은 그대로 발송되도록 분리해 두는 것이 포인트입니다.

3.2 뉴스 수집 — 웹검색 API

뉴스는 Tavily 같은 AI 웹검색 API로 모읍니다. 일반 검색과 달리 "최근 며칠 이내"로 범위를 좁힐 수 있어서, 매일 신선한 결과만 가져오기에 적합합니다. 섹션별로 쿼리를 돌려 제목·요약 스니펫·원문 URL을 받아옵니다.


async function searchOne(section, apiKey) {
  const res = await fetch("https://api.tavily.com/search", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      api_key: apiKey, query: section.query,
      topic: "news", days: 2, max_results: 5,
    }),
  });
  const data = await res.json();
  return (data.results ?? []).map(r => ({ title: r.title, url: r.url, snippet: r.content }));
}

핵심은 두 개의 옵션입니다. days: 2는 "최근 이틀 안에 나온 기사만" 가져오라는 뜻이라, 매일 돌려도 어제오늘 신선한 결과만 잡힙니다. max_results: 5는 섹션당 5건까지만 받겠다는 상한선으로, 뒤에서 AI가 추려낼 후보를 적당히 좁혀 두는 역할입니다. 반환값은 제목·원문 URL·요약 스니펫 세 가지로만 정리해, 다음 요약 단계가 다루기 쉬운 형태로 넘깁니다.

Tavily 대시보드에서 발급한 무료 웹검색 API 키
Tavily 대시보드에서 발급한 무료 API 키 (사용자 본인 실제 화면)

3.3 AI 요약 — 핵심만 골라내기

수집한 기사를 그대로 보내면 그건 또 다른 정보 과부하입니다. 그래서 AI에게 "진짜 중요한 것만 2~3건 골라, 한 줄 요약과 왜 중요한지를 붙여라. 건질 게 없으면 없다고 말하라"고 지시합니다. 억지로 칸을 채우지 않게 하는 것이 노이즈를 막는 핵심입니다.


const SYSTEM = `너는 매일 분야 동향을 추적하는 분석가다.
- 섹션마다 진짜 중요한 것만 2~3건 선별한다. 광고·중복·단순 리캡은 버린다.
- 건당 요약(한 줄)과 왜 중요한지(반 줄)를 쓴다.
- 건질 게 없는 섹션은 비워 둔다. 억지로 채우지 않는다.
- 반드시 정해진 JSON 형식으로만 답한다.`;

1번에서 말한 "형식까지 내 손에 들어온다"가 바로 이 지시문에서 실현됩니다. 이 한 곳만 고치면 "요약은 두 문장까지만, 비판적 시각은 생략"이나 "결과를 표로 정리" 같은 식으로 분량과 출력 형식을 그때그때 바꿀 수 있습니다. 시중 뉴스레터에서는 불가능한, 직접 만들 때만 얻는 자유입니다.

저는 요약 엔진으로 Cloudflare Workers AI(llama-3.3-70b)를 썼습니다. 키 발급도 필요 없고 같은 플랫폼 안에서 호출되어 빠릅니다. 다만 여기까지 오는 데 시행착오가 있었는데, 그 이야기는 4번에서 따로 다루겠습니다.

3.4 메일로 보내기 — 발송은 위임

메일 발송은 직접 구현하지 않고 Make.com 웹훅에 넘기는 방식이 가장 쉽습니다. Make에서 "웹훅 수신 → Gmail로 보내기" 시나리오를 만들어 두면, 우리 AI 에이전트는 요약 결과(받는사람·제목·본문)를 웹훅 주소로 한 번 던지기만 하면 됩니다. 발신 도메인 인증 같은 골치 아픈 과정이 사라지고, 내 Gmail 계정으로 바로 받을 수 있습니다.


await fetch(MAKE_WEBHOOK_URL, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ to: MY_EMAIL, subject: `오늘의 브리핑 ${date}`, html }),
});

보다시피 우리 코드가 하는 일은 "받는사람·제목·본문(HTML) 세 가지를 JSON 한 덩어리로 묶어 웹훅 주소에 던지는 것"이 전부입니다. 실제 Gmail 인증·발송은 Make가 대신 처리하므로, 메일 서버를 직접 다룰 필요가 없습니다. 덕분에 자동화에서 가장 자주 막히는 "발신 인증" 단계를 통째로 건너뜁니다.

Make.com 웹훅 수신에서 Gmail 발송으로 이어지는 시나리오
Make.com의 웹훅 수신에서 Gmail 발송으로 이어지는 시나리오 (사용자 본인 실제 화면)

3.5 매일 자동 실행 — 클라우드에 올리기

마지막으로 이 전체를 Cloudflare에 배포하면, 내 컴퓨터가 꺼져 있어도 매일 정해진 시각에 자동으로 돕니다. 설정 파일에 실행 시각만 정해주면 됩니다.


# wrangler.toml
[triggers]
crons = ["0 22 * * *"]   # UTC 22:00 = 한국시간 07:00

npx wrangler deploy 한 번이면 끝입니다. 이제 매일 아침 7시, 받은편지함에 나만의 뉴스레터가 도착합니다.

4. 자주 겪는 문제 — 실제로 부딪힌 시행착오

4.1 무료 AI의 함정 — "무료"가 다 같은 무료가 아니다

처음엔 Google Gemini 무료 API로 요약을 붙였습니다. 그런데 테스트를 몇 번 돌리자마자 429 quota exceeded가 떴습니다. 확인해 보니 그 모델의 무료 한도가 하루 20회였습니다. 실제 운영은 하루 1회라 충분하지만, 디버깅하며 여러 번 호출하면 금세 소진됩니다.

다음으로 Groq(무료 한도가 넉넉하고 빠름)로 바꿨습니다. 로컬에서 테스트하면 잘 됐는데, 클라우드에 올리니 403 Forbidden이 떴습니다. 원인은 Groq가 Cloudflare Workers의 데이터센터 IP를 차단하는 것이었습니다. 내 컴퓨터에서는 되고 클라우드에서만 막히는, 찾기 어려운 함정입니다.

결국 정답은 Cloudflare Workers AI였습니다. 같은 플랫폼 안에서 호출되니 IP 차단이 있을 수 없고, 키 발급도 필요 없습니다. 교훈은 명확합니다. "무료"라는 단어만 보지 말고, 일일 한도와 호출 환경(어디서 부르는지)을 먼저 확인해야 합니다.

4.2 AI가 JSON을 깔끔하게 안 준다

AI에게 "JSON으로만 답하라"고 해도, 종종 JSON 앞뒤에 "네, 여기 결과입니다" 같은 설명을 붙입니다. 그러면 결과를 코드로 처리하다 파싱 에러가 납니다. 해결은 간단합니다. 응답에서 첫 중괄호부터 마지막 중괄호까지만 잘라내 파싱하면, 모델이 무엇을 덧붙이든 안전합니다.


const start = raw.indexOf("{");
const end = raw.lastIndexOf("}");
const json = raw.slice(start, end + 1);  // 앞뒤 잡텍스트 무시

원리는 단순합니다. JSON 데이터는 항상 첫 { 에서 시작해 마지막 } 로 끝나므로, 그 바깥의 인사말이나 설명은 잘라서 버려도 안전합니다. 이 세 줄이면 모델이 앞뒤에 어떤 말을 붙이든 깨지지 않고 결과를 받아낼 수 있어, 자동화의 안정성이 크게 올라갑니다.

4.3 뉴스가 매일 똑같다 (중복 문제)

며칠 돌려보면 어제 본 기사가 오늘도 또 옵니다. 검색은 최근 며칠을 훑기 때문에 같은 기사가 반복되는 것입니다. 이럴 때는 "이미 보낸 기사 URL"을 Cloudflare KV에 저장해 두고, 다음 날 검색 결과에서 그 URL을 빼는 중복 제거를 선택적으로 켤 수 있습니다. URL 목록만 7일 정도 보관하면 충분합니다. 매일 전체를 다시 훑어보고 싶으면 꺼 두고, 새 기사만 받고 싶으면 켜는 식으로 취향에 맞추면 됩니다. (KV 저장소를 붙이는 한 단계만 추가하면 되는 선택 기능입니다.)

5. 마치며 — 만들고 나서 달라진 것

가장 큰 변화는 받은편지함에 뉴스레터가 한 통뿐이라는 점입니다. 여러 구독을 끊고, 내가 정한 주제의 핵심만 매일 1분 안에 훑습니다. 광고도, 일반론도, 읽지 않은 메일에 대한 죄책감도 없습니다. 월 운영 비용은 사실상 0원입니다(모든 도구의 무료 티어 안에서 동작).

이 프로젝트가 자동화 입문으로 좋은 이유는, "수집에서 가공, 전달"이라는 자동화의 가장 기본 골격을 그대로 담고 있기 때문입니다. 한번 이 구조를 손에 익히면, 뉴스 자리에 무엇을 넣든(채용 공고, 특정 키워드 논문, 경쟁 제품 업데이트) 똑같이 응용할 수 있습니다. 식상하지만, 그래서 가장 확실한 첫 자동화입니다.

다음 편에서는 이 뉴스레터에 "주간 다이제스트"와 "과거 기록 검색"을 붙이는 확장을 다룰 예정입니다.

댓글

이 블로그의 인기 게시물

디스코드 구글캘린더 연동 봇 만들기: 카톡 캡처 OCR 자동 등록 시스템 구축

클로드 코드 비용 줄이는 법 — ollama 로컬 LLM 연동 (M3 맥북)

유튜브 노션 자동 저장 봇 만들기 — Claude CLI MCP + Discord 233일 운영기