문서로 건너뛰기
SDK

열람 및 클릭 추적

`emails.getTracking`과 `tracking` 리소스 전체.

메시지 하나

tracking.ts
const report = await openemail.emails.getTracking('msg_…') console.log(report.openCount, 'opens from', report.recipients.length, 'recipients')for (const link of report.links) console.log(link.url, link.clickCount)

추적된 적 없는 메시지는 빈 보고서가 아니라 isNotFound가 true인 OpenEmailApiError를 던집니다. "아무것도 기록하지 않았다"와 "아무도 열지 않았다"는 다른 답이며 하나의 응답을 공유해서는 안 됩니다.

메일함 전체에서

tracking-report.ts
await openemail.tracking.list({ opened: false, days: 7, limit: 100 })await openemail.tracking.getStats({ days: 30, offsetMinutes: -new Date().getTimezoneOffset() })await openemail.tracking.get('msg_…')await openemail.tracking.listOpens('msg_…', { includeMachine: true })await openemail.tracking.listClicks('msg_…')

list, listOpens, listClicks는 평범한 배열로 resolve됩니다. get, listOpens, listClicksmsg_… 발송 id와 추적 레코드 자체의 tmsg_… 중 어느 쪽이든 받습니다.

emails의 필드가 아니라 별도의 리소스인 이유는 포괄 범위 때문입니다. emails는 발송 기록을 나열하는데, 그 기록은 이 API가 처리한 메일에만 존재합니다. 작성기, MCP 도구, 어시스턴트는 모두 그런 기록 없이 발송하므로, emails 위에 세운 보고서는 메일함이 아니라 자신의 API 트래픽에 대한 보고서가 됩니다.

숫자를 정직하게 읽기

의미
`opens` / `clicks`적용된 것. 메시지가 픽셀이나 재작성된 링크를 달고 나갔는지 여부입니다.
`opened` / `clicked`실제로 일어난 일.
`openCount`집계된 요청. 스캐너와 프라이버시 프록시는 제외됩니다.
`openCountRaw`모든 요청. 이 값을 참여 지표로 인용하는 것이 열람률이 100%를 넘게 되는 이유입니다.
`attributable`열람을 특정 수신자에게 귀속시킬 수 있는지 여부.

tracking.getStats의 비율은 보낸 메일 전체가 아니라 추적된 메시지를 모수로 합니다. 그렇지 않으면 열 통 중 한 통만 추적하는 메일함이 붕괴한 것처럼 보일 것입니다.

매개변수: tracking.list

openedboolean
`true`는 집계된 열람이 하나 이상인 메시지를, `false`는 집계된 열람이 없는 추적된 메시지를 선택합니다. 어느 쪽도 기본값이 아니며, `false`가 추적되지 않은 메일을 뜻하는 일은 없습니다. 그런 메일은 이 목록에 아예 나오지 않습니다.
clickedboolean
집계된 클릭에 대한 같은 필터이며, `opened`와 독립적으로 적용됩니다. 둘 다 줄 수 있고, 그때 메시지는 둘 다 만족해야 합니다.
daysnumber
지금부터 며칠 전까지 볼지이며, 1에서 365까지이고 기본값은 30입니다. 범위를 벗어나면 422입니다. 이 기간은 추적 레코드가 만들어진 시각을 기준으로 하며, 발송이 실제로 나간 레코드만 나열됩니다.
limitnumber
최대 이만큼의 메시지를 최신순으로 반환하며, 1에서 200까지이고 기본값은 50입니다. 커서는 없습니다. 이것은 피드가 아니라 일정 기간에 대한 보고서이므로, `days`와 `limit`으로 범위가 정해지고 통째로 읽습니다.

응답: TrackingResource

