Отправка и отслеживание писем
Отправляйте, отправляйте пакетами, переводите, планируйте и отменяйте почту командами `emails`, а затем следите за её доставкой, открытиями и кликами через `tracking`.
Обзор
Пространство имён emails это API отправки в виде команд, по одной на каждый метод openemail.emails в SDK. Каждая вызывает один эндпоинт и выводит то, что он возвращает. Пространство имён tracking читает открытия и клики по отправленной вами почте. openemail email работает вместо openemail emails.
Каждой команде здесь нужен вход, через браузер или по API-ключу, и один из двух scope: emails:send, чтобы отправлять, переводить, отменять и переносить, и emails:read для всего, что только читает.
Какую отправку выбрать
openemail send это написанная вручную команда со страницы «Почта», и она отправляет через emails send. Она сделана для человека за терминалом: выбирает адрес отправки, если вы не указали --from, читает тело из файла, stdin или редактора, прикрепляет файлы по пути и показывает сводку для подтверждения, прежде чем что-то уйдёт. openemail emails send принимает тело запроса флагами, по одному на поле, и ничего не спрашивает, что подходит скрипту, который точно знает, что отправляет.
| send | emails send |
|---|---|
| --from <address> | Обязателен, как --to, если его нет в --data. send может обойтись без него и выбрать адрес за вас |
| -f, --body-file <path> | Флага файла для тела нет. Передайте --html "$(cat body.html)" или весь запрос в --data @email.json |
| -a, --attach <path> | --attachments, JSON-массив файлов, каждый с filename и content в base64 или с fileId файла, который уже лежит в «Файлах» |
| --at <when> | --scheduled-at <when>, момент в ISO 8601 или длительность, например PT1H или P2D. send принимает и короткие задержки, например 10m, 2h и 1d |
| --undo <seconds> | --cancellable-for-seconds <n>, от 0 до 900 |
| --translate <language> | --translate '{"to":"de"}', который принимает также from, includeOriginal и subject |
| --template <id> --props <json> | --template '{"id":"welcome","props":{"name":"Ada"}}', который может также закрепить version |
| --draft <id> | --draft-id <id> |
| --thread <id> | --thread-id <id> |
| --tag <key=value> | --tags <key=value>, повторением или JSON-объектом |
Только у emails send есть --tracking, чтобы отключить открытия или клики для одной отправки, --signature, --headers для своих заголовков, --attachment-delivery, чтобы выбрать между вложением файлов и ссылками на них, и --data для всего тела в виде JSON: встроенно, из файла через @path или из stdin через -.
Завершаются они по-разному. send завершается с кодом 1, когда письмо возвращается со статусом failed. emails send завершается с кодом 0 всякий раз, когда API ответил, поэтому проверяйте status в выводе.
Все команды emails
send, send-batch, translate, cancel и reschedule нужен emails:send. list, get, list-events и get-tracking нужен emails:read. Идентификатор письма это msg_ и 24 шестнадцатеричных символа, в том виде, в каком его возвращает отправка.
| Команда | Что делает |
|---|---|
| openemail emails send --from <value> --to <a,b> | Отправить одно письмо сейчас, задержать его на окно отмены через --cancellable-for-seconds или запланировать через --scheduled-at. Тело это --html, --text или оба, сохранённый --template или сохранённый --draft-id |
| openemail emails send-batch <emails> | Отправить до 100 независимых писем одним запросом из JSON-массива в файле, встроенно или из stdin через -. Каждый элемент устроен как тело emails send и проходит или не проходит сам по себе |
| openemail emails translate --to <value> | Посмотреть заранее, что доставила бы отправка с переводом, для --subject, --html или --text. Ничего не сохраняется и не отправляется, тратится одно ИИ-действие |
| openemail emails list | Одна страница отправленных писем, новые сверху, с отбором по --status, --from или --broadcast-id |
| openemail emails get <id> | Одно отправленное письмо со статусом, ошибкой и временем доставки для каждого получателя и полным отчётом отслеживания, если оно отслеживалось |
| openemail emails list-events <id> | История событий одной отправки, старые сверху: приём, планирование, отправка, доставка, отказ, жалоба, открытие, клик и остальные |
| openemail emails get-tracking <id> | Отчёт о вовлечённости одной отправки: итоги, по одной записи на каждую отслеживаемую копию и каждая переписанная ссылка с её кликами |
| openemail emails cancel <id> | Остановить письмо в очереди или запланированное письмо до отправки. Просит подтверждения |
| openemail emails reschedule <id> <scheduled-at> | Перенести письмо в очереди или запланированное письмо на момент в ISO 8601 или на длительность, например PT30M, от одной секунды до 365 дней вперёд |
Все команды tracking
Всем пяти нужен emails:read. tracking get, list-opens и list-clicks принимают любой из двух идентификаторов письма: msg_, который вернула его отправка, или идентификатор отслеживания tmsg_, который несут tracking list и полезные нагрузки вебхуков.
| Команда | Что делает |
|---|---|
| openemail tracking list | Одна страница отслеживаемых писем, отправленных за период, новые сверху, каждое с полным отчётом. --opened и --clicked сужают её, а --no-opened оставляет те, которые никто не открыл. Период равен 30 дням, если --days или --minutes не говорит иначе |
| openemail tracking get-stats | Цифры за панелью вовлечённости: отслеживаемые, открытые и кликнутые письма, доли открытий и кликов, временной ряд с шагом --grain, а также самые популярные ссылки, почтовые клиенты и страны |
| openemail tracking get <id> | Отчёт о вовлечённости одного письма, тот же документ, что возвращает emails get-tracking |
| openemail tracking list-opens <id> | Отдельные открытия, из которых складывается число открытий письма, новые сверху, каждое с пометкой human, proxy или machine. --include-machine добавляет обращения, которые не засчитаны |
| openemail tracking list-clicks <id> | Отдельные клики по ссылкам письма, новые сверху, с исходным url каждой. --include-machine добавляет сканеры ссылок и схлопнутые повторы |
tracking list и get-stats охватывают каждое отслеживаемое письмо, отправленное ящиком, включая почту, написанную в веб-приложении, и почту, отправленную MCP-инструментами или ассистентом, а emails list содержит записи об отправках, созданные API. У отчёта без записи об отправке sendId равен null.
Примеры
Отправка из скрипта с собственным ключом идемпотентности. Повторный запуск с тем же --idempotency-key выводит первое письмо с replayed: true вместо того, чтобы отправить второе.
openemail emails send \ --from 'Acme Billing <[email protected]>' \ --to [email protected] \ --subject 'Your September invoice' \ --html '<p>The invoice is attached. Tell me if anything on it looks wrong.</p>' \ --attachments '[{"fileId":"file_6bb640f5b99e47deb758f1f5"}]' \ --tracking '{"opens":false}' \ --idempotency-key invoice:inv_2026_09_4192 \ --json | jq -r '.id + " " + .status'Пусть человек прочтёт перевод до отправки. Отправьте одобренный текст как обычные --subject и --html, без --translate, иначе он будет переведён второй раз. Переведённый html уже содержит ваш оригинал под переводом, если вы не передадите --no-include-original.
openemail emails translate --to de \ --subject 'Your September invoice' \ --html "$(cat invoice.html)" \ --json > preview.jsonjq -r .html preview.jsonopenemail emails send --from [email protected] --to [email protected] \ --subject "$(jq -r .subject preview.json)" \ --html "$(jq -r .html preview.json)"Отправка пакета из файла. Команда завершается с кодом 0 всякий раз, когда пакет был обработан, даже если часть элементов не прошла, поэтому читайте failed и status каждого элемента. Повторный запуск с тем же ключом воспроизводит уже ушедшие элементы и отправляет только остальные, пока массив сохраняет порядок.
[ { "from": "[email protected]", "to": "[email protected]", "subject": "Receipt 4192", "text": "Thanks for your order." }, { "from": "[email protected]", "to": "[email protected]", "subject": "Receipt 4193", "text": "Thanks for your order." }]openemail emails send-batch receipts.json --idempotency-key receipts:2026-09-27 --json > result.jsonjq '{ sent, failed }' result.jsonjq -r '.items[] | select(.status == "error") | "\(.index) \(.error.code)"' result.jsonЗапланируйте письмо, перенесите его и отмените. --yes отвечает на подтверждение, которое спрашивает cancel, чего скрипт сделать не может.
ID=$(openemail send --from [email protected] --to [email protected] --subject "Standup notes" \ --body-file notes.md --at 2026-10-01T09:00:00Z --json | jq -r .id)openemail emails reschedule "$ID" 2026-10-01T13:00:00Zopenemail emails get "$ID" --json | jq -r '.status + " " + .scheduledAt'openemail emails cancel "$ID" --yesНайдите неудавшиеся отправки и прочтите, что случилось с одной из них. При передаче в конвейер без --json --all выводит по одному JSON-объекту в строке.
openemail emails list --status failed,partial --from [email protected] --all | jq -r .idopenemail emails get msg_3f9a1c07d2b84e6a9c5b1f20openemail emails list-events msg_3f9a1c07d2b84e6a9c5b1f20 --all --json | jq -r '.items[] | .createdAt + " " + .type'Прочтите неделю вовлечённости по дням, которые начинаются в полночь по UTC+2, перечислите то, что никто не открыл, и посчитайте клики по каждой ссылке одного письма.
openemail tracking get-stats --days 7 --offset-minutes 120 --json | jq '{ tracked, openRate, clickRate }'openemail tracking list --no-opened --days 7 --all | jq -r .subjectopenemail tracking list-clicks msg_3f9a1c07d2b84e6a9c5b1f20 --all | jq -r .url | sort | uniq -cScope, коды и подтверждения
- Вход через браузер запрашивает scope на странице одобрения, а
openemail login --scopes emails:send,emails:readзаранее отмечает оба. Команда, которой не хватает scope, останавливается с кодом выхода4иinsufficient_scopeи называет этот scope. send --attachс файлами больше 5 МБ сначала загружает их в «Файлы», для чего нужен ещё иfiles:write.- Ни одна из этих команд не спрашивает код подтверждения, поэтому вход через браузер выполняет их так же, как API-ключ.
emails cancelспрашивает перед отменой, а--yesотвечает за вас. Без присмотра и без--yesона останавливается сRefusing to run unattended. Pass --yes to confirm.и кодом выхода2.emails send,send-batchиrescheduleникогда не спрашивают.sendпоказывает сводку и спрашивает только в терминале, а--yesпропускает и это.--dry-runвыводит запрос, который отправила бы команда, ничего не отправляет и завершается с кодом0. Дляemails translateэто не тратит ИИ-действие, аemails cancelпри этом ничего не спрашивает.
Страницы результатов
emails list, emails list-events, tracking list, list-opens и list-clicks читают одну страницу. --limit задаёт её размер: от 1 до 100, по умолчанию 25, для двух списков emails и от 1 до 200, по умолчанию 50, для трёх списков tracking. --cursor продолжает с курсора, который вывела страница.
--allчитает все страницы и передаёт элементы потоком: таблицей в терминале и по одному JSON-объекту в строке при передаче в конвейер или с--ndjson.--max <n>останавливается после стольких элементов и подразумевает--all.--jsonвыводит один документ{ items, hasMore, nextCursor }, в том числе с--all.- Страницы листаются по курсору, а не по смещению, поэтому почта, отправленная, пока вы листаете, никогда не сдвигает и не повторяет строку.
Что полезно знать
- Каждый запуск создаёт собственный ключ идемпотентности, который покрывает повторы внутри этого запуска. Если запустить отправку дважды, она отправит дважды, если только оба запуска не передают один и тот же
--idempotency-key. Тот же ключ с другим телом отклоняется сidempotency_key_reuseи кодом выхода7. - Отменить или перенести можно только почту в состоянии
queuedиscheduled. Немедленная отправка без окна отмены уходит внутри запроса, так что к тому моменту, когда у вас есть её идентификатор, обычно уже поздно, и вызов завершается сemail_not_cancellableи кодом выхода6. - Отменённое письмо остаётся отменённым. Перенос меняет только время, а длительность отсчитывается с момента, когда сервер получил запрос, поэтому, чтобы изменить текст, отмените письмо и отправьте заново.
- Если перевод невозможно сделать, вся отправка отклоняется, и ничего не уходит без перевода. Пакет с переводом содержит не больше 10 писем с
translate. - Исчерпанный лимит отправок останавливает отправку с
send_quota_exceededдо первого числа месяца, а исчерпанная квота ИИ останавливает перевод сai_quota_exceededдо полуночи UTC, в обоих случаях с кодом выхода8. - Почта, отправленная с ключом
oe_test_, никогда не доставляется. Она получает статусsentсtransport, равнымtest, и никогда не отслеживается. emails get-trackingиtracking getотвечают 404 с кодом выхода5для письма, в котором не было ни пикселя, ни переписанной ссылки, потому что «не отслеживалось» не то же самое, что «не открыто». Отслеживание следует настройке, с которой письмо было отправлено, поэтому, если включить его позже, оно не распространится на прежнюю почту.- Каждое число это нижняя граница. Читатель, чей почтовый клиент блокирует изображения, никогда не засчитывается как открытие, а клик доказывает прочтение надёжнее, чем открытие.
list-opensиlist-clicksотвечают 404 для идентификатораmsg_, по которому ничего не отслеживалось, но принимают идентификаторtmsg_как есть, поэтому неизвестный возвращается пустым списком.- Ключ, ограниченный некоторыми адресами, видит только почту, отправленную с этих адресов, а ключ, которому выдан целый домен, охватывает каждый адрес на нём.
Все флаги
На этой странице названы самые важные флаги. openemail <command> --help перечисляет каждый аргумент и флаг команды с типом, нужным scope, методом и путём, тем, что она возвращает, и примечаниями из справочника API. Добавьте --json, чтобы получить ту же справку одним JSON-документом.
openemail emails --helpopenemail emails send --helpopenemail tracking list-opens --help --json