Отслеживание открытий и переходов
`emails.get_tracking` и весь ресурс `tracking`.
Одно сообщение
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, а не возвращается пустой отчёт. «Мы ничего не записали» и «никто не открыл» являются разными ответами и не должны делить один ответ.
По всему почтовому ящику
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- Все переходы по этой ссылке, включая машинные обращения и повторы.
Справочник
emails.get_tracking()Полный справочникtracking.list()Полный справочникtracking.list_all()Полный справочникtracking.iterate()Полный справочникtracking.get_stats()Полный справочникtracking.get()Полный справочникtracking.list_opens()Полный справочникtracking.list_all_opens()Полный справочникtracking.iterate_opens()Полный справочникtracking.list_clicks()Полный справочникtracking.list_all_clicks()Полный справочникtracking.iterate_clicks()Полный справочник