SDK
열람 및 클릭 추적
`emails.getTracking`과 `tracking` 리소스 전체.
메시지 하나
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를 던집니다. "아무것도 기록하지 않았다"와 "아무도 열지 않았다"는 다른 답이며 하나의 응답을 공유해서는 안 됩니다.
메일함 전체에서
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, listClicks는 msg_… 발송 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- 이 링크에 대한 모든 방문이며, 기계적 히트와 반복도 포함합니다.