Hermes Agent로 데일리 뉴스 뉴스레터 봇을 만들며 겪은 시행착오와 성공기
요약
Hermes Agent를 활용하여 뉴스레터 자동화 봇을 구축하며 겪은 기술적 시행착오를 다룹니다. GitHub Codespaces 환경에서의 SMTP 설정 오류와 컨테이너 재빌드 시 데이터 유실 문제를 해결하는 과정을 공유합니다.
핵심 포인트
- SMTP 호스트 오타(smpt)로 인한 디버깅 사례
- Codespaces 재빌드 시 환경 변수 및 설치 파일 유실 방지법
- devcontainer.json을 활용한 자동화된 개발 환경 구축
- 오픈 소스 도구(OpenRouter, Nodemailer)를 활용한 비용 0원 구축
Hermes Agent Challenge를 위해 제출됨
아이디어
제가 원한 것은 단 한 가지였습니다. 매일 아침 캐나다, 세계, 인도, 그리고 AI/기술 분야의 주요 뉴스와 함께 동기 부여를 주는 명언, 그리고 건강 팁이 담긴 이메일이 제 편지함에 도착하는 것이었습니다. 이메일 단 하나에 모든 것을 담는 것이죠. 커피를 마시기 전 다섯 개의 서로 다른 앱을 스크롤하며 확인할 필요가 없도록 말입니다.
단순해 보이지만, 그렇지 않았습니다.
이 글은 Hermes Agent를 사용하여 데일리 브리핑 봇을 구축하며 겪은 솔직한 이야기입니다. 제가 마주했던 모든 장벽, 잃어버렸던 모든 설정값, 그리고 마침내 모든 것이 제대로 작동했던 순간들을 포함합니다.
기술 스택 (비용 0원, 완전 오픈 소스)
GitHub Codespaces — 무료 클라우드 개발 환경 (제 개인 Mac은 건드리지 않기 위해)
TypeScript + Nodemailer — Gmail SMTP를 통해 이메일 전송
OpenRouter — Hermes가 사용할 무료 LLM API
유료 서비스는 없습니다. 클라우드 비용도 없습니다. 그저 오픈 소스 도구들을 서로 연결했을 뿐입니다.
**왜 GitHub Codespaces인가?
저는 이 작업이 제 개인 Mac에서 실행되는 것을 원치 않았습니다. 격리된 환경, 즉 제 컴퓨터에 영향을 주지 않고 언제든 파괴하고 다시 구축할 수 있는 환경을 원했습니다. GitHub Codespaces는 브라우저에서 무료 Linux 환경을 제공했습니다. 완벽했습니다.
적어도 그렇게 생각했습니다.
문제점들 (진짜 이야기)
문제 1: 30분을 잡아먹은 오타
Nodemailer 설정을 마치고 전송 스크립트를 실행했는데, 터미널이 그냥... 멈춰버렸습니다. 에러도 없고, 출력도 없었습니다. 그저 침묵뿐이었습니다.
저는 Codespaces의 방화벽이 SMTP 포트 587을 차단하고 있는 문제라고 생각했습니다. 그래서 포트 465로 바꿨습니다. 여전히 어떤 실행에서는 멈춰 있었습니다. 상세 로그(verbose logging)를 추가했고, verify() 호출도 시도해 보았습니다.
그러다 발견했습니다:
hostname: 'smpt.gmail.com'
smtp가 아니라 smpt였습니다. 글자 하나가 뒤바뀐 것이었습니다. 30분 동안의 디버깅(debugging) 끝에 찾아냈습니다.
교훈: 코드를 디버깅하기 전에 항상 환경 변수(env vars)를 출력해 보세요.
문제 2: 컨테이너 재빌드(Rebuilding)가 모든 것을 삭제함
이 문제는 뼈아팠습니다. Hermes를 설치했고, .env를 설정했으며, SMTP 비밀 정보(secrets)도 모두 세팅하여 모든 것이 정상 작동하고 있었습니다. 그러다 관련 없는 다른 문제를 해결하기 위해 Codespace 컨테이너를 재빌드했습니다.
모두 사라졌습니다. 전부 다요.
Hermes는 삭제되었고, 환경 변수(environment variables)는 증발했으며, .env 파일도 사라졌습니다. 모든 것을 처음부터 다시 설치하고 모든 비밀 정보를 다시 설정해야 했습니다.
해결 방법은 두 가지였습니다:
첫째, 매번 재빌드할 때마다 Hermes가 자동으로 설치되도록 .devcontainer/devcontainer.json 파일을 생성하는 것입니다:
{
"name": "daily-brief-hermes",
"postCreateCommand": "pip install hermes-agent && npm install"
...
둘째, 비밀 정보(secrets)를 ~/.hermes/.env에 보관하고, 프로젝트의 .env 파일은 단순히 셸 세션(shell session)에 떠 있게 두지 말고 안전한 위치에 커밋(commit)해 두는 것입니다.
교훈: 셸 세션을 절대 믿지 마세요. 파일로 기록되지 않은 모든 것은 컨테이너가 재빌드되는 순간 사라집니다.
문제 3: "type": "module"과 충돌하는 ts-node
제 package.json에는 "type": "module"이 포함되어 있었는데, 이로 인해 ts-node에서 다음과 같은 오류가 발생했습니다:
TypeError: Unknown file extension ".ts"
세 가지의 서로 다른 에러 메시지, 두 번의 설정 변경, 그리고 한 번의 Stack Overflow 미로 탐험 끝에 찾아낸 해결책은 ts-node 대신 tsx로 전환하는 것이었습니다. tsx는 별도의 설정 없이도 ESM과 CommonJS를 모두 처리할 수 있는 교체 가능한 도구입니다:
npm install --save-dev tsx
교훈: 현대적인 Node 프로젝트에서 TypeScript를 사용할 때는 ts-node 대신 tsx를 사용하세요. 그냥 잘 작동합니다.
하나씩 배제해야 했던 세 가지 가능한 원인들:
- 스팸 폴더 — 그곳에 있었습니다. Gmail이 이를 스팸으로 분류했습니다.
- 잘못된 앱 비밀번호(App Password) — Gmail은 일반 로그인 비밀번호가 아닌 16자리의 앱 비밀번호를 요구합니다. 실수하기 쉽습니다.
- 빈 파일 전송 — 파일 경로(file path)는 올바르게 확인되었지만, 내용이 아직 파일에 쓰이지 않은 상태였습니다.
스팸 문제는 적절한 발신자 이름과 동적 제목(dynamic subject line)을 추가함으로써 해결되었습니다:
await transporter.sendMail({
from: `"Daily Brief 📰" <${SMTP_USER}>`,
to: RECIPIENTS.join(", "),
...
교훈: 항상 스팸함을 확인하세요. 계정 비밀번호가 아닌 반드시 Gmail 앱 비밀번호 (App Passwords)를 사용하세요. Gmail을 학습시키기 위해 첫 번째 이메일은 항상 "스팸 아님 (Not Spam)"으로 표시하세요.
문제 5: Hermes에 모델이 설정되지 않음
No inference provider configured. Run 'hermes model' to choose a provider and model,
or set an API key in ~/.hermes/.env
그 후 OpenRouter 키를 추가했음에도 다음과 같은 오류가 발생했습니다:
HTTP 400: No models provided
API 키는 존재했지만 모델이 설정되지 않은 상태였습니다. 해결 방법은 ~/.hermes/.env 파일에 HERMES_MODEL을 추가하는 것이었습니다:
OPENROUTER_API_KEY=sk-or-xxxxxxxxxxxxxxxx
HERMES_MODEL=owlobot/owl-7b
교훈: Hermes는 API 키와 모델 지정이 모두 필요합니다. 하나라도 누락되면 모호한 오류가 발생합니다.
실제로 완벽하게 작동했던 부분
모든 설정이 완료되자, Hermes의 성능은 진정으로 인상적이었습니다. 저는 평범한 영어 프롬프트를 붙여넣었습니다:
Today is 2026-05-26. Search the web for today's top 5 headlines for
Canada news, World news, India news, and AI/tech news. Add one
motivational quote and one health tip. Format as Markdown and save to
...
그러자 Hermes는 다음과 같이 수행했습니다:
- 네 가지 카테고리 전체에 대해 웹에서 최신 헤드라인을 검색함
- 각 기사를 읽기 쉬운 불렛 포인트 (bullet points)로 요약함
- 동기 부여 문구와 건강 팁을 추가함
- 모든 내용을 깔끔한 마크다운 (Markdown) 형식으로 구성함
- 제가 지정한 정확한 파일 경로에 저장함
이 부분이 그 고통스러웠던 설정 과정을 모두 가치 있게 만들어 주었습니다. 저는 뉴스를 가져오는 코드를 단 한 줄도 작성하지 않았습니다. RSS 파서 (RSS parsers), 뉴스 API, 스크래핑 (scraping)도 필요 없었습니다. Hermes가 자연어 (natural language)를 통해 이 모든 것을 처리했습니다.
최종 아키텍처 (Final Architecture)
~/.hermes/.env
└── OPENROUTER_API_KEY + HERMES_MODEL
└── SMTP credentials
...
Hermes 크론 설정 (완전 자동 일일 실행을 위해)
hermes cron start
hermes chat 내부에서:
/cron add "0 8 * * *" "Read the skill at /workspaces/daily-brief-hermes/skills/daily_brief.md
/workspaces/daily-brief-hermes/briefings/$(date +%Y-%m-%d).md"
참고: Codespaces는 유휴 상태일 때 절전 모드로 전환되므로, 진정으로 항상 켜져 있는 설정을 원한다면 이를 작은 VPS로 옮겨야 합니다. 하지만 프로토타이핑(Prototyping)과 학습 용도로는 Codespaces가 완벽하게 작동합니다.
오늘 이 프로젝트를 시작하는 사람에게 해주고 싶은 말
- 첫날부터
.devcontainer/devcontainer.json을 만드세요. 재빌드(Rebuild)로 인해 설정이 날아갈 때까지 기다리지 마세요. - 모든 비밀 정보(Secrets)는 파일에 보관하세요. 절대 셸 내보내기(Shell exports)로만 두지 마세요. 셸 내보내기는 사라지지만, 파일은 사라지지 않습니다.
ts-node대신tsx를 사용하세요.tsx는package.json과 충돌 없이 현대적인 Node 모듈 시스템을 처리합니다.- Hermes 크론(Cron)을 건드리기 전에 이메일 스크립트를 완전히 테스트하세요. 먼저 이메일이 작동하게 만드세요. 그다음 AI 레이어(Layer)를 추가하세요.
- 네트워크 문제를 디버깅하기 전에 환경 변수(Env vars)를 출력해 보세요.
echo $SMTP_HOST를 실행하는 데는 2초밖에 걸리지 않으며, 이는 저의 30분을 아껴줄 수 있었을 것입니다. - 스팸함을 확인하세요. 진심입니다. 스팸함부터 먼저 확인하세요.
Hermes Agent에 대한 최종 생각
설정 과정의 마찰(Friction)은 실제로 존재합니다. 특히 재빌드 시 상태가 초기화되는 Codespaces 환경에서는 더욱 그렇습니다. 하지만 일단 Hermes가 구성되면, 평이한 영어 지시문을 작성하고 그것이 웹을 검색하고, 콘텐츠에 대해 추론하며, 구조화된 출력(Structured output)을 생성하는 과정을 지켜보는 경험은 제가 이전에 만들었던 그 어떤 것과도 진정으로 다릅니다.
저는 뉴스 애그리게이터(News aggregator)를 작성한 것이 아닙니다. 스크레이퍼(Scraper)를 구축한 것도 아닙니다. 저는 프롬프트(Prompt)와 TypeScript 이메일 스크립트를 작성했을 뿐이며, 매일 아침 제 편지함으로 데일리 브리핑(Daily briefing)을 받습니다.
그 점이 저에게 가장 깊은 인상을 남깁니다.
Hermes Agent Challenge를 위해 제작됨 | GitHub: daily-brief-hermes
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기