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

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

Gmail 자동 라벨링 — 받은편지함을 LLM이 알아서 분류하게 만들기

n8n-workflow
전체 워크플로우 — n8n 스타일로 제작한 Gmail 자동 라벨 분류 파이프라인

gmail 라벨 자동 분류는 받은편지함이 감당이 안 될 때 가장 먼저 시도해 볼 만한 자동화입니다. 새 메일이 도착할 때마다 사람이 일일이 라벨을 붙이는 대신, LLM이 내용을 읽고 알맞은 라벨을 골라 붙이게 만드는 것입니다. 이 글에서는 n8n 같은 노코드 도구로 보던 자동 라벨링을, Python과 무료 LLM으로 가볍게 다시 만들어 맥에서 무인으로 돌리는 과정을 정리합니다.

시작하기 전에 한 가지 구분이 필요합니다. 지난 편에서 다룬 "나만의 AI 뉴스레터 만들기"가 정보를 받는 자동화였다면, 이번 글은 받은 메일을 정리하는 자동화입니다. 겉보기엔 이어져 보이지만, 손대는 부분이 서로 다릅니다.

1. 왜 받은편지함을 자동 분류하나 — 문제는 정리에 있다

메일이 안 와서 문제인 사람은 없습니다. 너무 많이 와서, 그리고 그게 뒤섞여 있어서 문제입니다. 이건 막연한 느낌이 아니라 숫자로도 분명합니다.

받은편지함의 대부분은 노이즈입니다

마이크로소프트와 여러 조사를 종합하면 직장인은 하루 평균 117~121통의 메일을 받습니다. 그런데 이 중 실제로 답장이나 행동이 필요한 메일은 약 12%뿐입니다. 나머지 88%는 사실상 노이즈라는 뜻입니다. 전 세계 이메일 트래픽의 절반 가까이(약 47%)가 스팸이고, 그 스팸의 36%는 내가 신청한 적도 없는 광고·프로모션입니다. 사람들은 이 스팸을 걸러내는 데만 주당 최대 3시간을 씁니다. 받은편지함을 열 때 느끼는 피로는 게으름이 아니라, 신호보다 노이즈가 압도적으로 많은 구조 탓입니다.

정작 중요한 메일은 어디로 갔나

진짜 짜증 나는 순간은 따로 있습니다. 분명히 받았는데 그 메일을 못 찾을 때입니다. 한 조사에서 Gmail 사용자의 70% 이상이 한 달에 한 번 이상 검색 연산자를 동원해야 원하는 메일을 찾는다고 답했습니다. 그냥 스크롤로는 안 나온다는 이야기입니다. 검색을 해도 엉뚱한 결과만 뜨거나, 광고 메일 수백 통에 묻혀 중요한 청구서나 연락을 놓치는 일이 생깁니다. 실제로 메일을 받은편지함에 그냥 쌓아 두는 사람은 52%만 "기록 관리가 된다"고 느낀 반면, 폴더로 분류해 둔 사람은 71%가 만족했습니다. 분류는 깔끔함의 문제가 아니라 못 찾던 것을 찾게 만드는 문제입니다.

내가 동의한 뉴스레터의 역습

받은편지함을 채우는 주범은 의외로 스팸이 아닙니다. 과거의 내가 직접 구독한 뉴스레터들입니다. 평균적인 사람은 25개 이상의 뉴스레터를 구독하지만 정작 꾸준히 여는 건 3~5개뿐입니다. 그 결과 41%가 구독 피로를 호소하고, 구독을 해지하는 사람의 31%가 "받은편지함을 어지럽히는 뉴스레터가 너무 많아서"를 첫 번째 이유로 꼽습니다. 나를 압도하는 건 악성 스팸이 아니라, 선의로 눌렀던 구독 버튼들이 쌓인 결과입니다.

발신자 규칙으로는 못 막습니다

폴더와 필터를 손으로 만들어 본 사람은 알겠지만, 발신자 규칙 기반 필터는 금세 한계에 부딪힙니다. 같은 발신자가 영수증과 광고를 같이 보내고, 처음 보는 발신자는 규칙에 안 걸리기 때문입니다. 그래서 발상을 바꿉니다. 발신자가 아니라 내용으로 분류하면 규칙을 일일이 만들 필요가 없습니다. 메일 한 통의 제목과 본문을 LLM에게 읽히고 "이건 어떤 라벨이 맞냐"고 물으면, 사람이 판단하던 그 일을 대신해 줍니다. 노코드 도구에서 흔히 보던 Gmail + OpenAI 워크플로우가 바로 이 구조인데, 같은 일을 코드로 직접 짜면 훨씬 가볍고 내 맥에서 공짜로 돌릴 수 있습니다.

