Pipedrive overdue deal을 매일 아침 Google Chat으로 AE에게 자동 push하는 봇을 FastAPI + Claude + MCP로 구축했습니다.
pull(자연어 조회)과 push(능동적 알림)를 처음부터 별도 레포로 분리하는 것이 핵심입니다.
LLM에게 계산을 맡기지 말고, 결정론적 집계는 코드로 빼는 구조가 안정성과 속도를 동시에 잡습니다.
B2B SaaS 세일즈 조직에서 가장 조용하게 새는 구멍은 “아무도 안 물어봤기 때문에 아무것도 안 한” 딜입니다.
리더가 매주 Pipedrive를 직접 열어 AE별 overdue 현황을 파악하던 루틴을 없애기 위해 push 에이전트를 만들었습니다.
결과적으로 리더의 수동 점검이 자동화로 흡수됐고, AE는 매일 아침 자기 핫딜을 브리프로 받아 FU 행동으로 즉시 연결할 수 있게 됐습니다.
왜 이걸 만들었나
6인 세일즈팀 기준으로 리더가 AE 한 명당 10~20분씩 브리핑을 받지 않으면 딜 진척을 알 수 없는 구조였습니다.
운영 인지부담이 리더에게 집중되고, AE는 “오늘 뭐부터 해야 하나”를 리더 입을 통해서만 알 수 있었습니다.
그 사이 FU 골든타임을 놓치는 케이스가 쌓이는 건 시간문제였습니다.
이커머스 SaaS 조직에서도 비슷한 패턴을 본 적이 있습니다. CRM 데이터는 있는데, 아무도 먼저 꺼내지 않으면 그냥 묻히는 구조였습니다.
트리거는 단순했습니다. “AI를 질문에 답하는 용도로만 쓰는 한, 사람이 질문할 시점을 잊으면 끝이다.”
pull 패턴의 한계를 인정하고, 조건이 충족되면 먼저 신호를 밀어주는 push 구조로 전환하기로 했습니다.
준비물
- Pipedrive 계정 (API 토큰 발급 권한 필요)
- Google Cloud 프로젝트 (Cloud Run 배포 권한 + Google Chat API 활성화)
- Google Sheets (AE별 유저 매핑 시트 — 이메일·Chat userId 컬럼 포함)
- Python 3.11 이상 로컬 환경 (FastAPI + httpx 설치)
- Anthropic API 키 (Claude Sonnet 사용)
- Google Chat 봇 등록 (Webhook 또는 App 방식 중 선택)

