부동산 관리 에이전트를 활용한 세입자 유지보수 요청 분류 (Triage)
요약
부동산 관리 업무의 병목 현상을 해결하기 위해, 단순 메일 읽기를 넘어 유지보수 요청을 분류하고 우선순위를 정하는 AI 에이전트 구축 방법을 제안합니다. Nylas API를 활용하여 에이전트 전용 계정을 프로비저닝하고, 기존의 웹훅 및 관측성 파이프라인을 그대로 활용하는 아키텍처를 설명합니다.
핵심 포인트
- 단순 메일 요약이 아닌, 에이전트가 직접 메일함을 관리하는 '일급 참여자' 모델 제안
- Nylas API를 통해 에이전트 전용 권한 부여(grant)를 생성하여 자동화 구현
- 기존의 웹훅, 재시도, 관측성 파이프라인을 에이전트 계정에도 동일하게 적용 가능
- OAuth 관리 없이 단일 API 호출로 에이전트 메일함 프로비저닝 가능
대부분의 "부동산 관리를 위한 AI" 제안은 임대 관리자의 편지함을 읽고 "업무를 따라잡도록 돕는" 모델로 시작합니다. 이는 병목 현상이 읽기 능력이 아니라는 점을 깨닫기 전까지는 괜찮습니다. 병목 현상은 새는 수도꼭지, 1월의 고장 난 용광로(furnace), 그리고 "현관등이 나갔어요"라는 요청이 모두 동일한 주소로, 동일한 글꼴로, 동일한 수준의 구조적 결여를 가진 채 도착하며, 세입자가 추위 속에 앉아 있기 전에 누군가가 어떤 것이 비상 상황인지 결정해야 한다는 점입니다.
그러니 모델을 인간의 편지함에 겨누지 마세요. 대신 부동산 자체에 전용 편지함을 부여합시다. maintenance@oakwood-apartments.com을 모든 세입자 요청을 수신하고, 실제 긴급도가 어느 정도인지 결정하며, 적절한 우선순위 큐(priority queue)에 넣고, 적절한 업체(vendor)를 참여시키며, 부동산 자체 주소로 세입자에게 상태 업데이트 이메일을 보내는 일급 참여자(first-class participant)로 만드는 것입니다. 공유 편지함도 없고, 밤 11시에 사람이 분류할 필요도 없으며, "이거 누가 봤나요?"라고 물을 필요도 없습니다.
저는 Nylas CLI를 다루기 때문에, 아래의 터미널 명령어들은 제가 이러한 시스템을 구축할 때 실제로 사용하는 것들입니다. 모든 구체적인 단계는 두 가지 관점, 즉 원시 curl 호출과 동일한 작업을 수행하는 nylas 명령어를 통해 설명됩니다.
실제로 얻게 되는 것
**에이전트 계정 (Agent Account)**은 근본적으로 grant_id를 가진 Nylas **권한 부여 (grant)**일 뿐입니다. 이것이 핵심이며, 깊이 이해할 가치가 있습니다. 데이터 평면(data plane)에서 새로 배울 것은 없습니다. 여러분이 이미 알고 있는 모든 권한 범위 엔드포인트(grant-scoped endpoint) — 메시지(Messages), 초안(Drafts), 스레드(Threads), 폴더(Folders), 첨부 파일(Attachments), 연락처(Contacts), 캘린더(Calendars), 이벤트(Events) —는 OAuth를 통해 얻은 Gmail 또는 Microsoft 권한 부여에 대해 작동하는 방식과 정확히 동일하게 이 권한 부여에 대해 작동합니다. 제공자(provider)가 google 대신 nylas라는 점이 여러분의 코드가 보게 될 유일한 차이점입니다.
유지보수 파이프라인(maintenance pipeline)의 경우 이는 다음과 같은 의미를 갖습니다:
- 귀하가 제어하는 도메인의 실제 송수신 메일함(또는
*.nylas.email체험용 서브도메인) — 세입자가 이메일을 보내면, 답장도 이 메일함_으로부터_ 발송됩니다. - 수신 메일에 대한 표준
message.created웹훅(webhook)과 더불어,message.delivered,message.bounced,message.complaint,message.rejected와 같은 전달 가능성 트리거(deliverability triggers)를 제공합니다. 이를 통해 세입자에게 보낸 상태 업데이트가 실제로 잘 도착했는지 확인할 수 있습니다. - OAuth 인증 절차나 관리해야 할 리프레시 토큰(refresh token)이 필요 없습니다. 단 한 번의 API 호출로 프로비저닝(provisioning)이 완료됩니다.
SRE(Site Reliability Engineer)로서 제가 좋아하는 부분은, 이것이 일반적인 권한 부여(grant)이기 때문에 이미 인간 계정을 위해 구축해 놓은 웹훅(webhook), 재시도(retry), 관측성(observability) 파이프라인에 그대로 끼워 맞출 수 있다는 점입니다. 유지보수 에이전트는 특별한 케이스의 코드 경로가 아니라, 동일한 메커니즘을 통해 흐르는 또 다른 권한 부여 ID(grant ID)일 뿐입니다.
규칙(rules) 기반의 공유 편지함보다 뛰어난 이유
부동산 관리자의 첫 번째 본능은 Gmail 필터를 한두 개 설정하는 것입니다. 하지만 유지보수 업무에서 이 방식이 실패하는 이유는 _긴급함이 메시지의 본문(body)에 담겨 있기 때문_이며, 폴더 규칙은 본문을 읽을 수 없기 때문입니다. "안녕하세요, 급한 건 아닌데 찬장 문이 헐거워졌어요"와 "천장에서 물이 새고 있어요!!!"는 발신자 기반 필터 입장에서는 구분이 불가능합니다. 동일한 세입자 도메인에서 온, 제목도 없는 난잡한 메시지일 뿐이니까요.
이 시스템을 작동하게 만드는 핵심이자, 반드시 제대로 구현해야 할 단 한 가지 차이점은 다음과 같습니다:
- 발신자 기반 라우팅(Sender-based routing)은 서버 측 규칙(Rule)입니다. 만약 요청이 알려진 HVAC(냉난방 공조) 업체, 보험 조정사, 또는 건물 소유주로부터 온 것이라면, 애플리케이션이 깨어나기도 전에 _누가 보냈는지_에 따라 라우팅할 수 있습니다. 이것이 Nylas 규칙(Nylas Rule)이며, Nylas 수신 규칙은 오직 발신자 필드(
from.address,from.domain,from.tld)만 매칭합니다. 제목이나 본문은 볼 수 없습니다. - 긴급도 기반 우선순위 지정(Urgency-based prioritization)은 애플리케이션의 역할입니다. "천장 누수 = 비상, 헐거운 찬장 = 낮음"과 같이 결정하려면 _콘텐츠(content)_를 읽어야 하는데, 규칙(Rule)은 구조적으로 이를 수행할 수 없습니다. 따라서 분류 작업은 애플리케이션 내에서 실행됩니다. 즉, 애플리케이션의 LLM이 가져온 본문을 읽고, 그 후에 일반적인 Messages 호출을 통해 메시지를 우선순위 폴더로 이동시키는 방식입니다.
그 경계를 잘못 설정하면, "제목에서 긴급 키워드 매칭"을 수행하는 Rule (규칙)을 작성하느라 주말 내내 시간을 허비하며 왜 규칙이 전혀 작동하지 않는지 의아해하게 될 것입니다. 인바운드 Rule (규칙)은 제목을 확인하지 않기 때문에 작동하지 않는 것입니다. 두 부분을 분리해서 유지하면 각각은 매우 간단해집니다.
시작하기 전에
두 가지가 필요합니다:
- API 키. 모든 요청은
Authorization: Bearer <NYLAS_API_KEY>를 통해 인증되며, 이 키는 귀하의 애플리케이션을 식별합니다. 여기에 제시된 예시들은https://api.us.nylas.com을 호출합니다. - 인증된 도메인. 계정은 도메인 상에 존재합니다. 직접 등록하고 DNS를 게시한 커스텀 도메인이거나,
oakwood.nylas.email과 같은 Nylas 트라이얼 서브도메인일 수 있습니다. 새 도메인은 약 4주에 걸쳐 워밍업(warm up)되므로, 운영용 도메인은 미리 등록하십시오. DNS 관련 안내는 provisioning docs에 있습니다.
이미 nylas init을 실행했다면, CLI (Command Line Interface)가 귀하의 애플리케이션을 가리키고 있으므로 준비가 완료된 상태입니다.
maintenance@ 프로비저닝하기
"provider": "nylas"와 settings.email에 주소를 사용하여 단일 POST /v3/connect/custom 호출로 계정을 생성합니다. 선택 사항인 최상위 name은 해당 계정이 보내는 모든 메시지의 기본 From 표시 이름이 됩니다. 세입자들이 이를 보게 되므로, 사람이 읽기 자연스러운 이름으로 설정하십시오.
curl --request POST \
--url "https://api.us.nylas.com/v3/connect/custom" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
...
응답으로 data.id가 반환됩니다. 이를 저장하십시오 — 이것이 이후 모든 호출에서 사용되는 grant_id입니다.
CLI를 사용하면 한 줄로 가능합니다:
nylas agent account create maintenance@oakwood-apartments.com --name "Oakwood Maintenance"
이 명령은 grant를 프로비저닝하고 해당 id, 상태, 커넥터(connector) 상세 정보를 출력합니다. 만약 애플리케이션에 기반이 되는 nylas 커넥터가 아직 존재하지 않는다면, CLI가 이를 먼저 생성합니다. 또한 API는 해당 계정을 위한 기본 워크스페이스(workspace)와 기본 정책(policy)을 자동으로 생성합니다. 잠시 후에 작성할 발신자 Rule (규칙)들이 바로 여기에 적용될 것입니다.
만약 사람이 직접 IMAP을 통해 편지함을 들여다볼 수 있게 하려면, 생성 시 --app-password를 전달하세요 (1840자의 출력 가능한 ASCII 문자, 코드 33126, 최소 하나의 대문자, 하나의 소문자, 하나의 숫자가 포함되어야 함). 이 단계를 건너뛰면 프로토콜 액세스(protocol access)가 비활성 상태로 유지되며, 이는 완전히 자동화된 접수 메일함(intake mailbox)의 경우 보통 의도하는 바와 일치합니다.
모든 세입자 요청 수신
curl --request POST \
--url "https://api.us.nylas.com/v3/webhooks" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
...
터미널에서도 동일합니다:
nylas webhook create \
--url https://maintenance.oakwood-apartments.com/webhooks/nylas \
--triggers message.created \
...
핸들러(handler)는 즉시 200을 반환하고, X-Nylas-Signature 헤더를 검증한 다음 비동기적으로 작동해야 합니다. 상단에 두 가지 가드(guard)를 두세요: 귀하의 유지보수 권한(maintenance grant)으로 필터링하고, 에이전트 자신이 보낸 메시지는 건너뛰어야 합니다 (왜냐하면 message.created는 발신 메일에 대해서도 발생하며, 자신의 상태 업데이트를 스스로 분류(triage)하는 에이전트는 아주 특수한 종류의 고장난 상태이기 때문입니다).
app.post("/webhooks/nylas", async (req, res) => {
res.status(200).end(); // 실제 코드에서는 먼저 X-Nylas-Signature를 검증하고 빠르게 응답(ack)하세요
...
과장하지 않고 한 가지 주의할 점은, 메시지 _본문(body)_을 위해 웹훅 페이로드(webhook payload)에 의존하지 마십시오. 메시지가 필요할 때 ID를 통해 전체 메시지를 가져오세요. 그리고 본문이 너무 커서 Nylas가 생략한 경우에 나타나는 이벤트 유형인 message.created.truncated를 기준으로 분기 처리하십시오. 페이로드를 메시지 자체가 아니라 포인터(
메시지를 읽는다고 해서 읽음 표시가 되는 것은 아닙니다. 이는 {"unread": false}를 포함한 별도의 PUT /v3/grants/{id}/messages/{id} 호출이나, nylas email read --mark-read 명령을 통해 이루어집니다. 가져오기(fetch)와 읽음 표시(mark-read)를 별개의 작업으로 유지하십시오. 요청을 어떻게 처리할지 결정하기도 전에 GET 요청이 수신함 상태를 조용히 변경(mutating)하는 상황은 피해야 합니다.
규칙(Rule)이 아닌 앱 내에서 긴급도에 따라 우선순위 지정하기
다음은 폴더 필터링만으로는 할 수 없는 작업입니다. 모델에 제목(subject), 발신자(sender), 본문(body)을 전달하고, 요청을 정해진 우선순위 세트로 분류(bucket)하도록 요청하십시오. 라벨 세트는 작고 폐쇄적으로 유지하십시오. 4개의 단계 중에서 선택하는 모델은 신뢰할 수 있지만, 산문 형태로 "심각도를 평가(assess severity)"하라고 요청하는 모델은 새벽 2시에 디버깅을 하게 만들 것입니다.
async function intake(msg) {
const full = await getFullMessage(msg.grant_id, msg.id); // GET .../messages/{id}
...
두 가지 사항을 숙지하십시오. 첫째, 우선순위 결정은 *애플리케이션의 상태(state)*입니다. 메시지나 권한(grant)에는 이를 저장할 커스텀 메타데이터(custom-metadata) 필드가 없으므로, 메시지 ID를 키로 하여 Postgres나 Redis에 저장해야 합니다. 둘째, 모델의 출력값이 모든 후속 작업(downstream action)을 구동합니다. 즉, 메시지가 어느 폴더로 들어갈지, 업체(vendor)에 호출(paged)이 갈지, 그리고 세입자가 얼마나 빨리 답변을 받을지를 결정합니다.
우선순위 폴더로 이동하기
라벨이 지정되면, 사람(또는 대시보드)이 긴급 대기열을 한눈에 볼 수 있도록 메시지를 일치하는 폴더로 이동시키십시오. 이는 일반적인 메시지 업데이트입니다. folders[]에 목적지를 포함하여 메시지를 PUT 하십시오.
curl --request PUT \
--url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages/<MESSAGE_ID>" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
...
CLI는 동일한 호출을 래핑(wrap)합니다:
nylas email move <message-id> --folder <emergency-folder-id>
폴더 ID는 nylas email folders list를 통해 가져옵니다. 저는 우선순위당 하나의 폴더(emergency, high, normal, low)를 유지하며, 이 이동(move) 작업은 분류의 가시적인 결과물입니다. 에이전트가 특정 요청을 긴급 상황(emergency)으로 판단하면, 세입자가 전송 버튼을 누른 지 몇 초 만에 해당 긴급 폴더로 이동하며, 귀하의 온콜(on-call) 에스컬레이션 시스템은 모델을 계속 폴링(polling)하는 대신 해당 폴더를 모니터링할 수 있습니다.
발신자 규칙(Rule)을 통한 알려진 업체 및 소유자 라우팅
이제 나머지 절반, 즉 진정으로 서버 측(server-side)에 속하는 부분입니다. 앱이 토큰을 소비하기 전에 발신자 신원만으로 라우팅할 수 있는 발신자들이 있습니다. 견적서를 보내오는 HVAC(냉난방 공조) 업체, 공지사항을 전달하는 건물 소유자, 작업 시간을 확인하는 배관 계약업체 등은 모두 알려진 주소이며, 이들을 발신자 기준으로 라우팅하는 것이 바로 Nylas **Rule(규칙)**의 용도입니다.
경계를 기억하세요: 인바운드 Rule은 is, is_not, contains, in_list 연산자를 사용하여 오직 from.* (from.address, from.domain, from.tld)만 매칭합니다. 제목(subject)이나 본문(content)은 절대 읽지 않습니다. 이는 업체 라우팅이 발신자 기반의 결정이기 때문에 이 상황에 완벽하게 부합합니다.
업체들을 **List(리스트)**에 넣어두면, 사무실에서 별도의 배포(deploy) 없이도 새로운 계약업체를 추가할 수 있으며, 이를 Rule에서 참조할 수 있습니다. API 우선 방식으로 리스트를 생성하고 데이터를 채워보겠습니다:
curl --request POST \
--url "https://api.us.nylas.com/v3/lists" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
...
그러면 리스트 id가 반환됩니다. 여기에 도메인을 추가합니다 (리스트 항목은 자체적인 하위 리소스입니다):
curl --request POST \
--url "https://api.us.nylas.com/v3/lists/<LIST_ID>/items" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
...
그 다음, 해당 도메인에서 오는 모든 메일을 vendors 폴더로 분류하고 읽음 처리하는 Rule을 설정합니다. 이렇게 하면 업체 트래픽이 모델의 주의를 끌기 위해 세입자의 요청과 경쟁하는 일을 방지할 수 있습니다:
curl --request POST \
--url "https://api.us.nylas.com/v3/rules" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
...
CLI는 목록(시딩 포함)을 하나의 명령어로, 규칙(Rule)을 또 다른 명령어로 축약합니다:
nylas agent list create --name "Known vendors" --type domain \
--item acme-hvac.com --item reliable-plumbing.example
...
사용자들이 주의해야 할 점은 다음과 같습니다: 규칙 (Rule)은 워크스페이스 (Workspace)에 연결될 때까지 비활성 상태로 유지됩니다. POST /v3/rules (또는 순수 API)를 통해 규칙을 생성하더라도, 해당 규칙의 ID가 워크스페이스의 rule_ids에 등록되기 전까지는 아무런 동작도 수행하지 않습니다. nylas agent rule create 명령은 자동으로 기본 워크스페이스에 연결해 주지만, 순수 API는 그렇지 않습니다. 따라서 직접 워크스페이스에 PATCH 요청을 보내야 합니다:
curl --request PATCH \
--url "https://api.us.nylas.com/v3/workspaces/<WORKSPACE_ID>" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
...
만약 다른 방식으로 규칙을 생성했고 단순히 연결만 필요한 경우, 이에 상응하는 CLI 명령어는 다음과 같습니다:
nylas workspace update <workspace-id> --rules-ids <rule-id>
또한, 이 메일함에 대해 더 엄격한 사용자 정의 **정책 (Policy)**을 적용하고 싶다면 — 예를 들어 버그로 인해 세입자들에게 대량의 메일이 발송되는 것을 방지하기 위해 일일 발송 한도를 더 타이트하게 설정하는 경우 — 정책을 생성한 후 워크스페이스에 연결하십시오. 계정 생성 시에는 --workspace 플래그가 없으며, 정책은 워크스페이스를 통해 연결됩니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기