Перейти к документации
Python

Отслеживание открытий и переходов

`emails.get_tracking` и весь ресурс `tracking`.

Одно сообщение

tracking.py
from openemail import openemail report = openemail.emails.get_tracking('msg_…') print(report['openCount'], 'opens from', len(report['recipients']), 'recipients')for link in report['links']:    print(link['url'], link['clickCount'])

Для сообщения, которое никогда не отслеживалось, выбрасывается OpenEmailApiError с истинным is_not_found, а не возвращается пустой отчёт. «Мы ничего не записали» и «никто не открыл» являются разными ответами и не должны делить один ответ.

По всему почтовому ящику

tracking_report.py
import time from openemail import openemail openemail.tracking.list(opened=False, days=7, limit=100)openemail.tracking.get_stats(days=30, offset_minutes=time.localtime().tm_gmtoff // 60)openemail.tracking.get('msg_…')openemail.tracking.list_opens('msg_…', include_machine=True)openemail.tracking.list_clicks('msg_…')

list, list_opens и list_clicks возвращают одну страницу, {'items': [...], 'hasMore': ..., 'nextCursor': ...}, а 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 считаются по ОТСЛЕЖИВАЕМЫМ сообщениям, а не по всему отправленному. Иначе почтовый ящик, отслеживающий одно сообщение из десяти, выглядел бы рухнувшим.

Параметры: tracking.list

openedbool
`True` выбирает сообщения хотя бы с одним засчитанным открытием, а `False` выбирает отслеживаемые сообщения без единого. Ни то ни другое не является значением по умолчанию, и `False` никогда не означает неотслеживаемую почту, которой в этом списке нет вовсе.
clickedbool
Тот же фильтр для засчитанных кликов, применяемый независимо от `opened`. Можно задать оба, и сообщения должны удовлетворять обоим.
daysint
На сколько дней назад от текущего момента смотреть: от 1 до 365, по умолчанию 30; значение вне этого диапазона даёт 422. Окно отсчитывается по моменту создания записи трекинга, и перечисляются только записи, чья отправка действительно ушла.
limitint
Отчётов на страницу: от 1 до 200, по умолчанию 50, новые сверху. Чтобы получить следующую, передайте `nextCursor` страницы как `cursor` с теми же фильтрами, либо доверьте `list_all` и `iterate` пройти всё окно.

Ответ: TrackingResource

objectLiteral['tracking']
Всегда `'tracking'` у отчёта, полученного сам по себе (через `tracking.get`, `tracking.list` или `emails.get_tracking`). Тот же отчёт, вложенный как `email['tracking']` в полученное сообщение, приходит без этого ключа, потому что там он часть того объекта, а не что-то запрошенное отдельно.
idstr
Собственный идентификатор записи трекинга, `tmsg_…`. Именно на нём ключуются вызовы по отдельным обращениям (`list_opens` и `list_clicks`); переданный им `msg_…` сперва разрешается в него.
sendIdstr | None
Отправка `msg_…`, с которой это соотносится; null там, где запись об отправке не создавалась. Композер, `sendEmail` в MCP и ассистент отправляют без неё. Отслеживание охватывает почтовый ящик, а не только трафик API.
threadIdstr | None
Заполняется после передачи, чтобы интерфейс чтения мог снова найти письмо; null там, где драйвер ничего не сообщил. Не несёт нагрузки: запись с null здесь всё равно учитывается.
messageIdstr | None
Message-ID по RFC 5322, а не наш идентификатор. Тоже заполняется после передачи; null там, где транспорт не вернул ничего, чем его заполнить.
subjectstr | None
Тема в том виде, в каком она была на момент отправки. Null для письма, записанного без темы.
fromstr
Адрес отправителя, скопированный в запись, а не подтянутый из отправки. Отчёты читают спустя долгое время, и адрес, с тех пор исправленный или удалённый, иначе переписал бы историю.
sourceEmailSource | str
Какая поверхность отправила письмо: `composer`, `api`, `mcp`, `ai`, `oauth` или `form`. Тип открытый, поэтому поверхность, которую этот SDK ещё не называет, не станет ломающим изменением.
sentAtstr | None
Когда письмо ушло, как момент времени ISO-8601. Null для записи, отправка которой так и не завершилась. `tracking.list` такие исключает, а `get` не исключает.
opensbool
Был ли к этому письму ПРИМЕНЁН пиксель. Это то, что было сделано, а не то, что говорит настройка аккаунта сейчас.
clicksbool
Были ли переписаны ссылки этого сообщения. False, когда в теле не было ссылок, потому что тогда ничего не менялось, а запись, утверждающая обратное, не сошлась бы с байтами.
openedbool
Было ли зафиксировано хоть одно засчитанное открытие среди копий. Читайте его вместе с `opens`: отсутствие данных из-за того, что их не собирали, говорит совсем не о том, что сообщение никто не прочитал.
clickedbool
Был ли зафиксирован хоть один засчитанный клик. Более веское свидетельство, чем открытие, поскольку изображения блокируют гораздо чаще, чем не переходят по ссылкам.
attributablebool
Можно ли каждое прочтение здесь привязать к конкретному получателю. False в тот момент, когда непривязанная копия показывает засчитанную активность: это случай нескольких получателей, когда одно тело уходит всему списку под одним токеном, так что проверьте его, прежде чем писать «Боб это не открывал».
openCountint
Открытия, которые считаются вызванными человеком, просуммированные по копиям. Машинные обращения исключены, а повторы в пределах тридцати секунд схлопываются в одно, так что именно эту цифру стоит показывать читателю.
clickCountint
Засчитанные клики, просуммированные по копиям. Дедуплицируются по ссылке, а не по сообщению, так что две разные ссылки, пройденные с разницей в секунды, дают два клика.
openCountRawint
Каждая загрузка пикселя, включая сканеры и прокси приватности. `openCountRaw - openCount` показывает, сколько их отсеял классификатор, и служит единственным доступным свидетельством того, что фильтрация вообще была.
clickCountRawint
Каждое посещение переписанной ссылки, включая машинные обращения и повторы.
firstOpenAtstr | None
Самое раннее засчитанное открытие среди копий, и null, пока его нет. Машинные обращения его не сдвигают.
lastOpenAtstr | None
Самое последнее засчитанное открытие среди копий; null, пока его нет.
firstClickAtstr | None
Самый ранний засчитанный клик среди копий; null, пока его нет.
lastClickAtstr | None
Самый последний засчитанный клик среди копий; null, пока его нет.
recipientslist[TrackingRecipientResource]
По одной записи на каждую отслеживаемую копию: по одной на получателя там, где транспорт позволяет байтам различаться для каждого человека, и одна общая запись там, где не позволяет. Общая запись отбрасывается, если на неё в действительности ничего не попало, поэтому нетронутая строка «кто-то» никогда не стоит рядом с настоящими именами.
recipients[].emailstr | None
Кому ушла эта копия: адрес в нижнем регистре и в том виде, в каком он был на момент отправки. Null ровно тогда, когда `attributed` равно false.
recipients[].kindRecipientKind | None
В каком заголовке стоял адрес, чтобы отчёт читался так же, как читалось письмо. Null для общей копии, которая не принадлежит ни одному адресу.
recipients[].attributedbool
Называет ли эта строка конкретного человека. Читайте её раньше `email`: false означает общую копию, которая попадает в список, как только на неё приходит хотя бы одно обращение, и приписать этому обращению имя, даже в письме с единственным получателем, значило бы выдумать тот единственный факт, которого механизм дать не может.
recipients[].openCountint
Засчитанные открытия только по этой копии, с теми же исключениями, что и в итоге по письму: машинные обращения отброшены, а повторы в пределах тридцати секунд схлопнуты в одно.
recipients[].clickCountint
Засчитанные клики только по этой копии, с дедупликацией по ссылке, а не по копии.
recipients[].firstOpenAtstr | None
Самое раннее засчитанное открытие этой копии; null, пока его нет.
recipients[].lastOpenAtstr | None
Самое последнее засчитанное открытие этой копии; null, пока его нет.
recipients[].firstClickAtstr | None
Самый ранний засчитанный клик по этой копии; null, пока его нет.
recipients[].lastClickAtstr | None
Самый последний засчитанный клик по этой копии; null, пока его нет.
linkslist[TrackingLinkResource]
Все ссылки, переписанные в этом письме, в порядке их расположения в теле. Пусто там, где переписывать было нечего: письмо отправлено с выключенным `clicks` или в его теле вообще не было ссылок.
links[].idstr
Собственный идентификатор ссылки, `lnk_…`. Это то значение, которое называет `linkId` в строке клика, поэтому обращение из `list_clicks` можно сопоставить с записью отсюда.
links[].urlstr
Куда ссылка ведёт на самом деле, в том виде, в каком она была в письме до переписывания. Редиректор разворачивает идентификатор обратно в это значение и отправляет посетителя дальше.
links[].labelstr | None
Текст ссылки в том виде, в каком он был в письме, или null там, где текста не было (например, у изображения или у голого URL). Он нужен, чтобы отчёт мог сказать «ссылка на тарифы», а не цитировать URL с тремя параметрами отслеживания, и он никогда не заменяет `url`.
links[].clickCountint
Засчитанные переходы по этой ссылке, просуммированные по копиям. То же тридцатисекундное окно на ссылку, что и у `clickCount` письма.
links[].clickCountRawint
Все переходы по этой ссылке, включая машинные обращения и повторы.

Справочник