object'tracking'
`tracking.get`, `tracking.list`, `emails.getTracking`으로 그 자체를 가져온 보고서에서는 항상 `'tracking'`입니다. 조회한 메시지에 `email.tracking`으로 중첩된 같은 보고서에는 이 키가 없는데, 거기서는 가져온 것이 아니라 그 객체의 일부이기 때문입니다.
idstring
추적 레코드 자체의 id인 `tmsg_…`입니다. 요청 단위 호출인 `listOpens`와 `listClicks`가 이 값을 키로 삼으며, 그 호출에 `msg_…`를 넘기면 먼저 이 값으로 해석됩니다.
sendIdstring | null
이 기록이 연결되는 `msg_…` 발송이며, 발송 레코드가 기록되지 않았으면 null입니다. 작성기, MCP의 `sendEmail`, 어시스턴트는 모두 발송 레코드 없이 메일을 보냅니다. 추적은 API 트래픽만이 아니라 메일함 전체를 대상으로 합니다.
threadIdstring | null
읽기 UI가 메시지를 다시 찾을 수 있도록 전송 후에 채워지며, 드라이버가 아무 값도 보고하지 않았으면 null입니다. 필수적인 값은 아닙니다. 이 값이 null인 레코드도 그대로 집계됩니다.
messageIdstring | null
OpenEmail의 id가 아니라 RFC 5322 Message-ID입니다. 이 값도 전송 후에 채워지며, 전송 계층이 채울 값을 돌려주지 않았으면 null입니다.
subjectstring | null
발송 시점의 제목입니다. 제목 없이 기록된 메시지에서는 null입니다.
fromstring
발신 주소이며, 발송 레코드와 조인하지 않고 이 레코드에 복사해 둡니다. 리포트는 한참 뒤에 읽히기 마련이고, 그러지 않으면 그사이 수정되거나 삭제된 주소가 과거 기록까지 바꿔 버리기 때문입니다.
sourceEmailSource | (string & {})
어느 경로에서 발송했는지를 나타내며, `composer`, `api`, `mcp`, `ai`, `queue` 중 하나입니다. 이 SDK가 아직 이름을 붙이지 않은 경로가 추가되어도 호환성이 깨지지 않도록 열린 타입으로 선언되어 있습니다.
sentAtstring | null
메시지가 나간 시각이며, ISO-8601 시점으로 표기됩니다. 발송이 끝내 완료되지 않은 레코드에서는 null입니다. `tracking.list`는 그런 레코드를 제외하지만 `get`은 제외하지 않습니다.
opensboolean
이 메시지에 픽셀이 실제로 적용되었는지 여부입니다. 지금 계정 설정이 무엇이라고 말하는지가 아니라, 당시에 무엇을 했는지를 담습니다.
clicksboolean
이 메시지의 링크가 재작성되었는지 여부입니다. 본문에 링크가 없었다면 false인데, 그때는 바뀐 것이 없고 그렇지 않다고 주장하는 기록은 실제 바이트와 맞출 수 없기 때문입니다.
openedboolean
사본 전체에서 집계된 열람이 하나라도 기록되었는지 여부입니다. `opens`와 함께 읽으세요. 수집하지 않아 데이터가 없는 것과 아무도 메시지를 읽지 않은 것은 다른 사실입니다.
clickedboolean
집계된 클릭이 하나라도 기록되었는지 여부입니다. 열람보다 강한 증거인데, 이미지가 차단되는 경우가 링크를 따라가지 않는 경우보다 훨씬 많기 때문입니다.
attributableboolean
여기의 모든 열람을 특정 수신자에게 귀속시킬 수 있는지 여부입니다. 귀속되지 않은 사본에서 집계된 활동이 나타나는 순간 false가 되며, 이는 하나의 본문이 하나의 토큰으로 수신자 전체에게 가는 다중 수신자 상황입니다. "Bob은 이 메일을 열지 않았다"고 쓰기 전에 이 값을 확인하세요.
openCountnumber
사람이 발생시킨 것으로 보이는 열람을 사본 전체에 대해 합한 값입니다. 기계에 의한 요청은 제외되고 30초 이내의 반복은 하나로 합쳐지므로, 읽는 사람 앞에 내놓아야 할 수치는 이것입니다.
clickCountnumber
사본 전체에 대해 합한 집계 클릭 수입니다. 메시지 단위가 아니라 링크 단위로 중복 제거되므로, 몇 초 간격으로 따라간 서로 다른 두 링크는 두 번의 클릭입니다.
openCountRawnumber
스캐너와 프라이버시 프록시를 포함한 모든 픽셀 요청입니다. `openCountRaw - openCount`는 분류기가 걸러 낸 수이며, 그 필터링이 일어났다는 사실에 대해 구할 수 있는 유일한 증거입니다.
clickCountRawnumber
기계에 의한 요청과 반복을 포함해, 재작성된 링크에 대한 모든 방문입니다.
firstOpenAtstring | null
사본 전체에서 집계된 가장 이른 열람 시각이며, 아직 없으면 null입니다. 기계적 히트는 이 값을 움직이지 않습니다.
lastOpenAtstring | null
사본 전체에서 집계된 가장 최근 열람 시각이며, 아직 없으면 null입니다.
firstClickAtstring | null
사본 전체에서 집계된 가장 이른 클릭 시각이며, 아직 없으면 null입니다.
lastClickAtstring | null
사본 전체에서 집계된 가장 최근 클릭 시각이며, 아직 없으면 null입니다.
recipientsTrackingRecipientResource[]
추적되는 사본마다 항목이 하나씩 생깁니다. 전송 방식이 사람마다 바이트를 다르게 보낼 수 있으면 수신자별로, 그렇지 않으면 공유 항목 하나로 만들어집니다. 공유 항목은 실제로 무언가 들어온 경우가 아니면 제외되므로, 아무 일도 없었던 “누군가” 행이 실제 이름 옆에 나란히 놓이는 일은 없습니다.
recipients[].emailstring | null
이 사본이 전달된 대상이며, 발송 시점의 값을 소문자로 담습니다. `attributed`가 false일 때에만 null입니다.
recipients[].kind'to' | 'cc' | 'bcc' | null
해당 주소가 어느 헤더에 있었는지를 나타내므로, 리포트를 메시지와 같은 방식으로 읽을 수 있습니다. 어떤 주소에도 속하지 않는 공유 사본에서는 null입니다.
recipients[].attributedboolean
이 행이 특정 사람을 지목하는지 여부입니다. `email`보다 먼저 확인하십시오. false는 공유 사본으로, 히트가 하나라도 들어오면 곧바로 목록에 나타납니다. 수신자가 한 명뿐인 메시지라 하더라도 그 히트에 이름을 붙이는 것은 이 방식이 제공할 수 없는 유일한 사실을 지어내는 일입니다.
recipients[].openCountnumber
이 사본에만 집계된 열람 수이며, 메시지 합계와 동일한 제외 규칙이 적용됩니다. 기계적 히트는 버리고, 30초 이내의 반복은 하나로 합칩니다.
recipients[].clickCountnumber
이 사본에만 집계된 클릭 수이며, 사본 단위가 아니라 링크 단위로 중복을 제거합니다.
recipients[].firstOpenAtstring | null
이 사본에서 집계된 가장 이른 열람 시각이며, 아직 없으면 null입니다.
recipients[].lastOpenAtstring | null
이 사본에서 집계된 가장 최근 열람 시각이며, 아직 없으면 null입니다.
recipients[].firstClickAtstring | null
이 사본에서 집계된 가장 이른 클릭 시각이며, 아직 없으면 null입니다.
recipients[].lastClickAtstring | null
이 사본에서 집계된 가장 최근 클릭 시각이며, 아직 없으면 null입니다.
linksTrackingLinkResource[]
이 메시지에서 재작성된 모든 링크이며, 본문에 놓여 있던 순서대로 정렬됩니다. 재작성된 링크가 없으면 비어 있습니다. `clicks`를 끈 채 보낸 메시지이거나, 본문에 링크가 전혀 없던 메시지가 여기에 해당합니다.
links[].idstring
링크 자체의 id인 `lnk_…`입니다. 클릭 행의 `linkId`가 가리키는 값이므로, `listClicks`로 얻은 히트를 여기 있는 항목과 다시 맞춰 볼 수 있습니다.
links[].urlstring
링크가 실제로 향하는 곳이며, 재작성 전 메시지에 있던 그대로입니다. 리디렉터는 id를 이 값으로 되돌린 다음 방문자를 그쪽으로 보냅니다.
links[].labelstring | null
메시지에 나타난 그대로의 앵커 텍스트이며, 이미지나 맨 URL처럼 앵커 텍스트가 없던 링크에서는 null입니다. 리포트가 추적 파라미터 세 개가 붙은 URL을 그대로 인용하는 대신 “가격 안내 링크”라고 말할 수 있도록 존재하며, 결코 `url`을 대신하지 않습니다.
links[].clickCountnumber
이 링크에 집계된 방문 수이며, 사본 전체를 합산합니다. 메시지의 `clickCount`와 동일한 링크별 30초 창이 적용됩니다.
links[].clickCountRawnumber
이 링크에 대한 모든 방문이며, 기계적 히트와 반복도 포함합니다.