1. pull과 push를 처음부터 두 레포로 분리하기
자연어 조회(pull)와 능동적 알림(push)은 도구 권한 경계와 배포 사이클이 다릅니다.
단일 레포에 합치면 배포할 때마다 권한 모델과 스케줄이 섞여 유지 비용이 커집니다.
처음부터 두 레포로 나누는 것이 이 프로젝트의 첫 번째 설계 결정입니다.
# 레포 구조 (권장)
pipedrive-mcp/ # pull — Claude Code Plugin, 자연어 조회 전용
tools/ # Pipedrive read 도구 12개
main.py
team-agent/ # push — Google Chat 봇, 능동적 알림 전용
scheduler.py # APScheduler: 매일 09:00 KST 트리거
pipedrive_client.py # overdue deal 조회 (결정론적 집계)
sheets_client.py # AE 유저 매핑 조회
chat_sender.py # Google Chat DM push
agent.py # Claude — 메시지 정리만 담당pull 레포는 사람이 질문할 때 응답하는 용도, push 레포는 조건 충족 시 먼저 말 거는 용도입니다.
배포 사이클도, 권한 스코프도 다르게 관리할 수 있어 운영 부담이 줄어듭니다.
2. Pipedrive overdue deal 집계를 결정론적으로 처리하기
LLM에게 집계·계산을 맡기면 숫자가 틀립니다. 이 프로젝트에서 직접 겪은 첫 번째 실패입니다.
결정론적 계산은 반드시 Python 코드로 분리하고, Claude는 “어떤 도구를 부를지”와 “메시지를 어떻게 정리할지”만 담당하도록 구조를 바꿨습니다.
# pipedrive_client.py — overdue deal 조회 (결정론적)
import httpx
from datetime import date
PIPEDRIVE_API = "https://api.pipedrive.com/v1"
def get_overdue_deals(api_token: str, owner_id: int) -> list[dict]:
today = date.today().isoformat()
params = {
"api_token": api_token,
"status": "open",
"user_id": owner_id,
}
resp = httpx.get(f"{PIPEDRIVE_API}/deals", params=params, timeout=10)
resp.raise_for_status()
deals = resp.json().get("data") or []
# expected_close_date 기준 overdue 필터 (LLM 판단 없이 코드로 처리)
return [
d for d in deals
if d.get("expected_close_date") and d["expected_close_date"] < today
]이 함수 결과를 Claude에게 넘기면, Claude는 딜 목록을 읽고 자연스러운 한국어 브리프 메시지로 정리하는 역할만 합니다.
계산은 코드, 문장 생성은 LLM으로 역할을 나누는 것이 안정성의 핵심입니다.
3. Google Chat DM으로 AE별 브리프 push 보내기
Google Sheets에 AE 이메일과 Chat userId를 매핑해 두고, 스케줄러가 매일 아침 09:00 KST에 AE별로 개인 DM을 전송합니다.
Claude가 정리한 브리프 텍스트를 Chat API로 밀어주는 구조입니다.
# chat_sender.py — Google Chat DM push
from googleapiclient.discovery import build
from google.oauth2 import service_account
SCOPES = ["https://www.googleapis.com/auth/chat.bot"]
def send_dm(user_id: str, message: str, credentials_path: str) -> None:
creds = service_account.Credentials.from_service_account_file(
credentials_path, scopes=SCOPES
)
service = build("chat", "v1", credentials=creds)
# DM space 생성 후 메시지 전송
space = service.spaces().create(
body={"singleUserBotDm": True},
requestId=f"dm-{user_id}"
).execute()
service.spaces().messages().create(
parent=space["name"],
body={"text": message}
).execute()scheduler.py에서 APScheduler로 `cron(hour=0, minute=0)`(UTC 기준 = KST 09:00)을 설정하고,
AE 목록을 Sheets에서 불러와 루프를 돌리면 전체 팀에 순차 push됩니다.
Cloud Run에 배포하면 서버 관리 없이 스케줄을 유지할 수 있습니다.
마무리
목표: Pipedrive overdue deal을 매일 아침 Google Chat DM으로 AE별 자동 push하는 봇을 만든다.
기술 스택: Python 3.11, FastAPI, APScheduler, Anthropic Python SDK (Claude Sonnet), Pipedrive REST API v1, Google Chat API v1 (service account), Google Sheets API v4, Google Cloud Run (서버리스 배포), 환경변수: PIPEDRIVE_API_TOKEN / ANTHROPIC_API_KEY / GOOGLE_CREDENTIALS_PATH
단계 순서:
1. 레포를 pipedrive-mcp(pull)와 team-agent(push) 두 개로 분리하고 각각 requirements.txt 작성
2. pipedrive_client.py에서 expected_close_date 기준 overdue deal을 결정론적으로 필터링 — curl로 응답 확인
3. sheets_client.py에서 AE 이메일-Chat userId 매핑 테이블을 읽어 dict 반환 — 로컬 print로 검증
4. agent.py에서 overdue deal 리스트를 Claude에게 전달해 한국어 브리프 메시지만 생성하도록 프롬프트 작성
5. chat_sender.py에서 singleUserBotDm space 생성 후 메시지 전송 — 본인 userId로 테스트 DM 발송
6. scheduler.py에서 APScheduler cron(hour=0, minute=0 UTC)으로 AE 루프 실행 — 로컬 즉시 실행으로 전체 흐름 검증
7. Cloud Run에 배포하고 Cloud Scheduler로 트리거 등록 — 다음날 아침 실제 push 수신 확인이 구조의 에센스는 도구 하나로 구현할 수 있습니다.
Zapier나 Google Apps Script로 “지난 7일간 활동 없는 deal 3개를 매일 09:00에 Slack push”하는 것부터 1주일 돌려보세요.
팀 행동이 바뀌는지 관찰하는 것이 먼저입니다. 스택은 그 다음에 고민해도 충분합니다.
🙂
