에이전트 outreach 이메일 열람 추적
출처: Dev.to
아웃리치 에이전트를 만들었는데 이번 주에 80개의 후속 메일을 보냈고, 그 중 어느 것이든 어떤 일이 일어났는지 알 수 없습니다. 수신자가 메시지를 열었나요? 데모 링크를 클릭했나요? 침묵은 ‘아니오’ 때문인가, 아니면 스팸 폴더 문제인가요? 참여 신호가 없으면 에이전트는 공허에 총을 쏘는 것과 같고, 후속 로직은 추측에 기반합니다.
해결책은 두 가지로 나뉩니다: 전송 시 추적을 켜고, 수신자 행동을 보고하는 웹훅을 구독하세요.
열기, 클릭, 답변은 추적이 켜진 메시지에 대해서만 보고되며, 이미 보낸 메시지는 retrospecitively 추적할 수 없습니다. Send Message 요청 시 tracking_Options 객체를 전달해야 합니다. 이 객체는 3개의 부울 값과 나중에 모든 알림에 반영되는 선택적 라벨을 포함해야 합니다:
curl --request POST \
--url 'https://api.us.nylas.com/v3/grants//messages/send' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ' \
--data-raw '{
"subject": "Quick follow-up on your trial",
"body": "Thanks for trying us out. Reply or book a demo when ready.",
"to": [{
"name": "Kim Townsend",
"email": "kim@example.com"
}],
"tracking_Options": {
"opens": true,
"links": true,
"thread_replies": true,
"label": "trial-followup-q2"
}
}'
라벨은 에이전트가 의존해야 하는 부분입니다. 캠페인 ID나 연락처 ID를 찍어 두면 이후 알림에 모두 포함되어, 핸들러가 메시지 ID 매핑을 저장하지 않고 outreach 상태와 이벤트를 매칭할 수 있습니다. 테스트하기 전 주의할 점 하나: 추적 기능은 프로덕션 환경에서만 작동하며, 트라이얼 계정에서는 “Tracking options are not allowed for trial accounts”라는 응답을 받습니다.
참여 이벤트는 웹훅을 통해 도착합니다. 모든 트리거(message.opened, message.link_clicked, thread.replied)에 대해 하나의 HTTPS 엔드포인트를 구독하세요:
curl --request POST \
--url 'https://api.us.nylas.com/v3/webhooks/' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ' \
--data-raw '{
"trigger_types": ["message.opened", "message.link_clicked", "thread.replied"],
"webhook_url": "https://yourapp.com/webhooks/nylas",
"description": "Email engagement tracking"
}'
웹훅을 만들 때 흔히 발생하는 문제점이 있습니다. Nylas는 웹훅 생성 시 챌린지 쿼리 파라미터가 포함된 GET 요청을 보내고, 여러분의 엔드포인트는 구독이 활성화되기 전에 이를 되돌려 주어야 합니다. POST만 처리하는 라우트라면 웹훅이 jamais 활성화되지 않아 0건의 이벤트가 보이는 상황에서도 의아해할 수 있습니다.
각 페이로드는 추적된 메시지 ID, 라벨, 그리고 해당 메시지에 대한 마지막 50개 이벤트를 담은 recents 배열을 포함합니다. 각각 타임스탬프, IP 주소, 사용자 에이전트와 함께 표시됩니다. 10초 이내에 200 OK를 반환하지 않으면 전달이 실패로 간주되고 재시도됩니다. 이는 무인(idempotency‑naive) 에이전트가 두 번째 오픈으로 오해할 수 있습니다.
챌린지 핸드쉐이크와 트리거 유형별 라우팅을 모두 커버하는 최소 핸들러 예시:
from flask import Flask, request
app = Flask(__name__)
@app.route("/webhooks/nylas", methods=["GET", "POST"])
def nylas_webhook():
if request.method == "GET":
return request.args.get("challenge", ""), 200
event = request.get_json()
trigger = event["type"]
message_id = event["data"]["object"]["message_id"]
if trigger == "message.opened":
record_open(message_id) # soft signal — log it, don’t act on it
elif trigger == "message.link_clicked":
warm_up_sequence(message_id) # real human action
elif trigger == "thread.replied":
stop_sequence(message_id) # strongest signal — hand off the thread
return "", 200
세 가지 트리거 이름은 에이전트 결정을 명확히 반영하며, 핸들러 상단에서 event["type"]을 라우팅하는 것이 한 개의 “engagement” 카운터로 모든 것을 모으는 것보다 낫습니다.
아웃리치 에이전트가 잘못하는 점은 오픈을 의도로 취급하는 것입니다.
오픈 추적은 투명한 1픽셀 이미지를 삽입해 수신자 클라이언트가 로드하도록 합니다. 기업 게이트웨이와 프라이버시 중심 클라이언트는 원격 이미지를 차단해 실제 오픈을 조용히 놓칩니다. 더 나쁜ことに 프리패칭 프록시는 반대 동작을 합니다 — iOS 15 이후 기본으로 켜져 있는 Apple Mail Privacy Protection은 이미지 대신 프록시를 통해 로드하고, 인간이 내용을 읽었는지 여부와 관계없이 오픈을 등록합니다. Apple이 이메일 클라이언트 시장의 약 50%를 차지하고 있어, 실제 오픈 데이터의 상당 부분이 과장되어 있습니다. “Opened at least once”으로 보고하고, 읽은 횟수를 기준으로 하지 않으며, 오픈만으로 에이전트를 확장하지 않도록 하세요.
클릭은 견고한 신호입니다. 클릭은 실제 인간의 행동입니다. links: true를 사용하면 본문 내 모든 유효한 HTML锚가 추적 URL로 재작성됩니다(메시지당 최대 100개 링크까지 추적 가능하며, 로그인 인증 정보를 포함하는 URL은 건너뛰어 인증된 대상이 깨지지 않게 합니다). thread.replied를 통해 발생하는 답변은 세 가지 중 가장 강력한 신호이며, 명확한 의도를 나타냅니다.
아웃리치 에이전트의 합리적인 결정 정책: 오픈 → 아무 행동도 안 함, 후속 간격을 짧게; 클릭 → 더 따뜻한 시퀀스로 이동; 답변 → 즉시 시퀀스를 중단하고 트레드에 대한 처리 로직으로 넘김.
아웃리치 에이전트가 Nylas 호스팅 Agent Account(에이전트 전용 메일함을 제공하는 베타 기능으로, 연결된 Gmail 또는 Outlook 위임을 대신함)를 사용하는 경우, 직접 API 전송에서는 네이티브 메시지 추적이 불가능합니다. message.opened와 message.link_clicked는 해당 계정에서 POST /messages/send로 전송된 메시지에 대해 방출되지 않습니다. 전달 가능성 가시성은 대신 전송 측면 트리거(message.send_success, message.send_failed, message.bounce_detected)에서 제공됩니다.
실제로는 합리적인 타협입니다. 전송 측면 트리거는 실제로 전달된 메시지나 bounce된 메시지를 알려주고, 답변은 에이전트 메일함에 도착해 message.created 이벤트를 발생시킵니다. 답변 기반 라우팅 — 가장 강력한 신호 anyway — 역시 동일하게 작동합니다. 위Tracking 플래그는 연결된 제공업체 위임을 통해 전송될 때 적용되며, 이것이 많은 아웃리치 툴이 운영하는 방식과 같습니다.
각 트리거는 .legacy 변형(message.opened.legacy, message.link_clicked.legacy, thread.replied.legacy)을 제공하며, 이는 구식 알림 포맷을 사용하는 애플리케이션에 적용됩니다. 새로운 통합은 비 레거시 이름을 구독해야 하며, 두 가지를 혼합하면 페이로드 형태가 다른 중복 이벤트가 발생해 디버깅하기가 끔찍합니다.
추적은 실제로 메시지 콘텐츠를 재작성하고 수신자 피셜을 남깁니다. 플래그는 전송 위탁자가 Google, Microsoft 또는 다른 제공업체인지와 관계없이 동일하게 동작하므로, 하나의 전송 경로가 전체 발신자를 커버하지만, 이는 모든 메시지에 대한 공개 의무를 따릅니다. 동의에 따라 준수하고, 관할구역이 요구하는 곳에 추적을 공개하며, 민감한 데이터를 포함하는 링크는 추적하지 마세요.
답변 경로를 먼저 구축하고, 클릭을 다음, 오픈은 마지막으로 — 대부분의 팀이 반대 순서로 진행하는 방식과 달리 신호 품질에 맞는 순서를 따릅니다. 전체 페이로드 스키마와 제공업체별 동작은 추적 레시피에 포함되어 있습니다.
다음 단계: tracking_Options를 하나 이상의 실시간 캠페인에 추가하고, 일주일간 세 가지 트리거 유형을 로깅한 뒤 오픈률과 클릭률을 비교하세요. 두 지표가 극명하게 다른 이야기를 한다면, 현재 후속 로직이 신뢰하는 신호는 무엇인지 확인하세요.