스케줄링 봇에게 전용 캘린더를 부여하세요
요약
Nylas의 Agent Account를 활용하여 전용 캘린더를 가진 스케줄링 에이전트를 구축하는 방법을 설명합니다. LLM이 이메일 의도를 분석하고 에이전트의 독립적인 캘린더를 통해 직접 협상 및 일정을 생성하는 워크플로우를 다룹니다.
핵심 포인트
- 에이전트 전용 캘린더를 통해 사람의 개입 없이 독립적인 일정 협상 가능
- LLM을 활용한 이메일 의도(시간, 시간대, 긴급도) 추출 및 분석
- Nylas API를 통한 에이전트 ID 프로비저닝 및 Free/Busy 조회
- 표준 ICS 초대장 형식을 사용하여 자연스러운 일정 생성 및 알림
스케줄링 링크는 사람이 직접 작업을 하게 만들지만, 자신만의 캘린더를 가진 스케줄링 에이전트(Scheduling Agent)는 협상을 수행합니다. 예약 페이지는 주고받는 과정을 UI로 외주를 주는 방식입니다. 에이전트 모델은 그 과정이 이미 일어나고 있는 곳, 즉 이메일 내에 머물게 하며, 실제 캘린더를 기반으로 한 실제 주소에서 답변을 제공합니다.
설정 방식: 회의 요청이 scheduling@agents.yourcompany.com으로 들어오면, LLM(대규모 언어 모델)이 의도를 분석하고, 에이전트가 자신의 비어 있는 시간/바쁜 시간(free/busy)을 확인하여 시간대를 제안하며, Google Calendar, Microsoft 365, Apple Calendar에 일반 초대장처럼 나타나는 이벤트를 생성합니다. 과정에 개입하는 사람의 메일함도, 위임 권한도, 봇을 설정한 사람의 캘린더를 빌려 쓰는 방식도 필요하지 않습니다.
이 기능은 API를 통해 프로비저닝(Provisioning)하는 호스팅 메일함 및 캘린더 서비스인 Nylas Agent Account에서 작동합니다. Agent Account는 현재 베타 버전이므로, GA(General Availability, 정식 출시) 전까지 일부 변경 사항이 있을 수 있습니다.
ID 프로비저닝 (Provision the identity)
단 한 번의 CLI 명령어나 API 호출로 가능합니다:
nylas agent account create scheduling@agents.yourcompany.com
협상 루프 (The negotiation loop)
전체 튜토리얼에서 이 과정을 엔드 투 엔드(end-to-end)로 연결하는 방법을 설명하지만, 전체적인 흐름은 다음과 같습니다:
- 사람이 에이전트에게 이메일을 보냅니다.
message.created이벤트가 발생하며, 웹훅(webhook)은 요약 필드만 전달하므로 핸들러가 전체 본문을 가져옵니다. - LLM이 지속 시간, 시간대(timezone), 긴급도를 추출합니다.
- 에이전트가 자신의 기본 캘린더에 대해
/calendars/free-busy를 조회하고 3개의 후보 시간대를 답변합니다. - 사람이 하나를 선택하면, 또 다른
message.created이벤트가 발생하고, 에이전트는notify_participants=true옵션으로 이벤트를 생성합니다.
가용성 확인(availability check)은 사람들이 지나치게 복잡하게 생각하는 부분입니다. Free/busy는 특정 기간 동안의 바쁜 시간 블록을 반환합니다. 에이전트는 후보 시간대를 생성하고 겹치는 부분을 필터링합니다:
const freeBusy = await nylas.calendars.getFreeBusy({
identifier: AGENT_GRANT_ID,
requestBody: {
...
제안 답장(proposal reply)은 replyToMessageId가 설정된 표준 전송 엔드포인트(send endpoint)를 통해 발송되므로, 원래 요청의 스레드(thread) 아래에 묶이게 됩니다. 수신자는 에이전트의 주소로부터 온 일반적인 답장을 받게 되며, 릴레이 푸터(relay footer)나 'sent-via' 브랜딩이 표시되지 않습니다.
마지막 플래그가 중요합니다. notify_participants=true를 사용하면 에이전트의 주소로 ICS REQUEST가 발송되며, 수신자의 캘린더 클라이언트는 이를 표준 초대장으로 렌더링합니다.
curl --request POST \
--url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/events?calendar_id=primary¬ify_participants=true" \
--header "Authorization: Bearer <NYLAS_API_KEY>"
...
Alice가 Gmail에서 Yes를 클릭하면, Google은 응답을 에이전트의 메일함으로 다시 보냅니다. 그러면 이벤트의 participants[].status가 자동으로 업데이트되고 event.updated가 발생합니다. 즉, 에이전트는 이메일을 단 하나도 파싱(parsing)하지 않고도 누가 수락했는지 알 수 있습니다. 거절(Decline) 시에는 LLM이 초안을 작성한 "대안 일정은 다음과 같습니다"라는 답장을 트리거할 수 있습니다. 일정 재조정 제안(reschedule proposals)은 POST /events/{id}/send-rsvp와 함께 yes, no, 또는 maybe 상태를 사용하여 응답할 수 있으며, 이는 모든 참가자가 볼 수 있는 표준 ICS REPLY로 발송됩니다.
업데이트, 취소, 그리고 알림 스위치
notify_participants는 일회성 설정이 아니라 호출 시마다 결정되는 사항이며, 이벤트의 전체 생명주기(lifecycle)에 적용됩니다. 변경된 필드를 포함한 PUT /events/{id}는 모든 참가자의 캘린더에서 회의를 업데이트하며, DELETE /events/{id}는 모든 곳에서 해당 이벤트를 삭제합니다. 에이전트가 나중에 공지할 이벤트를 미리 준비(pre-staging)하거나, 아무에게도 이메일을 보내지 않고 과거 데이터를 채워 넣는(backfilling) 등 조용히 처리하고 싶을 때는 notify_participants=false를 전달하세요.
그 반대의 함정도 있습니다. notify_participants=true 설정 없이 이벤트를 삭제하면, 해당 회의가 참가자들의 캘린더에 그대로 남아 있게 됩니다. 스케줄링 에이전트(scheduling agent)를 구현할 때는 특별한 이유가 없는 한 알림과 함께 취소(cancel with notification)를 수행하세요. 캘린더 문서(calendars doc)에서 언급된 시간대(timezone) 관련 주의사항이 하나 더 있습니다. 에이전트 계정(Agent Account)은 사람의 캘린더와 달리 기본 시간대가 설정되어 있지 않으므로, 생성 시 timezone을 전달하거나 에포크(epoch) 형식의 start_time/end_time을 사용하세요.
단순한 주최자가 아닌, 초대받는 자로서의 에이전트
이 과정은 반대로도 작동합니다. 누군가 에이전트를 자신의 회의에 초대하면, 초대장이 메일함(mailbox)을 통해 전달되고 Nylas가 이를 파싱(parsing)합니다. 그러면 에이전트의 상태가 noreply로 설정된 상태로 에이전트의 기본 캘린더에 일치하는 이벤트가 나타납니다. 응답 로직은 전적으로 event.created 웹후크(webhook)를 기반으로 구동하면 됩니다. 이벤트 객체에는 이미 주최자(organizer), 참가자(participants), 그리고 시간이 포함되어 있기 때문입니다. 캘린더 문서에서 주의해야 할 점 하나는, 초대 이메일이 message.created 웹후크도 발생시킨다는 것입니다. 따라서 어떤 웹후크가 로직을 구동할지 결정하고 다른 하나는 무시해야 합니다. 그렇지 않으면 모든 초대를 두 번씩 처리하게 될 것입니다.
유용한 팁 (Field notes)
튜토리얼에서 강조하는 몇 가지 사항 중, 직접 겪어보고 나서야 깨닫기 쉬운 내용들입니다:
- 스레딩(Threading)은 테스트 가능합니다 — 반드시 테스트하세요. Gmail과 Outlook에서 대화 스레드가 유지되려면 답장이
Message-ID,In-Reply-To,References를 보존해야 합니다. Nylas는 아웃바운드(outbound) 시 이를 보존합니다. 출시 전, 직접 요청을 보내 답장이 원래 스레드에 제대로 들어가는지 확인하세요. - 전송 제한(send cap)을 주의하세요. 무료 플랜의 에이전트 계정(Agent Accounts)은 계정당 하루 최대 200개의 메시지를 전송할 수 있습니다. 제안, 확인, 리마인더 등을 처리하는 바쁜 스케줄링 에이전트라면 이 한도에 도달할 수 있습니다. 유료 플랜은 기본적으로 일일 제한이 없지만, 정책(policy)을 통해 더 엄격한 할당량을 설정할 수도 있습니다. 첫 번째 확인 메시지를 놓친 후에 후회하지 말고, 출시 전에 이 부분을 정리하세요.
- ngrok을 통해 터널링하지 마세요. Nylas는 처리량 제한(throughput limiting) 문제로 인해 ngrok의 웹훅(webhook) URL을 차단합니다. 로컬 개발을 위해서는 VS Code 포트 포워딩(port forwarding)이나 Hookdeck을 사용하세요.
- 사람이 IMAP을 통해 감독할 수 있습니다. 권한 부여(grant) 시
app_password를 설정하면, 운영 팀이 Outlook이나 Apple Mail에서 에이전트의 사서함을 열어 답장을 감사하고 개입할 수 있습니다. 모든 IMAP 작업은 API로 다시 동기화됩니다. - 관심사에 따라 캘린더를 분리하세요. 기본 캘린더 외에도 플랜 한도 내에서 추가 캘린더를 생성할 수 있습니다. 예를 들어, 동일한 에이전트에
sales-calls와internal캘린더를 각각 만들 수 있습니다. - 에이전트는 수신되지 않은 메일은 볼 수 없습니다. 스팸함으로 라우팅된 요청은 편지함(inbox)에서
message.created이벤트를 발생시키지 않습니다. 스팸 규칙을 실행 중이라면, 중요한 발신자가 조용히 필터링되지 않도록 규칙 평가(rule evaluations) 엔드포인트를 통해 규칙을 감사하세요. - 잘못된 파싱(parse)은 실제 캘린더의 혼란을 야기합니다. 지연 시간(latency)은 관대할 수 있지만(밀리초 단위가 아닌 분 단위), 의도 추출(intent extraction) 오류는 그렇지 않습니다. 처음 메일을 보내는 사람이나 가치가 높은 미팅의 경우 사람의 확인을 거치도록 하세요.
- 역할당 하나의 에이전트를 사용하세요. 스케줄링, 지원, 아웃리치(outreach)는 각각 다른 할당량과 스팸 민감도를 필요로 합니다. 각 역할을 고유한 정책을 가진 별도의 계정으로 모델링하세요.
현재로서는 시간을 역제안(Counter-proposing)하는 기능이 일급 시민 엔드포인트(first-class endpoint)는 아닙니다. 일반적인 패턴은 RSVP no 또는 maybe를 선택한 뒤 대안을 제안하는 답장을 보내는 방식입니다. 협상이 많이 필요한 다수 참여자 흐름(multi-participant flows)의 경우, Scheduler는 이를 위해 특수 제작되었으며 에이전트 계정(Agent Accounts)과 함께 작동합니다.
이를 테스트해보고 싶다면, 가장 빠르게 시작하는 방법은 테스트용 *.nylas.email 서브도메인을 사용하는 것입니다. 계정을 프로비저닝(provision)하고, notify_participants=true 옵션으로 이벤트를 하나 생성한 뒤, 개인 캘린더에서 이를 수락해 보세요. Yes를 클릭하는 순간 event.updated가 도착하는 것을 확인하는 지점이 바로 이 아키텍처의 핵심을 이해하게 되는 순간입니다. 인간의 확인 없이 LLM이 직접 예약하도록 허용하는 것에 대해 당신의 허용 범위는 어디까지이며, 그 경계선을 어디에 두시겠습니까?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기