이메일 에이전트가 실제로 수행한 작업 감사하기
요약
이메일 에이전트의 디버깅과 감사(Audit)를 위해 메일함 자체를 행동 기록(Action log)으로 활용하는 방법을 설명합니다. 에이전트 계정의 Sent 폴더와 API를 통해 에이전트의 실제 발신 내역과 대화 문맥을 정확히 추적할 수 있습니다.
핵심 포인트
- 에이전트 프레임워크의 의도(Intent) 대신 실제 행동(Action) 기록이 중요함
- 메일함의 Sent 폴더를 활용해 에이전트의 발신 메시지를 감사 가능하게 관리
- API를 통해 에이전트의 발신 내역과 대화 스레드를 쉽게 재구성 가능
- 메시지 헤더를 활용하여 에이전트의 동작 문맥(Context)을 파악할 수 있음
새벽 2시에 제대로 작동하지 않는 이메일 에이전트(email agent)를 디버깅하는 것은 특별한 종류의 비참함을 선사합니다. 애플리케이션 로그에는 LLM이 "후속 조치를 취하기로 결정했습니다"라고 적혀 있습니다. 멋지네요 — 그런데 누구에게요? 무엇을 말하는 건가요? 메시지가 실제로 발송되었나요, 아니면 반송되었나요? 에이전트 프레임워크(Agent frameworks)는 '의도(intentions)'를 기록합니다. 하지만 장애 발생 시 당신에게 필요한 것은 '행동(actions)'에 대한 기록입니다. 이메일 에이전트의 경우, 눈에 잘 띄지 않는 곳에 숨겨진 좋은 소식이 하나 있습니다. 바로 메일함(mailbox) 자체가 그 기록이라는 점입니다.
모든 행동은 메시지를 남깁니다
에이전트 계정(Agent Account, 현재 베타 버전)은 inbox, sent, drafts, trash, junk, archive라는 6개의 시스템 폴더를 가진 실제 호스팅된 메일함입니다. 보안 검토자가 주목해야 할 부분은 sent 폴더입니다. 에이전트가 생성하는 모든 발신 메시지는 실제 메시지 객체로서 그곳에 저장되며, 타임스탬프(timestamp)가 찍히고, 주소가 지정되며, 이미 사용 중인 것과 동일한 Messages API를 통해 가져올 수 있습니다.
이는 메일함으로 들어오는 모든 경로에 적용됩니다. mailboxes guide에서 언급하듯이, IMAP/SMTP를 통해 전송된 모든 것은 API에 나타나며, API를 통해 전송된 모든 것은 메일 클라이언트의 Sent 폴더에 나타납니다. 프로토콜 트래픽(protocol traffic)과 API 트래픽(API traffic) 사이에는 분리가 없으므로, 에이전트(또는 자격 증명을 보유한 공격자)가 복사본을 남기지 않고 통과할 수 있는 뒷문은 존재하지 않습니다.
감사 무결성(audit integrity)을 위해 중요한 속성이 하나 더 있습니다. 발신 기록에는 해당 권한(grant) 자체의 주소가 찍힙니다. 에이전트 계정은 다른 신원을 사칭할 수 없으므로, sales-agent@의 sent 폴더에 있는 메시지는 해당 에이전트로서 발송된 것이 확실합니다.
기록 읽기
지난 하루 동안 에이전트가 무엇을 했는지 검토하는 것은 해당 권한(grant)에 대해 단 한 번의 API 호출로 가능합니다:
curl --request GET \
--url "https://api.us.nylas.com/v3/grants/$GRANT_ID/messages?limit=50&in=sent" \
--header "Authorization: Bearer $NYLAS_API_KEY"
또한 표준 Message-ID, In-Reply-To, References 헤더를 사용하여 답장이 대화(conversations)로 그룹화되기 때문에, 특정 발신을 중심으로 에이전트가 무엇을 받았고, 무엇을 말했으며, 어떤 응답이 돌아왔는지에 대한 전체적인 주고받기 과정을 재구성할 수 있습니다:
curl --request GET \
--url "https://api.us.nylas.com/v3/grants/$GRANT_ID/threads/$THREAD_ID" \
--header "Authorization: Bearer $NYLAS_API_KEY"
이러한 스레드(thread) 뷰는 "에이전트가 14개의 메시지를 보냈다"와 "에이전트가 대답할 수 없는 질문에 누군가 계속 답장을 보냈기 때문에 14개의 메시지를 보냈다" 사이의 차이를 만들어냅니다. 문맥(Context)이야말로 로그를 설명(explanation)으로 바꾸는 핵심입니다.
발신 결과(Send outcomes) 또한 이벤트입니다
에이전트가 발신을 시도했다는 것을 아는 것과 실제로 전달되었다는 것을 아는 것은 다릅니다. 에이전트 계정(Agent Accounts)의 SMTP 경로는 엔드 투 엔드(end to end)로 관리되므로, 모든 아웃바운드(outbound) 메시지에 대한 전달 가능성(deliverability)이 웹훅(webhooks)을 통해 전달됩니다:
| 트리거 (Trigger) | 알려주는 내용 |
|---|---|
message.send_success | 수신 측 서버가 메시지를 수락함 |
| ... |
이러한 이벤트들을 애플리케이션 로그와 동일한 곳으로 파이프라인(Pipe) 연결하면, 에이전트 자체의 로깅이 정직하거나 완전한지에 의존하지 않는 서버 측 에이전트 결과 스트림을 얻을 수 있습니다. 인바운드(Inbound) 메일도 동일한 방식으로 message.created 이벤트를 생성합니다. 단, 약 1MB를 초과하는 본문은 본문이 생략된 message.created.truncated로 도착하므로, 이 경우에는 ID를 통해 전체 메시지를 가져와야 합니다.
규칙 평가(Rule evaluations) 또한 감사를 위해 로그에 기록됩니다. 정책 규칙(policy rule)이 메시지를 차단, 라우팅 또는 플래그(flag) 지정할 때, 어떤 규칙이 왜 실행되었는지에 대한 기록이 남습니다. 이는 새벽 2시에 마주할 수 있는 또 다른 질문인 "왜 이 메시지가 받은 편지함에 도달하지 못했는가?"에 대한 답을 제공합니다. 각 권한(grant)별로 해당 기록을 가져올 수 있습니다:
curl --request GET \
--url "https://api.us.nylas.com/v3/grants/$GRANT_ID/rule-evaluations?limit=50" \
--header "Authorization: Bearer $NYLAS_API_KEY"
인시던트 리뷰(incident review)의 구조
이 조각들을 모으면 "에이전트가 무엇을 했는가?"에 대한 조사는 정형화된 형태를 갖추게 됩니다. 예를 들어, 고객이 어제 에이전트가 이상한 것을 보냈다고 불평하는 상황을 가정해 봅시다:
received_after를 어제로 제한하여 해당 권한(grant)에 대한 보낸 편지함(sent folder) 목록을 나열합니다. 이제 프레임워크가 기록한 것이 아니라, 실제로 사서함에서 나간 모든 후보 메시지를 확보하게 됩니다.- 의심스러운 메시지의
thread_id를 통해 스레드(thread)를 가져옵니다. 이제 고객이 어떤 내용을 작성하여 답장을 유발했는지, 그리고 에이전트의 응답이 문맥상 적절했는지 확인할 수 있습니다. - 해당
message_id에 대해 기록된 **전송 결과 웹훅(send-outcome webhooks)**을 확인합니다. 메시지가 전달되었는지, 실패했는지, 아니면 반송(bounce)되었는지 확인하십시오. - 동일한 시간 범위에 대한 **규칙 평가(rule evaluations)**를 추출하여, 메시지가 나가는 과정에서 정책 규칙(policy rule)이 적용되었는지 확인합니다.
네 번의 조회, 하나의 권한 ID(grant ID)만으로 애플리케이션 로그를 뒤지는 고고학적 작업 없이 해결됩니다. 동일한 방식은
- API 전송 시 오픈 트래킹(open tracking) 불가. 에이전트 계정(Agent Account)에서
POST /messages/send를 통해 직접 전송된 메시지에 대해서는message.opened및message.link_clicked이벤트가 발생하지 않습니다. 메시지가 수락되었는지 또는 반송(bounce)되었는지는 알 수 있지만, 사람이 실제로 읽었는지 여부는 알 수 없습니다. - 메일함은 통신 기록을 남길 뿐, 추론 과정을 남기지 않습니다. 모델이 왜 해당 메시지를 보내기로 결정했는지는 여전히 애플리케이션에서 로그를 남겨야 하는 작업입니다. 프롬프트(prompt), 컨텍스트(context), 그리고 결정 사항은 결과물인
message_id를 키(key)로 하여 귀하의 자체 트레이스(trace)에 기록되어야 두 기록을 결합할 수 있습니다. - 보관 정책(Retention)이 적용됩니다. 메일함 콘텐츠는 귀하의 플랜 및 정책에 따른 보관 기간을 따릅니다. 무료 플랜의 경우 받은 편지함은 30일, 스팸함은 7일입니다. 따라서 장기적인 컴플라이언스(compliance)를 위해 필요한 데이터는 보관 기간이 만료되기 전에 내보내기(export) 하십시오.
또한 실질적인 한계치도 기억해야 합니다. 아웃바운드(outbound) 메시지는 40MB로 제한되며, 수신 측 서버는 종종 이보다 낮은 약 25MB의 제한을 적용합니다. 해당 크기에 근접할 때 발생하는 send_failed 오류는 에이전트의 문제가 아니라 페이로드(payload)의 문제일 가능성이 높습니다.
검토를 사후 분석이 아닌 습관으로 만드세요
현재 이메일 에이전트를 운영 중이라면 다음 테스트를 해보십시오. 어제 에이전트가 보낸 모든 메시지와 각 메시지 주변의 스레드 컨텍스트(thread context)를 5분 이내에 추출할 수 있습니까? 만약 그렇지 않다면, 위에서 설명한 보낸 편지함(sent-folder) 쿼리를 연결하는 것이 귀하가 얻을 수 있는 가장 빠른 관측성(observability) 개선책입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기