문서로 건너뛰기
Ruby

열람 및 클릭 추적

`emails.get_tracking`과 `tracking` 네임스페이스 전체.

메시지 하나

tracking.rb
report = client.emails.get_tracking("msg_3f9a1c07d2b84e6a9c5b1f20") puts "#{report[:openCount]} opens from #{report[:recipients].size} recipients"report[:links].each { |link| puts "#{link[:url]} #{link[:clickCount]}" }

추적된 적 없는 메시지는 빈 보고서가 아니라 not_found?가 true인 OpenEmail::NotFoundError를 발생시킵니다. “아무것도 기록하지 않았다”와 “아무도 열지 않았다”는 다른 답이며 하나의 응답을 공유해서는 안 됩니다. 테스트 키로 보낸 메시지는 결코 추적되지 않으므로, 항상 이 예외를 발생시킵니다.

메일함 전체에서

tracking_report.rb
client.tracking.list(opened: false, days: 7, limit: 100)client.tracking.get_stats(days: 30, offset_minutes: Time.now.utc_offset / 60)client.tracking.get("msg_3f9a1c07d2b84e6a9c5b1f20")client.tracking.list_opens("msg_3f9a1c07d2b84e6a9c5b1f20", include_machine: true)client.tracking.list_clicks("msg_3f9a1c07d2b84e6a9c5b1f20")

list, list_opens, list_clicks는 OpenEmail::Page 하나를 반환하고, list_all, iterate, list_all_opens, iterate_opens, list_all_clicks, iterate_clicks가 모든 페이지를 대신 훑어 줍니다. get, list_opens, list_clicks는 msg_… 발송 id와 추적 레코드 자체의 tmsg_… 중 어느 쪽이든 받습니다.

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

숫자를 정직하게 읽기

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

tracking.get_stats의 비율은 보낸 메일 전체가 아니라 추적된 메시지를 모수로 합니다. 그렇지 않으면 열 통 중 한 통만 추적하는 메일함이 붕괴한 것처럼 보일 것입니다. openRate와 clickRate는 42.5처럼 소수점 한 자리로 반올림한 백분율이며, 0과 1 사이의 비율이 아닙니다.

매개변수: tracking.list

openedBoolean
`true`는 집계된 열람이 하나 이상인 메시지를, `false`는 집계된 열람이 없는 추적된 메시지를 선택합니다. 어느 쪽도 기본값이 아니며, `false`가 추적되지 않은 메일을 뜻하는 일은 없습니다. 그런 메일은 이 목록에 아예 나오지 않습니다.
clickedBoolean
집계된 클릭에 대한 같은 필터이며, `opened`와 독립적으로 적용됩니다. 둘 다 줄 수 있고, 그때 메시지는 둘 다 만족해야 합니다.
daysInteger
지금부터 며칠 전까지 볼지이며, 1에서 365까지이고 기본값은 30입니다. 범위를 벗어나면 422입니다. 이 기간은 추적 레코드가 만들어진 시각을 기준으로 하며, 발송이 실제로 나간 레코드만 나열됩니다.
minutesInteger
대신 분 단위로 지정하는 기간이며, 1에서 527040까지입니다. 둘 다 설정하면 `days`보다 우선합니다. 하루보다 짧은 기간에는 더 세밀한 `grain`이 필요합니다.
grainString
`minute`, `hour`, `day` 중 하나이며 기본값은 `day`입니다. 기간의 시작을 내림할 뿐이므로, 이 목록은 같은 단위로 읽은 `get_stats`와 일치하며 응답의 형태에는 아무 영향을 주지 않습니다.
limitInteger
페이지당 보고서 수이며 최신순으로, 1에서 200까지이고 기본값은 50입니다. 다음 페이지는 같은 필터와 함께 페이지의 `next_cursor`를 `cursor:`로 넘겨 받고, 기간 전체는 `list_all`과 `iterate`에 맡기면 됩니다.
cursorString
이전 페이지의 `next_cursor`이며, `tmsg_` id입니다.
api_keyString
클라이언트의 키 대신 이 키로 목록을 가져옵니다.

응답: 추적 보고서

emails.get_tracking과 tracking.get은 보고서 하나를 Symbol 키를 가진 Hash로 반환하고, tracking.list는 그런 보고서의 페이지를 반환합니다.