2. 전체 그림 — 무료 스택으로 만드는 분류 봇

구조는 단순합니다. 다섯 단계가 5분마다 순서대로 돕니다.


[launchd 5분 타이머]  맥이 자동 기동
        |
        v
[1. 폴링]  안 읽고 라벨 없는 새 메일만 검색
        |
        v
[2. 라벨 목록]  지금 있는 라벨들을 LLM에게 알려줌
        |
        v
[3. 분류]  LLM이 제목·본문 읽고 라벨 1개 선택 (없으면 신규)
        |
        v
[4. 부착]  라벨 달기. 광고면 받은편지함에서 내림(아카이브)

컴포넌트별 무료 도구는 이렇게 매칭됩니다.

역할 도구 비고
자동 실행기 macOS launchd 5분 주기, 서버 불필요
메일 접근 Gmail API + 로컬 OAuth 무인 인증 가능
분류 LLM Groq llama-3.3-70b 무료 한도 넉넉, 빠름
라벨 부착 Gmail API label 신규 라벨 생성도 API로

핵심 설계 원칙은 idempotency(여러 번 실행해도 결과가 같은 성질)입니다. 같은 메일을 두 번 처리하지 않도록, 검색 조건에 -has:userlabels(라벨 없는 메일만)를 넣습니다. 이미 분류한 메일은 자동으로 빠지므로 5분마다 돌아도 중복이 없습니다.

3. 단계별 구현

3.1 분류 프롬프트 — 라벨 무한 증식을 막는 게 핵심

이 봇의 품질은 프롬프트가 80%를 결정합니다. LLM에게 기존 라벨 목록과 메일 한 통을 주고, 가장 맞는 라벨 하나를 JSON으로 받습니다. 이때 가장 중요한 규칙은 라벨을 함부로 새로 만들지 않게 하는 것입니다.


규칙:
1. 기존 라벨 중 의미가 통하면 재사용한다.
2. 안 맞을 때만 새 라벨을 만든다. 단 재사용 가능한
   카테고리 수준으로(뉴스레터·채용/구직·결제/영수증 등).
   메일 1통에만 쓸 초협소 라벨은 금지.
3. 광고·홍보는 라벨을 붙이되 받은편지함에서 내린다.
4. 분류 근거를 한국어 한 줄로 남긴다.

새 라벨은 confidence가 일정 값 이상일 때만 만들게 막아 두면, 라벨이 수십 개로 불어나는 사태를 방지할 수 있습니다.

3.2 Gmail 접근 — API + 로컬 OAuth (MCP가 아닌 이유)

처음엔 Claude의 Gmail MCP로 붙이려 했습니다. 편하지만 결정적 문제가 있었는데, 그 이야기는 4번에서 다룹니다. 결론만 말하면 무인 자동화에는 Gmail API + 로컬 OAuth 토큰이 정답이었습니다. OAuth 동의를 한 번만 거치면 토큰이 저장되어, 이후로는 사람 개입 없이 메일에 접근합니다.


SCOPES = ["https://www.googleapis.com/auth/gmail.modify"]
creds = Credentials.from_authorized_user_file("token.json", SCOPES)
service = build("gmail", "v1", credentials=creds)

여기서 권한은 gmail.modify까지만 받습니다. 라벨을 붙이고 받은편지함에서 내리는 데 필요한 최소 권한이고, 메일을 삭제하거나 발송하는 권한은 받지 않습니다.

3.3 분류 — Groq에 메일을 읽힌다

새 메일의 발신자·제목·본문 앞부분을 프롬프트에 끼워 Groq에 보냅니다. Groq의 무료 한도가 넉넉하고 응답이 빨라서, 5분마다 도는 봇에 적합합니다. 응답은 라벨 이름·신규 여부·아카이브 여부·근거가 담긴 JSON 한 덩어리입니다.


{
  "label": "결제/영수증",
  "is_new_label": false,
  "archive_from_inbox": false,
  "reason": "AWS 결제 영수증 메일이라 결제/영수증에 해당."
}

3.4 라벨 부착과 광고 아카이브

LLM이 고른 라벨이 기존에 없으면 먼저 라벨을 생성하고, 그다음 메일에 답니다. 광고·뉴스레터로 판정되면 라벨은 붙이되 받은편지함에서만 내립니다(INBOX 라벨 제거). 삭제는 절대 하지 않습니다. 분류가 잘못돼도 메일은 라벨 검색으로 언제든 찾을 수 있어야 하기 때문입니다.

