Отслеживание открытий и кликов
`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)Сообщение, которое никогда не отслеживалось, бросает OpenEmailApiError, у которого isNotFound равно true, а не отдаёт пустой отчёт. «Мы ничего не записали» и «никто не открыл» — разные ответы, и они не должны делить один ответ.
По всему почтовому ящику
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 разрешаются в обычные массивы. get, listOpens и listClicks принимают либо идентификатор отправки msg_…, либо собственный 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'` у отчёта, полученного сам по себе — через `tracking.get`, `tracking.list` или `emails.getTracking`. Тот же отчёт, вложенный как `email.tracking` в полученное сообщение, приходит без этого ключа, потому что там он часть того объекта, а не что-то запрошенное отдельно.
idstring- Собственный идентификатор записи трекинга, `tmsg_…`. Именно на нём ключуются вызовы по отдельным обращениям — `listOpens` и `listClicks`; переданный им `msg_…` сперва разрешается в него.
sendIdstring | null- Отправка `msg_…`, с которой это соотносится; null там, где запись об отправке не создавалась. Композер, `sendEmail` в MCP и ассистент отправляют без неё. Отслеживание охватывает почтовый ящик, а не только трафик API.
threadIdstring | null- Заполняется после передачи, чтобы интерфейс чтения мог снова найти письмо; null там, где драйвер ничего не сообщил. Не несёт нагрузки: запись с null здесь всё равно учитывается.
messageIdstring | null- Message-ID по RFC 5322, а не наш идентификатор. Тоже заполняется после передачи; 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 в тот момент, когда непривязанная копия показывает засчитанную активность, — это случай нескольких получателей, когда одно тело уходит всему списку под одним токеном, так что проверьте его, прежде чем писать «Боб это не открывал».
openCountnumber- Открытия, которые считаются вызванными человеком, просуммированные по копиям. Машинные обращения исключены, а повторы в пределах тридцати секунд схлопываются в одно, так что именно эту цифру стоит показывать читателю.
clickCountnumber- Засчитанные клики, просуммированные по копиям. Дедуплицируются по ссылке, а не по сообщению, так что две разные ссылки, пройденные с разницей в секунды, — это два клика.
openCountRawnumber- Каждая загрузка пикселя, включая сканеры и прокси приватности. `openCountRaw - openCount` — это сколько их отсеял классификатор и единственное доступное свидетельство того, что фильтрация вообще была.
clickCountRawnumber- Каждое посещение переписанной ссылки, включая машинные обращения и повторы.
firstOpenAtstring | null- Самое раннее засчитанное открытие среди копий; null, пока его нет. Машинные обращения его никогда не сдвигают.
lastOpenAtstring | null- Самое последнее засчитанное открытие среди копий; null, пока его нет.
firstClickAtstring | null- Самый ранний засчитанный клик среди копий; null, пока его нет.
lastClickAtstring | null- Самый последний засчитанный клик среди копий; null, пока его нет.
recipientsTrackingRecipientResource[]- По одной записи на каждую отслеживаемую копию: по одной на получателя там, где транспорт позволяет байтам различаться для каждого человека, и одна общая запись там, где не позволяет. Общая запись отбрасывается, если на неё в действительности ничего не попало, поэтому нетронутая строка «кто-то» никогда не стоит рядом с настоящими именами.
recipients[].emailstring | null- Кому ушла эта копия — в нижнем регистре и в том виде, в каком адрес был на момент отправки. Null ровно тогда, когда `attributed` равно false.
recipients[].kind'to' | 'cc' | 'bcc' | null- В каком заголовке стоял адрес, чтобы отчёт читался так же, как читалось письмо. Null для общей копии, которая не принадлежит ни одному адресу.
recipients[].attributedboolean- Называет ли эта строка конкретного человека. Читайте её раньше `email`: false — это общая копия, которая попадает в список, как только на неё приходит хотя бы одно обращение, и приписать этому обращению имя, даже в письме с единственным получателем, значило бы выдумать тот единственный факт, которого механизм дать не может.
recipients[].openCountnumber- Засчитанные открытия только по этой копии, с теми же исключениями, что и в итоге по письму: машинные обращения отброшены, а повторы в пределах тридцати секунд схлопнуты в одно.
recipients[].clickCountnumber- Засчитанные клики только по этой копии, с дедупликацией по ссылке, а не по копии.
recipients[].firstOpenAtstring | null- Самое раннее засчитанное открытие этой копии; null, пока его нет.
recipients[].lastOpenAtstring | null- Самое последнее засчитанное открытие этой копии; null, пока его нет.
recipients[].firstClickAtstring | null- Самый ранний засчитанный клик по этой копии; null, пока его нет.
recipients[].lastClickAtstring | null- Самый последний засчитанный клик по этой копии; null, пока его нет.
linksTrackingLinkResource[]- Все ссылки, переписанные в этом письме, в порядке их расположения в теле. Пусто там, где переписывать было нечего: письмо отправлено с выключенным `clicks` или в его теле вообще не было ссылок.
links[].idstring- Собственный идентификатор ссылки, `lnk_…`. Это то значение, которое называет `linkId` в строке клика, поэтому обращение из `listClicks` можно сопоставить с записью отсюда.
links[].urlstring- Куда ссылка ведёт на самом деле — в том виде, в каком она была в письме до переписывания. Редиректор разворачивает идентификатор обратно в это значение и отправляет посетителя дальше.
links[].labelstring | null- Текст ссылки в том виде, в каком он был в письме, или null там, где текста не было, — например, у изображения или у голого URL. Он нужен, чтобы отчёт мог сказать «ссылка на тарифы», а не цитировать URL с тремя параметрами отслеживания, и он никогда не заменяет `url`.
links[].clickCountnumber- Засчитанные переходы по этой ссылке, просуммированные по копиям. То же тридцатисекундное окно на ссылку, что и у `clickCount` письма.
links[].clickCountRawnumber- Все переходы по этой ссылке, включая машинные обращения и повторы.