Nylas API를 사용하여 이메일의 인용된 답장 정리하기
요약
Nylas API를 활용하여 이메일 답장 체인에서 불필요한 인용문, 서명, 법적 고지 사항 등 노이즈를 제거하는 방법을 설명합니다. 온디맨드 API 호출 방식과 웹훅을 통한 자동화 방식 두 가지 구현 경로를 다룹니다.
핵심 포인트
- 이메일 내 중복된 인용 텍스트 제거로 LLM 토큰 비용 절감
- 온디맨드(On-demand) API를 통한 특정 메시지 즉시 정리
- 웹훅(Webhook)을 활용한 실시간 메시지 자동 정리 파이프라인 구축
- Nylas CLI를 이용한 정리 결과 확인 및 테스트 방법
다섯 개의 메시지로 구성된 답장 체인을 열고, 그중 실제로 새로운 내용이 얼마나 되는지 살펴보세요. 상단에 한두 문장이 있고, 그 뒤에는 인용된 기록의 벽, 서명, 법적 고지 사항, 그리고 "Best regards."가 이어집니다. 훑어보는 사람에게 이러한 노이즈는 그저 잡음일 뿐입니다. 코드에게는 상황이 더 나쁩니다. 요약을 위해 해당 덩어리(blob)를 언어 모델 (Language Model)에 입력하면, 모델이 이미 본 인용 텍스트에 대해 토큰 비용을 지불하게 되며, 이를 미리보기로 보여줄 때도 모든 메시지에 동일한 인용 체인이 나타나게 됩니다. 이메일을 정리하면 의미 있는 텍스트만 추출할 수 있으며, 이 포스트에서는 이를 Email API 및 CLI와 연결하는 방법을 다룹니다.
이 글은 엔드포인트 투어라기보다는 실제 활용 사례를 다루며, clean conversation 엔드포인트와 정리된 메시지 웹훅 (webhook)을 두 가지 관점에서 다룹니다. 즉, 백엔드에서 호출하는 HTTP API와 터미널에서 메시지를 정리하기 위한 nylas CLI입니다. 저는 CLI를 담당하고 있으므로, 아래의 명령어들은 정리 결과가 실제로 무엇을 반환하는지 확인하고 싶을 때 제가 사용하는 것들입니다.
메시지를 정리하는 두 가지 방법
두 가지 경로가 있으며, 어떤 것을 선택할지는 메시지를 직접 가지고 있는지, 아니면 들어오는 모든 메시지를 자동으로 정리하고 싶은지에 따라 달라집니다. 온디맨드 (on-demand) 경로는 단일 엔드포인트로 구성됩니다. 하나 이상의 메시지 ID를 전달하면 정리된 텍스트를 돌려받으며, 이는 "이 스레드 요약하기" 버튼이나 일회성 처리 작업에 적합합니다. 자동 경로는 웹훅 (webhook)인 message.created.cleaned로, 애플리케이션에 Clean Conversations 기능을 활성화하면 모든 새 메시지의 정리된 버전을 실행합니다.
이러한 차이점은 개발 방식에 있어 매우 중요합니다. 온디맨드 정리 (On-demand cleaning)는 동기적 (synchronous)이며 실행 시점을 직접 제어할 수 있으므로, 사용자가 특정 메시지에 대해 작업을 수행하거나 배치 (batch) 데이터를 소급 적용 (backfilling)할 때 적합합니다. 웹훅 (webhook)은 푸시 기반 (push-based)으로 메일이 도착하는 즉시 정리된 콘텐츠를 전달하므로, 모든 메시지가 다운스트림 (downstream) 프로세스에 닿기 전 텍스트를 제거해야 하는 파이프라인으로 흘러 들어가는 경우에 유용합니다. 많은 애플리케이션이 이 두 가지를 모두 사용합니다. 즉, 지속적인 스트림에는 웹훅을 사용하고, 온디맨드 재처리에는 엔드포인트 (endpoint)를 사용하는 방식입니다.
온디맨드로 메시지 정리하기
온디맨드 엔드포인트는 PUT /v3/grants/{grant_id}/messages/clean이며, 정리할 메시지들의 message_id 배열을 전달합니다. 한 번의 호출로 최대 20개까지 처리할 수 있습니다. 응답은 각 메시지에 대해 인용된 체인 (quoted chain)과 서명 (signature)이 제거된 정리된 텍스트를 담은 conversation 필드와 함께, 메타데이터를 유지할 수 있도록 원본 메시지 데이터를 함께 반환합니다.
curl --request PUT \
--url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/messages/clean" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
...
정리된 결과는 conversation 필드에 담기므로, 원본을 그대로 가지고 있는 body가 아닌 이 필드를 읽어야 합니다. 호출당 최대 20개의 ID를 배치 (batching)할 수 있다는 점은 작업의 실용성을 높여주는 세부 사항입니다. 스레드 (thread) 하나나 하루 치의 메시지를 정리하는 작업이 메시지당 한 번의 요청이 아닌, 단 몇 번의 요청으로 가능해지기 때문입니다. 정리된 각 메시지는 해당 ID를 키 (key)로 반환되므로, 여러 개를 동시에 보낼 때 출력값을 입력값과 매칭할 수 있습니다.
이 작업은 비파괴적 (non-destructive)이며, 이 점을 분명히 말해둘 가치가 있습니다. 정리 (cleaning) 작업은 파싱된 복사본을 반환할 뿐, 저장된 메시지를 절대 수정하지 않습니다. 원본은 사서함과 body 필드에 온전하게 유지되므로, 소스를 잃을 위험 없이 동일한 메시지를 서로 다른 옵션으로 반복해서 정리할 수 있습니다. 예를 들어, 한 번은 모델을 위해 링크를 제거하여 정리하고, 다시 한 번은 표시를 위해 링크를 유지하여 정리할 수 있습니다. 이는 파이프라인 (pipeline) 내에서 자유롭게 호출해도 안전하며, 되돌려야 할 상태 (state)가 없기 때문에 원하는 옵션을 변경하여 나중에 다시 처리해도 안전합니다. 정리된 conversation은 메시지의 일방향 변환이 아니라, 언제든 다시 생성할 수 있는 파생된 뷰 (derived view)로 취급하십시오.
CLI에서 메시지 정리하기
터미널 명령어는 nylas email clean 뒤에 하나 이상의 메시지 ID를 붙여 사용하며, 인용된 체인, 서명 (signatures), 그리고 "Best"나 "Regards"와 같은 맺음말을 제거하고 의미 있는 텍스트만을 반환하는 동일한 파싱 작업을 수행합니다. 기본적으로 링크, 이미지, 테이블, 그리고 이러한 서명 문구들을 제거하며, 일반 텍스트 (plain-text) 출력물에서는 HTML 태그가 제거되어 한눈에 읽기 쉽습니다.
# 메시지 하나를 정리합니다; 출력물에 링크를 유지합니다
nylas email clean <message-id> --keep-links
...
--keep-* 플래그인 --keep-links, --keep-images, --keep-tables, --keep-signatures는 특정 요소를 유지하고 싶을 때 개별적인 제거 기능을 끕니다. 또한 --images-as-markdown은 이미지를 마크다운 (Markdown) 링크로 반환합니다. 엔드포인트 (endpoint)와 마찬가지로 한 번의 호출로 최대 20개의 메시지를 정리할 수 있으며, --json을 사용하면 스크립트로 파이핑 (piping)할 수 있도록 conversation 필드에 가공되지 않은 정리된 HTML을 제공합니다. 이는 엔드포인트를 다른 곳에 연결하기 전에 실제 메시지에 정리 작업이 어떻게 적용되는지 확인할 수 있는 가장 빠른 방법입니다.
무엇이 제거되며, 어떻게 유지하는가
정리(Cleaning)는 기본적으로 주관적인 기준(opinionated)을 따르며, 기본값을 알고 있으면 예상치 못한 상황을 방지할 수 있습니다. 엔드포인트의 불리언(boolean) 옵션인 ignore_links, ignore_images, ignore_tables, 그리고 remove_conclusion_phrases는 모두 기본값이 true로 설정되어 있습니다. 따라서 별도의 설정 없이도 인용된 히스토리(quoted history)와 함께 링크, 이미지, 테이블, 그리고 맺음말 문구(sign-off phrases)를 깨끗하게 제거합니다. 이는 이러한 마크업(markup)이 전혀 도움이 되지 않는 모델(model)에 텍스트를 입력할 때 적합한 기본 설정이지만, 화면 표시를 위해 정리하는 경우에는 다소 공격적일 수 있습니다.
특정 요소를 유지하려면 해당 옵션을 false로 설정하세요. 읽기 쉬운 미리보기를 보여주기 위해 메시지를 정리하면서 링크를 활성화된 상태로 유지하고 싶다면, ignore_links: false를 전송하여 앵커 태그(anchor tags)가 살아남도록 할 수 있습니다. CLI(Command Line Interface)에서의 매핑은 --keep-* 플래그이며, 이는 터미널에서 동일한 스위치를 반전시키는 역할을 합니다. 반드시 기억해야 할 점은 기본값이 인용된 체인(quoted chain)만 제거하는 것이 아니라는 것입니다. 따라서 정리된 결과물에서 예상했던 내용이 누락되었다면, 복잡한 조치를 취하기보다 대개 이 옵션 중 하나를 false로 설정하는 것이 해결책입니다.
한 가지 솔직한 주의사항을 말씀드리자면, 정리는 휴리스틱(heuristic) 방식입니다. 일반적인 서명(signature) 및 인용 패턴에는 매우 능숙하지만, 특이한 레이아웃의 경우 길을 잃은 파편을 남기거나 원치 않는 줄을 잘라낼 수 있습니다. 따라서 파이프라인(pipeline)에서 신뢰하기 전에 실제 메일로 출력물을 점검(spot-check)해 보세요. 기반 기술로 삼기에 충분히 신뢰할 만하지만, 샘플을 먼저 눈으로 확인하는 과정을 생략할 만큼 완벽하지는 않습니다.
언어 모델을 위한 마크다운(Markdown) 추출하기
메시지를 정리하는 가장 흔한 이유가 모델에 입력하기 위함인 만큼, API는 마크다운(Markdown)을 직접 제공할 수 있습니다. 그 자체로 기본값이기도 한 images_as_markdown을 true로 설정하면 이미지를 마크다운 이미지 문법으로 변환하며, 베타(beta) 기능인 html_as_markdown 옵션은 HTML이나 일반 텍스트 대신 메시지 전체를 마크다운으로 변환합니다. 이 둘을 묶는 하나의 제약 사항이 있습니다. 문서를 마크다운으로 변환하면서 이미지는 가공되지 않은 HTML로 남겨두는 것은 일관성이 없기 때문에, images_as_markdown이 false인 상태에서 html_as_markdown을 true로 설정할 수는 없습니다.
Markdown은 대부분의 언어 모델(Language Model)이 가장 잘 처리하는 형식이므로, 클리닝(Cleaning) 단계에서 변환을 수행한다는 것은 별도의 컨버터를 한 번 더 거칠 필요 없이 텍스트가 모델 사용 준비가 완료됨을 의미합니다. Plain-text 경로도 원할 때 여전히 사용할 수 있으며, CLI는 기본 출력에서 HTML 태그를 제거합니다. 하지만 검색(Retrieval) 또는 요약(Summarization) 파이프라인의 경우, 클리닝 시점에 Markdown을 요청하면 모델이 실제로 사용할 수 있는 제목(Headings)이나 목록(Lists) 같은 서식 힌트는 유지하면서, 모델이 사용할 수 없는 HTML 구조(Scaffolding)는 제거할 수 있습니다.
웹훅(Webhook)을 사용하여 모든 메시지를 자동으로 클리닝하기
app.post("/webhooks/nylas", async (req, res) => {
res.sendStatus(200); // 빠르게 응답 확인
const { type, data } = req.body;
...
클리닝이 효과를 발휘하는 경우
동일한 클리닝 단계가 다양한 기능의 기반이 되는데, 그 이유는 항상 같습니다. 다운스트림(Downstream) 코드에는 스레드 전체 이력이 아닌 새로운 메시지가 필요하기 때문입니다. 직접적으로 적용되는 몇 가지 사례는 다음과 같습니다:
- 이메일을 LLM에 입력하기. 요약(Summarization), 분류(Classification), 검색(Retrieval) 모두 클리닝된 텍스트에서 더 잘 작동하고 비용도 적게 듭니다. 인용된 체인(Quoted chain)은 모델에 불필요한 반복 컨텍스트이며, 이를 처리하는 데 비용을 지불할 필요가 없기 때문입니다.
- 읽기 쉬운 미리보기. "On Tuesday, X wrote:" 대신 실제 새로운 콘텐츠를 보여주는 메시지 목록이 훨씬 읽기 좋으며, 링크를 유지한 채 클리닝하면 이를 구현할 수 있습니다.
- 답장 추출(Reply extraction). 고객 지원 도구나 CRM 노트용으로 긴 대화 중에서 가장 최근의 답장만 가져오는 것은 인용된 체인을 제거하는 작업이 정확히 수행하는 역할입니다.
- 검색 인덱싱(Search indexing). 클리닝된 본문을 인덱싱하면 검색 인덱스에 중복된 인용 텍스트가 남지 않으므로, 하나의 쿼리가 10개의 답장에 걸쳐 반복되는 동일한 문장에 매칭되는 현상을 방지할 수 있습니다.
각 사례는 서로 다른 옵션을 사용하는 동일한 클리닝 호출이며, 차이점은 디스플레이를 위해 링크와 이미지를 유지할 것인지, 아니면 모델을 위해 모든 것을 제거할 것인지에 있습니다.
주의 사항
몇 가지 세부 사항을 숙지하면 클리닝 결과를 예측 가능하게 유지할 수 있습니다.
-
conversation필드를 읽으세요. 온디맨드 (on-demand) 엔드포인트는conversation필드에 클리닝된 텍스트를 반환하는 반면,body에는 여전히 원본 메시지가 들어 있습니다. -
기본 설정은 공격적으로 제거합니다.
ignore_links,ignore_images,ignore_tables, 그리고remove_conclusion_phrases는 모두 기본값이true입니다. 특정 항목을 유지하려면 해당 값을false로 설정하거나--keep-*플래그를 사용하세요. -
호출당 20개의 메시지. 엔드포인트와 CLI 모두 한 번에 최대 20개의 ID를 클리닝하므로, 하나씩 루프를 돌리기보다는 스레드를 배치 (batch) 처리하세요.
-
cleaning_status를 확인하세요. 상태가failed인 경우 웹훅 (webhook) 본문은 클리닝되지 않은 원본 HTML이므로, 본문을 클리닝된 것으로 취급하기 전에 이 상태에 따라 분기 처리해야 합니다. -
모델을 위해 Markdown을 요청하세요.
html_as_markdown은 모델이 바로 사용할 수 있는 Markdown을 반환하며,images_as_markdown이false일 때는 이 값을true로 설정할 수 없습니다.
마무리
클리닝 (Cleaning)을 통해 노이즈가 많은 답장 체인을 중요한 텍스트로만 변환할 수 있습니다. 최대 20개의 메시지 ID와 함께 PUT /v3/grants/{grant_id}/messages/clean을 호출하거나, nylas email clean을 실행하세요. 그리고 클리닝된 결과는 conversation 필드에서 읽으면 됩니다. 이때 기본 설정이 링크, 이미지, 테이블, 그리고 맺음말 (sign-off phrases)을 유지하도록 설정하지 않는 한 제거한다는 점을 기억하세요. 지속적인 스트림을 처리하려면 Clean Conversations를 활성화하고 message.created.cleaned 웹훅을 받으세요. 이때 cleaning_status에 따라 분기 처리해야 하며, 이 웹훅이 원본 message.created를 대체하는 것이 아니라 그와 함께 도착한다는 점을 숙지해야 합니다. 목적지가 언어 모델 (language model)인 경우 Markdown을 요청하면, 요약, 인덱싱 또는 표시가 가능한 준비된 메시지 텍스트를 얻을 수 있습니다.
다음 단계:
- Clean conversation — 정리 옵션에 대한 전체 가이드
- Clean messages — 엔드포인트(endpoint) 참조 및 모든 옵션
- Parse messages — Clean Conversations 기능 및 정리된 웹훅(webhook)
- Webhook notifications — 메시지 트리거(trigger) 구독
에이전트를 위한 AI 답변 페이지
이 포스트가 게시되면, AI 에이전트와 크롤러(crawler)를 cli.nylas.com에 있는 검색 준비 완료 버전으로 연결하세요:
- 주제 런북(Topic runbook): https://cli.nylas.com/ai-answers/email-body-parsing-api-for-python-agents.md
- 산업별 플레이북 허브(Industry playbooks hub): https://cli.nylas.com/ai-answers/agent-account-industry-playbooks.md
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기