new-label-created
내용에 맞춰 자동 생성된 라벨들 — 뉴스레터·광고/프로모션·소셜/알림·채용/구직 등이 메일 수와 함께 쌓였습니다 (사용자 본인 실제 화면)

3.5 5분마다 무인 실행 — launchd

마지막으로 이 전체를 macOS launchd에 5분 주기로 등록하면, 내가 아무것도 안 해도 새 메일이 들어오는 족족 분류됩니다. 별도 서버가 필요 없습니다.


<key>StartInterval</key>
<integer>300</integer>   <!-- 300초 = 5분 -->
launchd-log
5분마다 깨어나 새 메일을 분류하는 bot.log (사용자 본인 실제 화면)

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

4.1 라벨이 3개뿐일 때의 콜드스타트 함정

가장 먼저 막힌 곳입니다. 처음 돌렸더니 봇이 모든 메일을 기존의 범용 라벨 하나에 욱여넣고 전부 아카이브해 버렸습니다. 원인은 "신규 라벨을 자제하라"는 규칙이 너무 강했던 탓입니다. 기존 라벨이 두세 개밖에 없는 초기 상태에서는, 그 규칙이 "안 맞아도 일단 있는 것에 넣어라"로 작동해 분류가 아니라 그냥 치워 버리는 결과를 냅니다. 콜드스타트 구간에서는 오히려 새 카테고리 라벨 생성을 허용하고, 신규 생성 문턱(confidence)을 낮춰야 제대로 분류되기 시작합니다.

4.2 MCP는 무인 자동에서 인증이 안 된다

편할 줄 알았던 Gmail MCP가 launchd 무인 실행에서는 인증을 통과하지 못했습니다. 사람이 붙어 있는 대화형 환경을 전제로 하기 때문입니다. 5분마다 사람 없이 도는 봇에는 맞지 않아, 결국 Gmail API + 로컬 OAuth 토큰으로 갈아탔습니다. "편한 도구"와 "무인으로 도는 도구"는 다르다는 걸 체감한 지점입니다.

4.3 나에게 보낸 메모가 뉴스레터로 분류된다

나 자신에게 보내는 메모성 메일(할 일, 아이디어)이 자꾸 엉뚱한 라벨로 분류됐습니다. 내용만 보면 LLM이 헷갈릴 만합니다. 그래서 본인 발신 메일은 LLM을 거치지 않고 곧장 개인메모 라벨로 보내는 결정적 규칙을 앞단에 넣었습니다. LLM이 잘하는 일과, 규칙으로 못 박는 게 나은 일을 나누는 게 핵심입니다.

5. 쌓여 있던 메일도 한 번에 — 백필

라이브 봇이 새 메일만 처리하다 보니, 이미 받은편지함에 쌓여 있던 메일은 그대로 남았습니다. 그래서 날짜 제한 없이 in:inbox -has:userlabels 전체를 페이지 단위로 훑어 한 번에 분류하는 백필 스크립트를 따로 만들었습니다. 약 200통의 백로그를 일괄 처리할 때는, 라이브 봇과 동시에 돌면 충돌하므로 백필 동안에는 launchd를 잠시 멈췄다가 끝나고 다시 켰습니다.

backfill-run
받은편지함에 쌓여 있던 약 200통을 일괄 분류하는 백필 실행 (사용자 본인 실제 화면)

6. 마치며 — 분류 다음의 응용

inbox-labeled
자동 분류로 정리된 받은편지함 — 라벨별로 묶이고 광고는 받은편지함에서 내려갔습니다 (사용자 본인 실제 화면)

받은편지함을 LLM에게 정리시키고 나서 가장 크게 달라진 점은, 받은편지함을 열 때 느끼던 부담이 사라졌다는 것입니다. 광고는 알아서 내려가고, 중요한 메일은 라벨로 묶여 한눈에 들어옵니다. 운영 비용은 사실상 0원입니다.

이 구조가 좋은 이유는 "읽고, 판단하고, 분류한다"는 자동화의 기본 골격을 그대로 담고 있기 때문입니다. 메일 자리에 무엇을 넣든(파일, 메모, 알림) 같은 패턴으로 응용할 수 있습니다. 다음 편에서는 이 분류 결과를 노션 데이터베이스로 모아 주간 단위로 돌아보는 확장을 다룰 예정입니다.