objectString
`tracking.get`, `tracking.list`, `emails.get_tracking`으로 그 자체를 가져온 보고서에서는 항상 `tracking`입니다. `emails.get`에서 받은 메시지에 `tracking`으로 중첩된 같은 보고서에는 이 키가 없는데, 거기서는 가져온 것이 아니라 그 메시지의 일부이기 때문입니다.
idString
추적 레코드 자체의 id인 `tmsg_…`입니다. `list_opens`와 `list_clicks`가 이 값을 키로 삼으며, 그 호출에 `msg_…`를 넘기면 먼저 이 값으로 해석됩니다.
sendIdString or nil
이 기록이 연결되는 `msg_…` 발송이며, 발송 레코드가 기록되지 않았으면 nil입니다. 작성기, MCP의 `sendEmail`, 어시스턴트는 모두 발송 레코드 없이 메일을 보냅니다. 추적은 API 트래픽만이 아니라 메일함 전체를 대상으로 합니다.
threadIdString or nil
읽기 UI가 메시지를 다시 찾을 수 있도록 전송 후에 채워지며, 드라이버가 아무 값도 보고하지 않았으면 nil입니다. 필수적인 값은 아닙니다: 이 값이 nil인 레코드도 그대로 집계됩니다.
messageIdString or nil
이 API의 id가 아니라 RFC 5322 Message-ID입니다. 이 값도 전송 후에 채워지며, 전송 계층이 채울 값을 돌려주지 않았으면 nil입니다.
subjectString or nil
발송 시점의 제목입니다. 제목 없이 기록된 메시지에서는 nil입니다.
fromString
발신 주소이며, 발송 레코드와 조인하지 않고 이 레코드에 복사해 둡니다. 리포트는 한참 뒤에 읽히기 마련이고, 그러지 않으면 그사이 수정되거나 삭제된 주소가 과거 기록까지 바꿔 버리기 때문입니다.
sourceString
어떤 경로로 보냈는지입니다: `composer`, `api`, `mcp`, `ai`, `queue` 중 하나. 이 gem이 아직 이름을 모르는 경로가 나타날 수도 있으므로, 알 수 없는 값은 오류가 아니라 정보로 취급하세요.
sentAtString or nil
메시지가 나간 시각이며, ISO 8601 시각으로 표기됩니다. 발송이 끝내 완료되지 않은 레코드에서는 nil입니다. `tracking.list`는 그런 레코드를 제외하지만 `get`은 제외하지 않습니다.
opensBoolean
이 메시지에 픽셀이 실제로 적용되었는지 여부입니다. 지금 계정 설정이 무엇이라고 말하는지가 아니라, 당시에 무엇을 했는지를 담습니다.
clicksBoolean
이 메시지의 링크가 재작성되었는지 여부입니다. 본문에 링크가 없었다면 false인데, 그때는 바뀐 것이 없고 그렇지 않다고 주장하는 기록은 실제 바이트와 맞출 수 없기 때문입니다.
openedBoolean
사본 전체에서 집계된 열람이 하나라도 기록되었는지 여부입니다. `opens`와 함께 읽으세요. 수집하지 않아 데이터가 없는 것과 아무도 메시지를 읽지 않은 것은 다른 사실입니다.
clickedBoolean
집계된 클릭이 하나라도 기록되었는지 여부입니다. 열람보다 강한 증거인데, 이미지가 차단되는 경우가 링크를 따라가지 않는 경우보다 훨씬 많기 때문입니다.
attributableBoolean
여기의 모든 열람을 특정 수신자에게 귀속시킬 수 있는지 여부입니다. 귀속되지 않은 사본에서 집계된 활동이 나타나는 순간 false가 되며, 이는 하나의 본문이 하나의 토큰으로 수신자 전체에게 가는 다중 수신자 상황입니다. "Bob은 이 메일을 열지 않았다"고 쓰기 전에 이 값을 확인하세요.
openCountInteger
사람이 발생시킨 것으로 보이는 열람을 사본 전체에 대해 합한 값입니다. 기계에 의한 요청은 제외되고 30초 이내의 반복은 하나로 합쳐지므로, 읽는 사람 앞에 내놓아야 할 수치는 이것입니다.
clickCountInteger
사본 전체에 대해 합한 집계 클릭 수입니다. 메시지 단위가 아니라 링크 단위로 중복 제거되므로, 몇 초 간격으로 따라간 서로 다른 두 링크는 두 번의 클릭입니다.
openCountRawInteger
스캐너와 프라이버시 프록시를 포함한 모든 픽셀 요청입니다. `openCountRaw`에서 `openCount`를 뺀 값이 걸러 낸 수로, 기계에 의한 요청과 30초 이내의 반복을 합친 것이며, 그 필터링이 일어났다는 사실에 대해 구할 수 있는 유일한 증거입니다.
clickCountRawInteger
기계에 의한 요청과 반복을 포함해, 재작성된 링크에 대한 모든 방문입니다.
firstOpenAtString or nil
사본 전체에서 가장 이른 집계 열람이며, 없으면 nil입니다. 기계에 의한 요청은 이 값을 움직이지 않습니다.
lastOpenAtString or nil
사본 전체에서 집계된 가장 최근 열람 시각이며, 아직 없으면 nil입니다.
firstClickAtString or nil
사본 전체에서 집계된 가장 이른 클릭 시각이며, 아직 없으면 nil입니다.
lastClickAtString or nil
사본 전체에서 집계된 가장 최근 클릭 시각이며, 아직 없으면 nil입니다.
recipientsArray<Hash>
추적되는 사본마다 항목이 하나씩 생깁니다. 전송 방식이 사람마다 바이트를 다르게 보낼 수 있으면 수신자별로, 그렇지 않으면 공유 항목 하나로 만들어집니다. 공유 항목은 실제로 무언가 들어온 경우가 아니면 제외되므로, 아무 일도 없었던 “누군가” 행이 실제 이름 옆에 나란히 놓이는 일은 없습니다.
linksArray<Hash>
이 메시지에서 재작성된 모든 링크이며, 본문에 놓여 있던 순서대로 정렬됩니다. 재작성된 링크가 없으면 비어 있습니다. `clicks`를 끈 채 보낸 메시지이거나, 본문에 링크가 전혀 없던 메시지가 여기에 해당합니다.

