> ## Documentation Index
> Fetch the complete documentation index at: https://omniflow-vpn.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Тексты сообщений

> Изменение транзакционных уведомлений, которые установка отправляет сама: справочник значений, предпросмотр и тестовая отправка, которая никогда не доходит до клиента

`/admin/settings/notices` меняет то, что установка говорит, когда с подпиской
что-то происходит: она скоро закончится, лимит трафика почти исчерпан,
автосписание не прошло.

Тексты кампаний пишутся в другом месте — в разделе
[Кампании](https://omniflow-vpn.mintlify.site/ru/commerce/campaigns.md), где вы составляете сообщение и выбираете,
кто его получит. Здесь всё иначе. Эти сообщения доходят до каждого клиента, их
запускает событие, а не решение отправить, и это тот голос, который ваши клиенты
слышат от вас чаще всего, — поэтому оставлять его вшитым в код было неправильно.

## Девять уведомлений

| Уведомление | Когда отправляется | Значения |
| - | - | - |
| Об истечении | За 7, 3 и 1 день до конца и в день окончания | `{days}` |
| О трафике | При превышении 80% лимита и повторно при 100% | `{percent}` |
| О продлении | Когда до конца подписки остаётся достаточно мало | `{plan}`, `{days}`, `{until}` |
| Льготный период | Пока доступ сохраняется после окончания | `{until}`, `{graceUntil}` |
| Предложение восстановить | После льготного периода | — |
| Активация завершена | Выдача доступа закончилась, можно подключаться | — |
| Активация задерживается | Выдача доступа затягивается настолько, что нужно объяснение | — |
| Автосписание не прошло | Списание не удалось и будет повторено | — |
| Автопродление остановлено | Попытки списания прекращены | — |

У большинства подставлять нечего. Это не упущение: обычно установка хочет
изменить тон, а не данные.

## Значения именованные, а не позиционные

В переопределении пишется `{days}`, а не `%d`.

Вшитый каталог использует `fmt.Sprintf` — это безопасно для текста, написанного
рядом с кодом, который его отрисовывает, и небезопасно для текста, написанного в
браузере. `%d`, встретивший строку, печатает `%!d(string=Pro)`. Лишний глагол
формата печатает `%!(EXTRA ...)`. Ни то, ни другое не падает — оно доходит до
клиента в сломанном виде, а между тем, как вы написали сообщение, и тем, как он
его получил, его никто не читает.

Поэтому подстановка отклоняется при сохранении, если уведомление не несёт такого
значения. `{name}` в предупреждении об истечении — это ошибка на экране, который
вы сейчас смотрите, а не буквальные фигурные скобки в десяти тысячах сообщений.

## Что отклоняется при сохранении

Всё перечисленное иначе превратилось бы в сообщение, которое падает или
выглядит сломанным у клиента, спустя часы, когда никто не смотрит:

* Значение, которого уведомление не несёт.
* Разметка вне `<b>`, `<i>`, `<u>`, `<s>`, `<code>`, `<pre>` и `<a>` — того
  подмножества, которое принимает HTML-режим Telegram.
* Незакрытый тег или закрывающий тег, который ничего не закрывает.
* Ссылка не на `https`. Цель вида `tg://` или `javascript:` в сообщении, которое
  установка отправляет сама, — это ссылка, которой клиент имеет все основания
  доверять и которую он никак не может проверить.
* Одиночная `<`. Telegram отклоняет сообщение с такой, поэтому «5 \< 10»
  ловится, пока вы ещё на экране. Пишите `&lt;`.
* Пустое тело или длиннее 2000 символов. Telegram принимает 4096; ограничение
  вдвое меньше, потому что к уведомлению добавляется строка с подпиской и
  клавиатура.

Отказ называет проблемную подстановку или тег. «Некорректно» — это не то, с чем
можно что-то сделать, глядя на текстовое поле.

## Предпросмотр и тестовая отправка

**Предпросмотр** подставляет в ваш текст примеры значений — `{plan}` становится
`Pro`, `{days}` становится `5`, — чтобы вы оценивали сообщение, а не шаблон. В
панели он показывается как обычный текст: тело — это разметка Telegram, а это
браузер, и его интерпретация показала бы то, чего не выдаст ни Telegram, ни
клиент.

**Отправить в группу операторов** кладёт одну готовую копию в тему предпросмотра
уведомлений в вашей операторской группе. Именно это показывает настоящий
результат: принимает ли Telegram разметку, как выглядят эмодзи на телефоне, где
проходят переносы строк.

Клиенту это не уходит никогда, и это сделано намеренно. У транзакционного
уведомления есть повод. Сфабриковать предупреждение об истечении на реальной
подписке, чтобы посмотреть, как оно читается, значило бы сообщить человеку, что
его доступ заканчивается, когда это не так.

Тело отрисовывается и сохраняется в момент нажатия кнопки, поэтому группа
получает то, что было в редакторе тогда, — даже если вы продолжили править и
даже если так и не сохранили.

## Переопределение — исключение, а не копия

Уведомление, у которого нет строки для языка, использует стандартный текст.
Ничего не копируется в базу, когда вы впервые открываете экран.

Поэтому **Вернуть стандартный текст** — это удаление, а не запись текущего
стандартного текста. Установка, которая вернулась к стандарту сегодня и
обновилась через месяц, получит улучшенный текст. Если бы возврат сохранял
копию, установка застыла бы на том тексте, который был в день нажатия кнопки.

Два языка независимы. Переписав английское предупреждение об истечении, вы
ничего не решили про русское, и каждый сохраняется отдельным запросом — поэтому
этот экран не может перезаписать перевод, над которым работает кто-то другой.

## Когда вступает в силу

В следующем проходе рассыльщика, в пределах пятнадцати минут.

Тексты читаются один раз в начале прохода и используются для всех сообщений в
нём. Это дешевле, чем запрос на сообщение, и связнее: сотня предупреждений об
истечении, отправленных вместе, говорит одно и то же, даже если вы сохранили
правку в середине.

Если чтение не удалось, рассыльщик оставляет действующие тексты, а не
откатывается к стандартным. Изменить то, что читает каждый клиент, из-за одного
таймаута было бы хуже, чем отстать на один проход.

## Права

Для чтения текстов нужно `settings.read`. Для записи, возврата, предпросмотра и
тестовой отправки — `settings.write`.

Предпросмотр относится к записи, хотя ничего не сохраняет: он прогоняет
отрисовщик по тексту, который прислал вызывающий, а право на чтение не должно
быть способом это сделать.

Каждое сохранение, возврат и тестовая отправка попадают в журнал аудита как
`notice.overridden`, `notice.reverted` и `notice.tested`. Тела сообщения в
записи аудита нет: это до 2000 символов текста, читаемых из той таблицы, куда
его только что записали, а журнал аудита фиксирует, кто что сделал, а не хранит
вторую копию содержимого.

## Справочник

| Маршрут | Метод | Примечания |
| - | - | - |
| `/v1/panel/notices` | `GET` | `settings.read`; все уведомления со стандартными текстами, значениями и переопределениями |
| `/v1/panel/notices/{code}` | `PUT` | `settings.write`; `{locale, body}`, один язык за запрос |
| `/v1/panel/notices/{code}` | `DELETE` | `settings.write`; `?locale=` — возврат нетронутого не является ошибкой |
| `/v1/panel/notices/{code}/preview` | `POST` | `settings.write`; пустое тело показывает стандартный текст |
| `/v1/panel/notices/{code}/test` | `POST` | `settings.write`; отвечает `202`, доставляет в группу операторов |
| `/v1/panel/notices/{code}/tests` | `GET` | `settings.read`; недавние тесты и их результаты |

Отказы — это problem-документы RFC 9457, причина в поле `detail`.

## Смежные страницы

> **[Уведомления](https://omniflow-vpn.mintlify.site/ru/customer/notifications.md)**
>
> Что настраивает клиент и что было доставлено на самом деле.

> **[Кампании](https://omniflow-vpn.mintlify.site/ru/commerce/campaigns.md)**
>
> Маркетинговые тексты, сегменты и отправки — другой род сообщений.

> **[Уведомления операторам](https://omniflow-vpn.mintlify.site/ru/operations/operator-notifications.md)**
>
> Группа, куда приходит тестовая отправка, и все её темы.

> **[Брендирование](https://omniflow-vpn.mintlify.site/ru/operations/branding.md)**
>
> Вторая половина того, как установка себя подаёт.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.