n8n-workflow
전체 워크플로우 — n8n 스타일로 제작한 Gmail 자동 라벨 분류 파이프라인

gmail 라벨 자동 분류는 받은편지함이 감당이 안 될 때 가장 먼저 시도해 볼 만한 자동화입니다. 새 메일이 도착할 때마다 사람이 일일이 라벨을 붙이는 대신, LLM이 내용을 읽고 알맞은 라벨을 골라 붙이게 만드는 것입니다. 이 글에서는 n8n 같은 노코드 도구로 보던 자동 라벨링을, Python과 무료 LLM으로 가볍게 다시 만들어 맥에서 무인으로 돌리는 과정을 정리합니다.

시작하기 전에 한 가지 구분이 필요합니다. 지난 편에서 다룬 "나만의 AI 뉴스레터 만들기"가 정보를 받는 자동화였다면, 이번 글은 받은 메일을 정리하는 자동화입니다. 겉보기엔 이어져 보이지만, 손대는 부분이 서로 다릅니다.

1. 왜 받은편지함을 자동 분류하나 — 문제는 정리에 있다

메일이 안 와서 문제인 사람은 없습니다. 너무 많이 와서, 그리고 그게 뒤섞여 있어서 문제입니다. 이건 막연한 느낌이 아니라 숫자로도 분명합니다.

받은편지함의 대부분은 노이즈입니다

마이크로소프트와 여러 조사를 종합하면 직장인은 하루 평균 117~121통의 메일을 받습니다. 그런데 이 중 실제로 답장이나 행동이 필요한 메일은 약 12%뿐입니다. 나머지 88%는 사실상 노이즈라는 뜻입니다. 전 세계 이메일 트래픽의 절반 가까이(약 47%)가 스팸이고, 그 스팸의 36%는 내가 신청한 적도 없는 광고·프로모션입니다. 사람들은 이 스팸을 걸러내는 데만 주당 최대 3시간을 씁니다. 받은편지함을 열 때 느끼는 피로는 게으름이 아니라, 신호보다 노이즈가 압도적으로 많은 구조 탓입니다.

정작 중요한 메일은 어디로 갔나

진짜 짜증 나는 순간은 따로 있습니다. 분명히 받았는데 그 메일을 못 찾을 때입니다. 한 조사에서 Gmail 사용자의 70% 이상이 한 달에 한 번 이상 검색 연산자를 동원해야 원하는 메일을 찾는다고 답했습니다. 그냥 스크롤로는 안 나온다는 이야기입니다. 검색을 해도 엉뚱한 결과만 뜨거나, 광고 메일 수백 통에 묻혀 중요한 청구서나 연락을 놓치는 일이 생깁니다. 실제로 메일을 받은편지함에 그냥 쌓아 두는 사람은 52%만 "기록 관리가 된다"고 느낀 반면, 폴더로 분류해 둔 사람은 71%가 만족했습니다. 분류는 깔끔함의 문제가 아니라 못 찾던 것을 찾게 만드는 문제입니다.

내가 동의한 뉴스레터의 역습

받은편지함을 채우는 주범은 의외로 스팸이 아닙니다. 과거의 내가 직접 구독한 뉴스레터들입니다. 평균적인 사람은 25개 이상의 뉴스레터를 구독하지만 정작 꾸준히 여는 건 3~5개뿐입니다. 그 결과 41%가 구독 피로를 호소하고, 구독을 해지하는 사람의 31%가 "받은편지함을 어지럽히는 뉴스레터가 너무 많아서"를 첫 번째 이유로 꼽습니다. 나를 압도하는 건 악성 스팸이 아니라, 선의로 눌렀던 구독 버튼들이 쌓인 결과입니다.

발신자 규칙으로는 못 막습니다

폴더와 필터를 손으로 만들어 본 사람은 알겠지만, 발신자 규칙 기반 필터는 금세 한계에 부딪힙니다. 같은 발신자가 영수증과 광고를 같이 보내고, 처음 보는 발신자는 규칙에 안 걸리기 때문입니다. 그래서 발상을 바꿉니다. 발신자가 아니라 내용으로 분류하면 규칙을 일일이 만들 필요가 없습니다. 메일 한 통의 제목과 본문을 LLM에게 읽히고 "이건 어떤 라벨이 맞냐"고 물으면, 사람이 판단하던 그 일을 대신해 줍니다. 노코드 도구에서 흔히 보던 Gmail + OpenAI 워크플로우가 바로 이 구조인데, 같은 일을 코드로 직접 짜면 훨씬 가볍고 내 맥에서 공짜로 돌릴 수 있습니다.

