Отслеживание открытий и переходов
`emails.get_tracking` и всё пространство имён `tracking`.
Одно сообщение
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]}" }Для сообщения, которое никогда не отслеживалось, выбрасывается OpenEmail::NotFoundError, у которого not_found? равно true, а не возвращается пустой отчёт. «Мы ничего не записали» и «никто не открыл» являются разными ответами и не должны выглядеть одинаково. Сообщение, отправленное с тестовым ключом, никогда не отслеживается, поэтому для него эта ошибка выбрасывается всегда.
По всему почтовому ящику
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_…, либо собственный 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_`.
api_keyString- Запрашивает список с этим ключом вместо ключа клиента.
Ответ: отчёт о трекинге
emails.get_tracking и tracking.get возвращают один отчёт как Hash с ключами типа Symbol, а tracking.list возвращает страницу таких отчётов.
objectString- Всегда `tracking` у отчёта, полученного самостоятельно, через `tracking.get`, `tracking.list` или `emails.get_tracking`. Тот же отчёт, вложенный как `tracking` в сообщение из `emails.get`, приходит без этого ключа, потому что там он является частью сообщения, а не отдельно полученным объектом.
idString- Собственный идентификатор записи трекинга, `tmsg_…`. Именно по нему работают `list_opens` и `list_clicks`, а `msg_…`, переданный им, сначала сопоставляется с этим идентификатором.
sendIdString or nil- Отправка `msg_…`, которой соответствует этот отчёт, и nil, если запись об отправке не создавалась. Композер, `sendEmail` из MCP и ассистент отправляют без такой записи. Трекинг охватывает почтовый ящик, а не только API-трафик.
threadIdString or nil- Заполняется после передачи, чтобы интерфейс чтения мог снова найти сообщение, и равно nil, если драйвер ничего не сообщил. Ни на что не влияет: запись, где это поле nil, всё равно учитывается.
messageIdString or nil- Message-ID по RFC 5322, а не наш идентификатор. Тоже заполняется после передачи и равно nil, если транспорт не вернул ничего, чем его заполнить.
subjectString or nil- Тема в том виде, в каком она была при отправке. nil у сообщения, записанного без темы.
fromString- Адрес отправителя, скопированный в запись, а не подтянутый из отправки. Отчёты читают спустя долгое время, и адрес, с тех пор исправленный или удалённый, иначе переписал бы историю.
sourceString- Какая поверхность его отправила: `composer`, `api`, `mcp`, `ai` или `queue`. Может появиться поверхность, которую этот гем пока не называет, поэтому считайте незнакомое значение информацией, а не ошибкой.
sentAtString or nil- Когда сообщение ушло, в виде момента ISO 8601. nil у записи, отправка которой так и не завершилась. `tracking.list` такие записи не показывает, а `get` показывает.
opensBoolean- Был ли к этому письму ПРИМЕНЁН пиксель. Это то, что было сделано, а не то, что говорит настройка аккаунта сейчас.
clicksBoolean- Были ли переписаны ссылки этого сообщения. False, если в теле не было ссылок, потому что тогда ничего не менялось, а запись, утверждающую обратное, нельзя было бы сверить с байтами.
openedBoolean- Было ли зафиксировано хоть одно засчитанное открытие среди копий. Читайте его вместе с `opens`: отсутствие данных из-за того, что их не собирали, говорит совсем не о том, что сообщение никто не прочитал.
clickedBoolean- Был ли зафиксирован хоть один засчитанный клик. Более веское свидетельство, чем открытие, поскольку изображения блокируют гораздо чаще, чем не переходят по ссылкам.
attributableBoolean- Можно ли каждое прочтение здесь привязать к конкретному получателю. False в тот момент, когда непривязанная копия показывает засчитанную активность: это случай нескольких получателей, когда одно тело уходит всему списку под одним токеном, так что проверьте его, прежде чем писать «Боб это не открывал».
openCountInteger- Открытия, которые считаются вызванными человеком, просуммированные по копиям. Машинные обращения исключены, а повторы в пределах тридцати секунд схлопываются в одно, так что именно эту цифру стоит показывать читателю.
clickCountInteger- Засчитанные клики, просуммированные по копиям. Дедуплицируются по ссылке, а не по сообщению, так что две разные ссылки, пройденные с разницей в секунды, дают два клика.
openCountRawInteger- Каждая загрузка пикселя, включая сканеры и прокси конфиденциальности. `openCountRaw` минус `openCount` показывает, сколько обращений было отсеяно (машинные загрузки и повторы в пределах тридцати секунд вместе), и это единственное доступное свидетельство того, что фильтрация вообще происходила.
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- Кому ушла эта копия, в нижнем регистре и в том виде, в каком адрес был при отправке. nil ровно тогда, когда `attributed` равно false.
kindString or nil- `to`, `cc` или `bcc`: в каком заголовке стоял адрес, чтобы отчёт читался так же, как сообщение. nil у общей копии, которая не принадлежит ни одному адресу.
attributedBoolean- Называет ли эта строка конкретного человека. Читайте её раньше `email`: false означает общую копию, которая попадает в список, как только на неё приходит хотя бы одно обращение, и приписать этому обращению имя, даже в письме с единственным получателем, значило бы выдумать тот единственный факт, которого механизм дать не может.
openCountInteger- Засчитанные открытия только по этой копии, с теми же исключениями, что и в итоге по письму: машинные обращения отброшены, а повторы в пределах тридцати секунд схлопнуты в одно.
clickCountInteger- Засчитанные клики только по этой копии, с дедупликацией по ссылке, а не по копии.
firstOpenAtString or nil- Самое раннее учтённое открытие этой копии, nil, пока его нет.
lastOpenAtString or nil- Последнее учтённое открытие этой копии, nil, пока его нет.
firstClickAtString or nil- Самый ранний учтённый клик по этой копии, nil, пока его нет.
lastClickAtString or nil- Последний учтённый клик по этой копии, nil, пока его нет.
Каждая запись в links
idString- Собственный идентификатор ссылки, `lnk_…`. Именно это значение называет `linkId` в строке клика, поэтому обращение из `list_clicks` можно сопоставить с записью здесь.
urlString- Куда на самом деле ведёт ссылка, в том виде, в каком она была в сообщении до переписывания. Редиректор находит по идентификатору этот адрес и отправляет посетителя дальше.
labelString or nil- Текст ссылки в том виде, в каком он был в сообщении, или nil, если его не было, например у изображения или голого URL. Он нужен, чтобы отчёт мог сказать «ссылка на цены», а не цитировать URL с тремя параметрами трекинга, и он никогда не заменяет `url`.
clickCountInteger- Засчитанные переходы по этой ссылке, просуммированные по копиям. То же тридцатисекундное окно на ссылку, что и у `clickCount` письма.
clickCountRawInteger- Все переходы по этой ссылке, включая машинные обращения и повторы.