Шаблоны, правила и вебхуки
Каждая команда `templates`, `rules` и `webhooks`: сохранённые тела, которые вы отправляете по slug, правила, которые раскладывают приходящую почту, и подписанные события для вашего сервера.
Три пространства имён
Эти три пространства имён позволяют ящику работать без присмотра. templates хранит тела, которые вы отправляете много раз, rules раскладывает почту по мере поступления, а webhooks сообщает вашему серверу, что произошло. Каждая команда это метод SDK под своим именем в kebab-case, поэтому webhooks.rotateSecret становится openemail webhooks rotate-secret, и она читает аргументы и флаги, как любая другая команда ресурса.
| Пространство имён | Также | Для чтения нужно | Для изменений нужно |
|---|---|---|---|
| templates | template | templates:read | templates:write, а для send ещё и emails:send |
| rules | rule | rules:read, включая test | rules:write |
| webhooks | webhook | webhooks:read | webhooks:write, включая test и replay-delivery |
На этой странице перечислены все команды и то, что стоит знать, прежде чем писать для них скрипт. Чтобы увидеть каждый аргумент и флаг с типом, нужными scope, эндпоинтом и тем, что он возвращает, запустите openemail <namespace> <verb> --help. Добавьте --json, чтобы получить ту же страницу в виде JSON.
openemail templates --helpopenemail rules create --helpopenemail webhooks replay-delivery --help --jsonШаблоны
Тела, сохранённые один раз и отправляемые много раз, с версиями, предпросмотром и типизированными props. Каждая команда, принимающая <id-or-slug>, принимает идентификатор tpl_ или slug. Slug никогда не меняется при переименовании шаблона, поэтому закрепляйте в скриптах slug.
| Команда | Что делает |
|---|---|
| openemail templates list | Показать шаблоны, недавно обновлённые сверху. --status оставляет черновые, активные или архивные, --search ищет по именам, slug и темам, а --sort выбирает порядок |
| openemail templates get <id-or-slug> | Прочитать шаблон с его головной версией целиком, включая тело |
| openemail templates create --name <value> | Создать шаблон и его первую версию. Он остаётся черновиком, если не передан --publish, а --starter берёт за основу стартовый дизайн |
| openemail templates update <id-or-slug> | Изменить имя, slug, описание или статус либо тело черновика. Отправки продолжают использовать опубликованную версию, пока вы не опубликуете |
| openemail templates duplicate <id-or-slug> | Скопировать головную версию в новый шаблон, который начинается как черновик |
| openemail templates replace-content <id-or-slug> | Заменить тело на тело стартового дизайна (--starter) или другого шаблона (--from-template-id). Просит подтверждения |
| openemail templates delete <id-or-slug> | Удалить шаблон и все его версии. Просит подтверждения |
| openemail templates list-versions <id-or-slug> | Показать версии, новые сверху, без их тел |
| openemail templates get-version <id-or-slug> <version> | Прочитать одну версию с её телом, не трогая черновик |
| openemail templates publish <id-or-slug> | Опубликовать черновик, чтобы отправки использовали его. Публикация головной версии, которая уже опубликована, ничего не меняет |
| openemail templates restore-version <id-or-slug> <version> | Вернуть тело более старой версии в черновик. Просит подтверждения |
| openemail templates delete-version <id-or-slug> <version> | Удалить одну версию. Опубликованная версия, головная и единственная версия отклоняются. Просит подтверждения |
| openemail templates list-starters | Показать встроенные стартовые дизайны |
| openemail templates get-starter <slug> | Прочитать один стартовый дизайн целиком, с деревом блоков и отрисованным предпросмотром |
| openemail templates list-fonts | Показать веб-шрифты, которые может загружать шаблон |
| openemail templates render | Отрисовать тело, которое нигде не сохранено, из --html или --document |
| openemail templates preview <id-or-slug> | Отрисовать сохранённый шаблон с --props и --slots, включая черновики, не отправляя его |
| openemail templates get-analytics <id-or-slug> | Отправки, открытия и клики за период, по дням, по источникам и по версиям |
| openemail templates list-sends <id-or-slug> | Отдельные письма, отправленные шаблоном, новые сверху, постранично |
| openemail templates send <id-or-slug> --from <value> --to <a,b> | Отправить письмо, отрисованное из опубликованной версии или из той, которую закрепляет --template-version |
У шаблона есть головная версия, которая остаётся черновиком, пока в ней есть неопубликованные правки, и опубликованная версия, которую использует отправка без --template-version. create без --publish, правка тела через update, replace-content и restore-version пишут в черновик, поэтому получатели не увидят ничего нового до publish.
- Архивный шаблон отказывается отправлять с
template_archived.publishснова делает его активным. - В рабочем пространстве может быть не больше 200 шаблонов, включая архивные, поэтому освободить место можно только удалением.
deleteотклоняется сtemplate_in_use, пока запланированная рассылка или рассылка в очереди всё ещё ссылается на шаблон.
Правила
Условия и действия, которые проверяются на приходящей почте в том порядке, который показывает rules list. Правило действует только на почту, пришедшую, пока оно включено. Ни одна команда не применяет правило к почте, которая уже в ящике, а увидеть, что оно поймало бы, позволяет rules test. Идентификаторы правил начинаются с rul_.
| Команда | Что делает |
|---|---|
| openemail rules list | Показать правила в порядке выполнения. --enabled или --no-enabled оставляет один вид |
| openemail rules get <id> | Прочитать одно правило с matchCount и lastMatchedAt |
| openemail rules create --name <value> --conditions <json|@file|-> --actions <json|@file|-> | Создать правило в конце порядка. Оно включено, если не передан --no-enabled |
| openemail rules update <id> | Изменить правило. --conditions и --actions заменяют весь список, а --position перемещает только это правило |
| openemail rules delete <id> | Удалить правило. То, что оно уже сделало, остаётся в list-runs. Просит подтверждения |
| openemail rules reorder <rule-ids...> | Задать порядок всех правил сразу, назвав каждое правило ровно один раз |
| openemail rules test <id> | Пробно прогнать правило по почте, которая уже в ящике. Ничего не меняет и работает для выключенного правила |
| openemail rules list-runs | Что правила на самом деле сделали с приходящей почтой, новые сверху. --rule-id и --thread-id сужают список |
--conditions это список объектов { field, op, value }, объединённых через --match all или --match any, где value всегда строка, а negate: true обращает одно условие. --actions это список объектов { type, value }, применяемых по порядку. Правило содержит от 1 до 20 условий и от 1 до 10 действий, а в ящике может быть не больше 100 правил.
- Поля условий:
from,from_domain,envelope_from,to,cc,bcc,recipient,reply_to,delivered_to,subject,body,header,list_id,attachment_name,attachment_type,has_attachment,attachment_size,message_size,spam,hourиweekday. - Операторы:
matches,contains,equals,starts_with,ends_with,gtиlt.gtиltработают только с числовыми полями, аhas_attachmentиspamпринимают толькоequalsсtrueилиfalse. - Типы действий:
label,remove_label,archive,mark_read,star,spam,trash,forward,reply,block_senderиreject.labelиremove_labelпринимают идентификатор метки, напримерUSER_RECEIPTS,forwardпринимает адрес, аreplyидентификатор или slug шаблона. from_domainсовпадает и с поддоменами, аhourиweekdayчитаются в UTC, причём воскресенье это0.- Правило с действием
rejectдолжно также проверятьenvelope_from, иначе оно отклоняется сreject_needs_envelope.
Вебхуки
Эндпоинты на вашем сервере, которые получают подписанные события ящика, с их секретами подписи, журналом доставок и журналом аудита каждого изменения. Идентификаторы эндпоинтов начинаются с whe_, а идентификаторы доставок с whd_.
| Команда | Что делает |
|---|---|
| openemail webhooks list | Показать эндпоинты рабочего пространства, новые сверху, с их состоянием |
| openemail webhooks get <id> | Прочитать один эндпоинт. Секрет подписи никогда не входит в чтение |
| openemail webhooks create --url <value> | Зарегистрировать эндпоинт HTTPS. Команда выводит секрет подписи, и это единственный раз, когда вы его видите |
| openemail webhooks update <id> | Изменить URL, события, списки разрешённых или то, включён ли он. Каждый список заменяет сохранённый |
| openemail webhooks delete <id> | Удалить эндпоинт и его журнал доставок. Просит подтверждения |
| openemail webhooks rotate-secret <id> | Выпустить новый секрет подписи. Старый сразу перестаёт работать. Просит подтверждения |
| openemail webhooks test <id> | Отправить подписанное синтетическое событие email.sent и сообщить, как прошла доставка |
| openemail webhooks list-deliveries <id> | Попытки доставки одного эндпоинта, новые сверху. --status, --since и --until сужают список |
| openemail webhooks get-delivery <id> <delivery-id> | Одна попытка целиком: отправленное тело, ответ вашего сервера, каждая попытка события и будет ли принят повтор |
| openemail webhooks replay-delivery <id> <delivery-id> | Снова отправить одно сохранённое событие на эндпоинт, сейчас |
| openemail webhooks list-workspace-deliveries | Попытки доставки по всем эндпоинтам или по тем, что названы в --endpoint-ids |
| openemail webhooks list-activity <id> | Журнал аудита одного эндпоинта: кто создал, изменил, проверил, повторил доставку или удалил его |
| openemail webhooks list-workspace-activity | Журнал аудита всех эндпоинтов, включая удалённые |
Если не указать --event-types, эндпоинт получает набор по умолчанию: события email.*, кроме email.replied. email.replied, события domain.* и события suppression.* доходят до него, только если вы их назовёте. --address-allowlist и --domain-allowlist сужают эндпоинт до некоторых адресов или доменов так же, как сужают API-ключ.
- В рабочем пространстве может быть 10 эндпоинтов, если поддержка не подняла этот предел.
- Эндпоинт, у которого 100 доставок подряд не прошли, сервер выключает, а
webhooks update <id> --enabledвозвращает его. - При входе через браузер прочитать доставку через
get-deliveryможет только владелец рабочего пространства. Любой другой получаетowner_onlyи код выхода4.
Проверить шаблон, затем опубликовать
templates preview отрисовывает ровно то, что дала бы отправка с теми же значениями, включая черновики, и ей нужен только templates:read, так что её может запустить даже ключ только для чтения. Отсутствующее обязательное свойство она сообщает как предупреждение там, где send отказался бы, поэтому валите сборку при любом предупреждении. publish безопасен при каждом развёртывании, потому что публикация уже опубликованной головной версии ничего не меняет.
draft=$(openemail templates get order-shipped --json | jq .latestVersion)openemail templates preview order-shipped --template-version "$draft" \ --props '{"orderId":"AC-4192","customer":"Ada"}' --json | jq -e '.warnings == []'openemail templates publish order-shippedОтправка из шаблона
Закрепите версию, чтобы переработка, опубликованная завтра, не изменила то, что отправляет этот код, и передайте ключ идемпотентности, взятый из того, что вызвало отправку, чтобы повтор после потерянного ответа воспроизвёл первое письмо, а не отправил второе. --dry-run выводит метод, URL, заголовки со скрытыми учётными данными и тело, ничего не отправляет и завершается с кодом 0. Чтобы отправить, запустите снова без --dry-run.
openemail templates send order-shipped \ --from 'Acme <[email protected]>' \ --to [email protected] \ --template-version 5 \ --props '{"orderId":"AC-4192","customer":"Ada"}' \ --idempotency-key order-shipped:AC-4192 \ --dry-runПроверить правило до запуска
Создайте правило выключенным, пробно прогоните его по недавней почте и включите, когда оно ловит то, что вы имели в виду. При входе через браузер rules create и rules update спрашивают код подтверждения, который скрипт ввести не может, поэтому сначала запустите openemail verify. Следующие 60 минут этот профиль выполняет их без вопросов.
[ { "field": "from_domain", "op": "equals", "value": "stripe.com" }, { "field": "has_attachment", "op": "equals", "value": "true" }][ { "type": "label", "value": "USER_RECEIPTS" }, { "type": "archive" }]openemail verifyrule=$(openemail rules create --name 'Stripe receipts' \ --conditions @conditions.json --actions @actions.json --no-enabled --json | jq -r .id)openemail rules test "$rule" --days 30 --limit 100openemail rules update "$rule" --enabledЧитайте предупреждения rules test раньше его совпадений. field_unevaluable значит, что условие читает то, чего в сохранённой почте уже нет, и проверка не смогла его оценить, а forward_unverified значит, что адрес пересылки размещён не здесь. wouldApply перечисляет то, что объявляет правило: пересылка на адрес, который не подтвердился, всё равно не сработает, когда придёт настоящая почта.
Поставить правило первым и понять, почему письмо переместилось
rules reorder принимает каждое правило ящика ровно один раз. Если правило пропущено или названо дважды, вызов отклоняется, и ничего не сдвигается. rules list возвращает идентификаторы в порядке выполнения, поэтому поставьте нужное правило перед остальными.
first=rul_4f1c9a2b7d3e8f6a0b5c1d2eopenemail rules reorder "$first" $(openemail rules list --all --ndjson \ | jq -r --arg first "$first" 'select(.id != $first) | .id')openemail rules list-runs --thread-id CAHk7pQ2x9LmZ4 --json | jq '.items[] | {ruleName, actions, failures}'list-runs это запись того, что произошло на самом деле. Каждая строка это одно правило, совпавшее с одним письмом, с действиями, которые сработали, и в failures с теми, что ящик отклонил, например ответ отправителю, которому уже отвечали в тот день. Каждая строка хранит имя, которое было у правила в тот момент, поэтому --rule-id работает и для правила, которое вы с тех пор удалили.
Зарегистрировать вебхук и убедиться, что он работает
webhooks create показывает секрет подписи один раз, и никакая следующая команда его больше не покажет. С --json он есть в JSON в stdout, а напоминание сохранить его уходит в stderr, так что вывод по-прежнему разбирается. webhooks test отправляет подписанное синтетическое событие email.sent, на что бы ни был подписан эндпоинт, и никакой почты не отправляется.
openemail verifyopenemail webhooks create --url https://hooks.acme.com/openemail \ --event-types email.received,email.bounced,email.complained \ --description 'Support desk sync' --json > endpoint.jsonjq -r .secret endpoint.jsonopenemail webhooks test "$(jq -r .id endpoint.json)" --json | jq .deliveryrm endpoint.jsonПоложите секрет в хранилище секретов, прежде чем удалять файл. test завершается с кодом 0, даже когда ваш сервер не справился, поэтому читайте delivery.status: delivered для ответа 2xx и failed для всего остального, включая перенаправление, поскольку перенаправления никогда не выполняются. responseCode, равный null, значит, что ответ не пришёл вообще.
Найти неудавшиеся доставки и отправить одну снова
После сбоя на вашей стороне перечислите, что не прошло, по всем эндпоинтам, проверьте, что повтор будет принят, и отправьте событие снова. Повтор несёт тот же идентификатор события, поэтому получатель, который отбрасывает уже обработанные идентификаторы, воспримет его как знакомое событие.
openemail webhooks list-workspace-deliveries --status failed --since 2026-09-26T00:00:00Z --all --ndjson \ | jq -r '[.endpointId, .id, .eventType, (.responseCode // "no answer")] | @tsv'openemail webhooks get-delivery whe_3f9c2a7b1e4d8f60a5c7b92d whd_8c1e4a7f2b9d3e6a0c5f1b28 --json | jq .replayRefusalopenemail webhooks replay-delivery whe_3f9c2a7b1e4d8f60a5c7b92d whd_8c1e4a7f2b9d3e6a0c5f1b28--sinceи--untilпринимают момент в ISO 8601.- У неудавшейся строки, в
nextAttemptAtкоторой стоит время, ещё впереди автоматический повтор. replayRefusalравенnull, когда повтор уйдёт, а иначе называет причину отказа, напримерwebhook_disabled, пока эндпоинт выключен.- Повторы идут по одному событию. Команды, которая заново отправила бы все неудавшиеся доставки, нет.
Коды подтверждения
При входе через браузер четыре из этих команд, прежде чем что-то изменить, спрашивают код подтверждения, как веб-приложение: rules create, rules update, webhooks create и webhooks update. У API-ключа код не спрашивают никогда. Все остальные команды на этой странице выполняются без кода, включая удаления и webhooks rotate-secret.
- В терминале CLI присылает на почту шестизначный код или, если включён двухфакторный вход, просит код из приложения-аутентификатора, а затем выполняет команду один раз.
- Без присмотра, с
--jsonили--no-input, в CI или без терминала, ввести код некому, поэтому команда останавливается с кодом выхода4и ничего не меняет. Сначала запуститеopenemail verify, и 60 минут этому профилю код не понадобится. --yesподтверждает удаление, но никогда не пропускает код.
Подтверждения и пробные запуски
Семь команд здесь что-то удаляют или перезаписывают, поэтому сначала просят подтверждения: templates delete, templates delete-version, templates replace-content, templates restore-version, rules delete, webhooks delete и webhooks rotate-secret. Без присмотра каждая останавливается с кодом выхода 2, если вы не передадите --yes.
$ openemail webhooks delete whe_3f9c2a7b1e4d8f60a5c7b92d --no-input✗ Refusing to run unattended. Pass --yes to confirm.$ openemail webhooks delete whe_3f9c2a7b1e4d8f60a5c7b92d --yes--dry-run выводит первый запрос, который что-то изменил бы, и завершается с кодом 0, не отправляя его и не прося подтверждения. С --json выводится один документ { dryRun, request }. rules test, templates render и templates preview ничего не меняют, но это запросы POST, поэтому пробный запуск выводит их вместо выполнения.
Постраничный вывод
templates list,templates list-versions,rules list,rules list-runsи каждая командаwebhooks list…читают по одной странице, 25 строк, если--limitне просит до 100. Терминал показывает--cursor, который нужно передать для следующей страницы.--allчитает все страницы,--max <n>останавливается после стольких строк, а--ndjsonвыводит по одному JSON-объекту в строке. С--jsonсписок выводит один документ{ items, hasMore, nextCursor }, в том числе с--all.- Передавайте курсор обратно с теми же фильтрами и сортировкой, с которыми он пришёл. Всё остальное отклоняется как
invalid_cursorс кодом выхода7. templates list-sendsлистает вместо этого по номеру, через--pageи--page-size, сообщаетtotalи не имеет--all. Номера страниц сдвигаются, пока уходит почта, поэтому сужайте период через--daysили--minutes, а не листайте далеко.templates list-startersиtemplates list-fontsвозвращают весь каталог сразу, аrules reorderвозвращает все правила простым списком в новом порядке.- В ящике может быть не больше 100 правил, поэтому
rules list --limit 100всегда возвращает все правила одной страницей.
Флаги, на которые стоит взглянуть ещё раз
--template-versionэто поле телаversion, переименованное, потому что--versionвыводит версию CLI. Аргумент<version>уget-version,restore-versionиdelete-versionэто номер версии, а не идентификаторtplv_.--conditions,--actions,--document,--slots,--propsи другие флаги JSON принимают JSON встроенно, из файла через@pathили из stdin через-.--dataпринимает так же всё тело, а любой флаг, переданный вдобавок, переопределяет свой ключ.--htmlпринимает саму разметку, а не файл, поэтому--html @page.htmlотправляет текст@page.html. Передайте--html "$(cat page.html)"или поместитеhtmlв файл, который отдаёте--data.rules update --conditionsи--actionsзаменяют весь список, как иwebhooks update --event-types,--address-allowlistи--domain-allowlist. Прочитайте текущее значение, измените его и отправьте целиком.- Пустой
--event-typesэто ошибка использования. Чтобы вернуть эндпоинт на набор по умолчанию, отправьте--data '{"eventTypes":[]}', а чтобы остановить его доставки, передайте--no-enabled. --expected-versionуtemplates update,replace-contentиrestore-versionпринимает головную версию, которую вы прочитали. Если с тех пор головную версию сдвинул кто-то другой, команда останавливается с кодом выхода6иversion_conflictи ничего не записывает.rules update <id> --no-enabledвыключает правило и сохраняет его место в порядке, и так правило ставят на паузу, не удаляя его.