2. 전체 그림 — 무료 스택으로 만드는 분류 봇

구조는 단순합니다. 다섯 단계가 5분마다 순서대로 돕니다.


[launchd 5분 타이머]  맥이 자동 기동
        |
        v
[1. 폴링]  안 읽고 라벨 없는 새 메일만 검색
        |
        v
[2. 라벨 목록]  지금 있는 라벨들을 LLM에게 알려줌
        |
        v
[3. 분류]  LLM이 제목·본문 읽고 라벨 1개 선택 (없으면 신규)
        |
        v
[4. 부착]  라벨 달기. 광고면 받은편지함에서 내림(아카이브)

컴포넌트별 무료 도구는 이렇게 매칭됩니다.

역할 도구 비고
자동 실행기 macOS launchd 5분 주기, 서버 불필요
메일 접근 Gmail API + 로컬 OAuth 무인 인증 가능
분류 LLM Groq llama-3.3-70b 무료 한도 넉넉, 빠름
라벨 부착 Gmail API label 신규 라벨 생성도 API로

핵심 설계 원칙은 idempotency(여러 번 실행해도 결과가 같은 성질)입니다. 같은 메일을 두 번 처리하지 않도록, 검색 조건에 -has:userlabels(라벨 없는 메일만)를 넣습니다. 이미 분류한 메일은 자동으로 빠지므로 5분마다 돌아도 중복이 없습니다.

3. 단계별 구현

3.1 분류 프롬프트 — 라벨 무한 증식을 막는 게 핵심

이 봇의 품질은 프롬프트가 80%를 결정합니다. LLM에게 기존 라벨 목록과 메일 한 통을 주고, 가장 맞는 라벨 하나를 JSON으로 받습니다. 이때 가장 중요한 규칙은 라벨을 함부로 새로 만들지 않게 하는 것입니다.


규칙:
1. 기존 라벨 중 의미가 통하면 재사용한다.
2. 안 맞을 때만 새 라벨을 만든다. 단 재사용 가능한
   카테고리 수준으로(뉴스레터·채용/구직·결제/영수증 등).
   메일 1통에만 쓸 초협소 라벨은 금지.
3. 광고·홍보는 라벨을 붙이되 받은편지함에서 내린다.
4. 분류 근거를 한국어 한 줄로 남긴다.

새 라벨은 confidence가 일정 값 이상일 때만 만들게 막아 두면, 라벨이 수십 개로 불어나는 사태를 방지할 수 있습니다.

3.2 Gmail 접근 — API + 로컬 OAuth (MCP가 아닌 이유)

처음엔 Claude의 Gmail MCP로 붙이려 했습니다. 편하지만 결정적 문제가 있었는데, 그 이야기는 4번에서 다룹니다. 결론만 말하면 무인 자동화에는 Gmail API + 로컬 OAuth 토큰이 정답이었습니다. OAuth 동의를 한 번만 거치면 토큰이 저장되어, 이후로는 사람 개입 없이 메일에 접근합니다.


SCOPES = ["https://www.googleapis.com/auth/gmail.modify"]
creds = Credentials.from_authorized_user_file("token.json", SCOPES)
service = build("gmail", "v1", credentials=creds)

여기서 권한은 gmail.modify까지만 받습니다. 라벨을 붙이고 받은편지함에서 내리는 데 필요한 최소 권한이고, 메일을 삭제하거나 발송하는 권한은 받지 않습니다.

3.3 분류 — Groq에 메일을 읽힌다

새 메일의 발신자·제목·본문 앞부분을 프롬프트에 끼워 Groq에 보냅니다. Groq의 무료 한도가 넉넉하고 응답이 빨라서, 5분마다 도는 봇에 적합합니다. 응답은 라벨 이름·신규 여부·아카이브 여부·근거가 담긴 JSON 한 덩어리입니다.


{
  "label": "결제/영수증",
  "is_new_label": false,
  "archive_from_inbox": false,
  "reason": "AWS 결제 영수증 메일이라 결제/영수증에 해당."
}

3.4 라벨 부착과 광고 아카이브

LLM이 고른 라벨이 기존에 없으면 먼저 라벨을 생성하고, 그다음 메일에 답니다. 광고·뉴스레터로 판정되면 라벨은 붙이되 받은편지함에서만 내립니다(INBOX 라벨 제거). 삭제는 절대 하지 않습니다. 분류가 잘못돼도 메일은 라벨 검색으로 언제든 찾을 수 있어야 하기 때문입니다.