recipients의 각 항목

emailString or nil
이 사본이 전달된 대상이며, 발송 시점의 값을 소문자로 담습니다. `attributed`가 false일 때에만 nil입니다.
kindString or nil
`to`, `cc`, `bcc` 중 하나로, 해당 주소가 어느 헤더에 있었는지를 나타내므로 보고서를 메시지와 같은 방식으로 읽을 수 있습니다. 어떤 주소에도 속하지 않는 공유 사본에서는 nil입니다.
attributedBoolean
이 행이 특정 사람을 지목하는지 여부입니다. `email`보다 먼저 확인하십시오. false는 공유 사본으로, 히트가 하나라도 들어오면 곧바로 목록에 나타납니다. 수신자가 한 명뿐인 메시지라 하더라도 그 히트에 이름을 붙이는 것은 이 방식이 제공할 수 없는 유일한 사실을 지어내는 일입니다.
openCountInteger
이 사본에만 집계된 열람 수이며, 메시지 합계와 동일한 제외 규칙이 적용됩니다. 기계적 히트는 버리고, 30초 이내의 반복은 하나로 합칩니다.
clickCountInteger
이 사본에만 집계된 클릭 수이며, 사본 단위가 아니라 링크 단위로 중복을 제거합니다.
firstOpenAtString or nil
이 사본에서 집계된 가장 이른 열람 시각이며, 아직 없으면 nil입니다.
lastOpenAtString or nil
이 사본에서 집계된 가장 최근 열람 시각이며, 아직 없으면 nil입니다.
firstClickAtString or nil
이 사본에서 집계된 가장 이른 클릭 시각이며, 아직 없으면 nil입니다.
lastClickAtString or nil
이 사본에서 집계된 가장 최근 클릭 시각이며, 아직 없으면 nil입니다.

links의 각 항목

idString
링크 자체의 id인 `lnk_…`입니다. 클릭 행의 `linkId`가 가리키는 값이므로, `list_clicks`로 얻은 히트를 여기 있는 항목과 다시 맞춰 볼 수 있습니다.
urlString
링크가 실제로 향하는 곳이며, 재작성 전 메시지에 있던 그대로입니다. 리디렉터는 id를 이 값으로 되돌린 다음 방문자를 그쪽으로 보냅니다.
labelString or nil
메시지에 나타난 그대로의 앵커 텍스트이며, 이미지나 맨 URL처럼 앵커 텍스트가 없던 링크에서는 nil입니다. 보고서가 추적 파라미터 세 개가 붙은 URL을 그대로 인용하는 대신 “가격 안내 링크”라고 말할 수 있도록 존재하며, 결코 `url`을 대신하지 않습니다.
clickCountInteger
이 링크에 집계된 방문 수이며, 사본 전체를 합산합니다. 메시지의 `clickCount`와 동일한 링크별 30초 창이 적용됩니다.
clickCountRawInteger
이 링크에 대한 모든 방문이며, 기계적 히트와 반복도 포함합니다.