구글 시트에 업체 목록(업체명·힌트)만 넣으면, 이메일 자동 탐색 → 업체별 맞춤 제안 메일 작성 → (사람 승인) → 일괄 발송 → 답장 분류·후속 대응까지 이어지는 협찬/제안 메일 자동화 도구입니다. 성균관대학교 총학생회 S'PEAK 대외협력국의 협찬·부스 입점 제안 업무를 염두에 두고 만들었습니다.
바로 사용하기 — 배포된 앱에 접속해 바로 실행할 수 있습니다: https://automail1398.streamlit.app/
- LLM: Google Gemini (
langchain-google-genai) - 웹 검색: Tavily (
langchain-tavily) - 오케스트레이션: LangGraph — supervisor 멀티 에이전트 그래프 + 답장 그래프
- 연동: Google Sheets(업체/초안 저장), Gmail(발송·답장 조회·라벨)
- UI: Streamlit 웹 대시보드 (
automail_st.py) —자동 실행/후속 대응2개 탭
구분 기준: START 이후의 경로(분기)를 누가 정하는가 — LLM 이 정하면 Agent, 코드/사람이 정하면 Workflow. 불확실성이 큰 부분만 Agent 에 맡기고, 예측 가능성·안전이 중요한 부분은 Workflow 로 고정했습니다.
| 구성 요소 | 형식 | 키워드 |
|---|---|---|
supervisor (supervisor.py) |
Agent | 동적 라우팅 (search/write/approve_send/finish), 재시도 지시, 발송까지만 관리 |
검색 에이전트 (search_agent.py) |
Agent (ReAct) | 도구 자율 선택·반복 (web_search/open_website), 업체별 탐색 전략 |
답장 에이전트 (reply_agent.py) |
Agent (ReAct) | 분류 판단, 조사 필요 여부 자율 결정, 필요 시에만 web_search |
작성 모듈 (writer_agent.py) |
Workflow (LLM 워커) | 도구·루프 없음 — LLM 은 도입부만, 템플릿은 코드가 보장 |
발송 (send_email) |
Workflow (도구) | 판단 없는 결정적 실행, 사람 승인 필수 |
grounding (tools.CandidateStore) |
Workflow | 후보 정확 일치 + 공식 도메인 목격 검증 — 환각 차단 |
발송 승인 (interrupt()) |
Workflow (사람) | 발송 최종 결정권은 항상 사람 |
후속 대응 탭 흐름 (graph.py) |
Workflow | 고정 그래프 START → reply → END, 사람이 버튼으로 실행 |
[자동 실행 탭] START → supervisor → ? → supervisor → ? … ← 매 턴 LLM 이 다음 노드 결정 (Agent 지휘)
├→ search (Agent/ReAct)
├→ write (Workflow 워커)
└→ approve_send (사람 승인 → 발송 도구)
[후속 대응 탭] START → reply → END ← 경로 고정 (Workflow 지휘, 사람 트리거)
└→ reply_agent (Agent/ReAct)
핵심: 지휘 구조와 일꾼의 형식은 별개입니다.
- supervisor 는 검색 에이전트(Agent)와 작성 워커(Workflow)를 함께 오케스트레이션합니다.
- reply_agent 는 그 자체로는 Agent 지만, 사람이 지휘하는 Workflow 흐름 안에서 실행됩니다.
- supervisor 는 답장을 확인하지 않습니다 — 답장은 실행 종료 후 며칠 뒤에 오므로, 발송 이후의 시간 축은 사람이 후속 대응 탭에서 지휘합니다.
- 목표: 협찬/제휴 문의 이메일 발견 + 신뢰도 판정 (한국 업체/한국 공식몰만)
- Agent: 도구 조합·순서·반복을 LLM 이 결정 (도메인 확인 → 한정 검색 → 직접 조회 → 통합 검색은 '권장'일 뿐),
max_steps=6 - Workflow: grounding — 도구가 수집한 후보와 정확 일치해야 채택, 공식 도메인에서 실제 목격된 경우에만
HIGH(검증 O). 그 외REVIEW/NONE - 관측: 도구 호출 체인(
→)이 카드에 표시
- 제목: 고정 템플릿 / 본문: 담당자 소개 + (LLM) 도입부 + 제안 블록(굵게) + 맺음말·서명
- 엄밀히는 에이전트가 아닌 LLM 워커: 도구·루프 없이 LLM 1회 호출을 템플릿 코드가 감쌈 — 형식 보장이 목적
- Agent: 수락/거절/대기 분류, 조사 필요 여부 자율 판단 — 사실 질문(재학생 수, 행사 규모 등)이면
web_search호출·재검색(최대 3턴), 단순 답장이면 도구 0회 - Workflow: 답장 조회는 결정적 Gmail 쿼리(발신자+제목 격리), 답장 없으면 LLM 없이
no_reply, 미확인 사실은 "담당자 확인 후 회신", 발송은 사람
한 줄 요약: supervisor 에이전트가 검색 에이전트와 작성 에이전트를 멀티 에이전트 오케스트레이션한다.
- 매 턴 상태표 →
search / write / approve_send / finish결정, 재시도 시 지시(instruction) 변경 - 범위: 발송까지 (답장 확인은 후속 대응 탭 담당)
- 안전장치(Workflow):
interrupt()사람 승인 없인 발송 불가, 검색 재시도 상한(2회)·턴 상한(12회), 무효 결정 시 결정적 폴백
- Gmail 발송 + PDF 첨부 +
{행사명}/{이름}라벨,**굵게**HTML 변환, 후속 메일은 원본 스레드에 회신(threadId/In-Reply-To)
automail/
├── automail_st.py # 웹 대시보드 (Streamlit) — 실제 배포·구동되는 메인 앱: 자동 실행/후속 대응 2탭 + 테스트 모드 토글 · 설정/첨부 관리
├── agents/
│ ├── __init__.py # agents 패키지 초기화 (import 진입점)
│ ├── supervisor.py # supervisor 멀티 에이전트 그래프 (Agent — 동적 라우팅 + 발송 승인 interrupt)
│ ├── search_agent.py # ① 검색 에이전트 (Agent/ReAct)
│ ├── writer_agent.py # ② 작성 에이전트 (Workflow + LLM 도입부)
│ ├── reply_agent.py # ③ 답장 에이전트 (Agent/ReAct)
│ ├── tools.py # 에이전트 도구 (web_search/open_website) + grounding 후보 저장소 (Workflow)
│ ├── react.py # 경량 ReAct 루프 실행기 (도구 선택 루프 + 반복 상한 + trace)
│ ├── graph.py # 후속 대응 탭용 답장 그래프 (Workflow: START → reply → END)
│ ├── google_clients.py # OAuth + Sheets/Gmail + send_email(발송 도구)
│ ├── sheet_sync.py # 시트 저장 시 수동 입력 이메일 보존(병합) — 순수 함수
│ ├── config.py # .env 로드 + Gemini LLM 팩토리
│ └── state.py # 상태 정의(WorkflowState/Company)
├── requirements.txt
├── .env # GEMINI_API_KEY, TAVILY_API_KEY (직접 작성)
├── credentials.json # Google OAuth 인증서 (직접 준비)
└── token.json # 첫 인증 시 자동 생성
구글 시트가 중심 저장소입니다. 에이전트가 시트를 읽고 쓰며, Gmail 로 실제 메일을 주고받습니다.
[구글 시트] 업체명 · 힌트 · 이메일 · 제목 · 본문 · 발송여부
│ (이메일 오른쪽 세 칸이 자동 지정: 이메일 F → 제목 G · 본문 H · 발송 I)
│ 발송 여부는 O/X 로 기록되며, 후속 대응 탭은 발송 O 인 업체만 조회한다
▼
[자동 실행 탭 — supervisor 가 지휘, 발송까지]
검색(이메일 탐색·등급) → 작성(초안) → 발송 승인 대기(사람) → 발송(승인분만) → 종료
· 각 단계 결과는 시트에 자동 저장 (승인 대기 시점 + 종료 시점)
[후속 대응 탭 — 사람이 지휘] (답장이 실제로 도착한 뒤)
전체 분류 → 업체 선택 → 답장 원문·후속 초안 확인 → 수정 → 발송(원본 스레드에 회신)
상단 설정 패널에서 스프레드시트 ID·범위(업체명/힌트/이메일)·제안 내용·행사명·행사 일자·담당자·캠퍼스를 입력하고 PDF 를 첨부합니다. 상단 헤더의 테스트 모드 토글을 켜면 실제 업체 대신 지정한 테스트 주소로 발송해 미리 확인할 수 있습니다.
- 자동 실행 시작 — 시트의 기존 이메일·초안을 무시하고 검색부터 전체 파이프라인을 새로 실행합니다.
- 검색 건너뛰기 — 시트에 기재된 업체명·이메일을 그대로 사용해 초안 작성부터 진행합니다 (이메일이 빈 업체는 제외).
- 진행 로그에 supervisor 의 결정과 각 에이전트의 작업이 실시간 표시됩니다.
- 초안이 준비되면 발송 승인 대기 카드가 뜨며 전체가 멈춥니다. 초안을 수정하고 보낼 업체만 체크해 "선택한 업체 발송 승인" — 이 승인 없이는 어떤 메일도 나가지 않습니다. "모두 건너뛰기" 는 이번 실행에서 발송을 전부 보류합니다(초안은 시트에 저장됨).
- 테스트 모드 설정을 발송 때와 동일하게 두고 "전체 분류" — 답장 에이전트가 업체별 답장을 조회·분류합니다.
- 왼쪽에서 업체를 선택하면 답장 원문과 후속 초안이 표시됩니다 (문의성 답장이면 에이전트가 필요 시 웹 검색으로 사실 확인 후 작성).
- 초안을 검토·수정하고 발송 — 원본 스레드에 답장으로 나갑니다.
- 테스트 모드로 본인 메일에 먼저 보내본 뒤 실제 발송하세요.
- Gmail 일일 발송 한도: 일반 계정 약 500통/일.
- OAuth 앱이 "테스트" 상태면
token.json은 약 7일 후 만료됩니다. 만료 시token.json을 삭제하고 다시 실행하면 재로그인됩니다. - Gemini/Tavily 무료 티어는 분당 요청 한도가 있습니다. 자동 실행은 supervisor 판단 + 에이전트 도구 반복으로 업체당 LLM 호출이 기존 워크플로보다 많으므로, 한도에 걸리면 처리 개수를 줄여서 실행하세요.