new-label-created
내용에 맞춰 자동 생성된 라벨들 — 뉴스레터·광고/프로모션·소셜/알림·채용/구직 등이 메일 수와 함께 쌓였습니다 (사용자 본인 실제 화면)

3.5 5분마다 무인 실행 — launchd

마지막으로 이 전체를 macOS launchd에 5분 주기로 등록하면, 내가 아무것도 안 해도 새 메일이 들어오는 족족 분류됩니다. 별도 서버가 필요 없습니다.


<key>StartInterval</key>
<integer>300</integer>   <!-- 300초 = 5분 -->
launchd-log
5분마다 깨어나 새 메일을 분류하는 bot.log (사용자 본인 실제 화면)

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

4.1 라벨이 3개뿐일 때의 콜드스타트 함정

가장 먼저 막힌 곳입니다. 처음 돌렸더니 봇이 모든 메일을 기존의 범용 라벨 하나에 욱여넣고 전부 아카이브해 버렸습니다. 원인은 "신규 라벨을 자제하라"는 규칙이 너무 강했던 탓입니다. 기존 라벨이 두세 개밖에 없는 초기 상태에서는, 그 규칙이 "안 맞아도 일단 있는 것에 넣어라"로 작동해 분류가 아니라 그냥 치워 버리는 결과를 냅니다. 콜드스타트 구간에서는 오히려 새 카테고리 라벨 생성을 허용하고, 신규 생성 문턱(confidence)을 낮춰야 제대로 분류되기 시작합니다.

4.2 MCP는 무인 자동에서 인증이 안 된다

편할 줄 알았던 Gmail MCP가 launchd 무인 실행에서는 인증을 통과하지 못했습니다. 사람이 붙어 있는 대화형 환경을 전제로 하기 때문입니다. 5분마다 사람 없이 도는 봇에는 맞지 않아, 결국 Gmail API + 로컬 OAuth 토큰으로 갈아탔습니다. "편한 도구"와 "무인으로 도는 도구"는 다르다는 걸 체감한 지점입니다.

4.3 나에게 보낸 메모가 뉴스레터로 분류된다

나 자신에게 보내는 메모성 메일(할 일, 아이디어)이 자꾸 엉뚱한 라벨로 분류됐습니다. 내용만 보면 LLM이 헷갈릴 만합니다. 그래서 본인 발신 메일은 LLM을 거치지 않고 곧장 개인메모 라벨로 보내는 결정적 규칙을 앞단에 넣었습니다. LLM이 잘하는 일과, 규칙으로 못 박는 게 나은 일을 나누는 게 핵심입니다.

5. 쌓여 있던 메일도 한 번에 — 백필

라이브 봇이 새 메일만 처리하다 보니, 이미 받은편지함에 쌓여 있던 메일은 그대로 남았습니다. 그래서 날짜 제한 없이 in:inbox -has:userlabels 전체를 페이지 단위로 훑어 한 번에 분류하는 백필 스크립트를 따로 만들었습니다. 약 200통의 백로그를 일괄 처리할 때는, 라이브 봇과 동시에 돌면 충돌하므로 백필 동안에는 launchd를 잠시 멈췄다가 끝나고 다시 켰습니다.

backfill-run
받은편지함에 쌓여 있던 약 200통을 일괄 분류하는 백필 실행 (사용자 본인 실제 화면)

6. 마치며 — 분류 다음의 응용

inbox-labeled
자동 분류로 정리된 받은편지함 — 라벨별로 묶이고 광고는 받은편지함에서 내려갔습니다 (사용자 본인 실제 화면)

받은편지함을 LLM에게 정리시키고 나서 가장 크게 달라진 점은, 받은편지함을 열 때 느끼던 부담이 사라졌다는 것입니다. 광고는 알아서 내려가고, 중요한 메일은 라벨로 묶여 한눈에 들어옵니다. 운영 비용은 사실상 0원입니다.

이 구조가 좋은 이유는 "읽고, 판단하고, 분류한다"는 자동화의 기본 골격을 그대로 담고 있기 때문입니다. 메일 자리에 무엇을 넣든(파일, 메모, 알림) 같은 패턴으로 응용할 수 있습니다. 다음 편에서는 이 분류 결과를 노션 데이터베이스로 모아 주간 단위로 돌아보는 확장을 다룰 예정입니다.

댓글

이 블로그의 인기 게시물

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

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

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