Teams 봇 입문: Bot Framework와 Graph API의 역할 구분
실무 입문 #6에서 Teams로 알림을 보내는 세 경로를 다루면서, 본격적인 봇은 별도 주제로 미뤘습니다. 이번 글에서 다룹니다. 결론을 먼저 적으면, Teams에서 “봇"이라는 정체성으로 대화하려면 Graph API만으로는 안 되고 Bot Framework(Azure Bot)가 필요합니다. 이 글은 왜 그런지, 봇은 어떤 구조로 도는지, 그리고 Graph가 봇의 어디를 보완하는지를 정리합니다.
왜 Graph만으로는 봇이 안 되는가 #
Graph에도 채팅 메시지를 보내는 API가 있으니 “이걸로 봇을 만들면 되지 않나"라는 생각이 자연스럽습니다. 두 가지 벽이 있습니다.
- 정체성: Graph로 보내는 메시지는 토큰의 주인(사용자 또는 자동화 계정) 이름으로 갑니다. 봇 이름과 아바타로, 사람과 구분되는 주체로 말하는 것은 Bot Framework를 통해야 합니다.
- 앱 권한 제약: 채널·채팅에 일반 메시지를 보내는 Graph 호출은 애플리케이션 권한이 사실상 막혀 있습니다(마이그레이션 전용 권한 제외 — #6에서 다룬 그 제약입니다). 무인 서비스가 자기 이름으로 대화하는 구조 자체가 Graph의 설계 밖입니다.
그래서 역할이 나뉩니다. 대화(수신, 응답, 카드, 버튼)는 Bot Framework가 맡고, 조직 데이터(사용자 조회, 파일, 일정)는 봇의 백엔드에서 Graph로 호출합니다. 두 기술은 경쟁이 아니라 한 앱 안에서 분업합니다.
봇의 동작 구조: 웹훅을 받는 웹 서비스입니다 #
봇의 실체는 신비롭지 않습니다. HTTPS 엔드포인트 하나를 가진 웹 서비스입니다.
- Azure Bot 리소스 등록: 봇의 신원(앱 ID)과 메시징 엔드포인트 URL을 등록합니다.
- 메시지 수신: 사용자가 봇에게 말을 걸면, Bot Framework 커넥터 서비스가 그 활동(activity)을 JSON으로 내 엔드포인트에 POST합니다.
- 응답: 내 서비스가 SDK(C#, JavaScript, Python 지원)로 응답 활동을 돌려보냅니다.
- Teams 앱 매니페스트: 봇을 Teams 앱으로 포장해 조직 카탈로그에 올리면 사용자가 설치할 수 있게 됩니다.
개발 언어 관점에서 SDK가 편하지만, 커넥터 서비스의 REST API를 직접 호출할 수도 있으므로 언어에 갇히지는 않습니다. 로컬 개발은 터널링(개발용 터널)으로 엔드포인트를 노출해 테스트하는 것이 표준 흐름입니다.
선제 메시지: 봇이 먼저 말을 거는 법 #
알림 봇의 핵심 요구는 “사용자가 말을 걸기 전에 봇이 먼저 보낸다"입니다. 여기에 Bot Framework의 중요한 규칙이 있습니다. 선제 메시지(proactive message)를 보내려면 대화 참조(conversation reference)가 필요합니다. 대화 참조는 봇이 그 사용자·채널과의 활동에서 얻는 주소 정보로, 보통 사용자가 봇과 한 번은 상호작용해야(또는 봇이 설치되는 이벤트를 받아야) 확보됩니다.
그러면 “설치도 안 한 사용자에게 처음으로 보내려면?“이 남는데, 여기서 Graph가 봇을 보완합니다. Graph의 선제 설치 API(userteamwork/installedApps, 앱 권한)로 봇 앱을 대상 사용자에게 설치하면, 설치 이벤트(conversationUpdate)가 봇 엔드포인트로 날아오고, 거기서 대화 참조를 확보해 첫 메시지를 보낼 수 있습니다. 전사 공지형 봇이 쓰는 표준 패턴입니다.
운영 주의점은 빈도입니다. 짧은 시간에 선제 메시지를 몰아 보내면 채널 정책에 걸릴 수 있고, Teams 쪽 전송 속도 제한도 있으므로 대량 발송은 큐로 완만하게 흘려보내는 설계가 필요합니다.
읽기: 멘션 없이 채널 메시지를 받는 RSC #
기본적으로 채널에 추가된 봇은 @멘션된 메시지만 받습니다. 채널의 모든 메시지를 들어야 하는 봇(모더레이션, 자동 태깅, AI 어시스턴트)은 RSC(Resource-Specific Consent, 리소스별 동의) 권한을 앱 매니페스트에 선언합니다. ChannelMessage.Read.Group 류의 권한을 팀 소유자가 설치 시점에 동의하는 방식으로, 테넌트 전체가 아니라 그 팀 하나에 한정된 동의라는 것이 이름의 의미입니다. 전체 메시지 수신은 강력한 만큼, 무엇을 저장하는지에 대한 사내 고지가 따라와야 합니다.
어댑티브 카드: 알림에서 상호작용으로 #
봇이 텍스트만 보낸다면 웹훅과 다를 것이 없습니다. 봇의 진짜 힘은 어댑티브 카드(Adaptive Card)입니다. JSON으로 선언하는 카드 UI에 입력 필드와 버튼을 넣을 수 있고, 사용자가 버튼을 누르면 그 액션이 봇 엔드포인트로 돌아옵니다. “승인/반려 버튼이 달린 결재 알림”, “폼을 채워 제출하는 신청 카드"가 이것으로 만들어집니다. 알림에 버튼 하나가 붙는 순간부터는 웹훅·Graph 전송으로는 처리할 수 없고 봇이 필요해진다는 것이 도구 선택의 경계선입니다.
그래서 무엇으로 시작하나: 요구 수준별 사다리 #
Teams 메시징 요구를 낮은 단계부터 쌓으면 이렇게 됩니다.
- 채널에 단방향 알림만: Workflows 수신 웹훅으로 충분합니다(실무 입문 #6).
- 활동 피드 알림: Graph의 activity feed notification API로 배지형 알림을 보낼 수 있습니다.
- 봇 이름으로 보내는 알림, 최소 상호작용: 알림 전용 봇을 만듭니다. 수신 로직 없이 선제 메시지 + 카드만으로 구성됩니다.
- 대화·버튼·폼: 본격 Bot Framework 봇 + 백엔드에서 Graph로 조직 데이터 조회.
한 단계 아래로 충분한 요구에 위 단계를 도입하는 것이 Teams 봇 프로젝트가 무거워지는 가장 흔한 원인입니다. 사다리의 어느 칸이 필요한지부터 정하는 것이 설계의 첫 질문입니다.
정리 #
- Graph만으로는 봇이 안 됩니다. 봇 정체성과 대화는 Bot Framework의 일이고, 일반 메시지 전송의 앱 권한 제약도 그대로입니다.
- 봇의 실체는 Azure Bot 리소스에 등록된 HTTPS 엔드포인트입니다. 활동을 JSON으로 받고 SDK로 응답합니다.
- 선제 메시지에는 대화 참조가 필요하고, 미설치 사용자는 Graph의 선제 설치 API로 설치 이벤트를 만들어 확보합니다. 대량 발송은 속도 제한을 전제로 설계합니다.
- 멘션 없이 채널 전체를 읽으려면 RSC 권한을 매니페스트에 선언하고 팀 단위 동의를 받습니다.
- 버튼·폼이 필요한 순간이 봇 도입의 경계선입니다. 그 전 단계는 웹훅·활동 피드 알림으로 가볍게 해결합니다.