4.1.0 (2026-08-20)
New Features
- add typed ban notification templates (30878b1)
- cabinet: удаление конкретной подписки из карточки пользователя (56a9959)
- email: заголовок Reply-To (SMTP_REPLY_TO) (6a26e39)
- email: отписка от маркетинговых писем (one-click, RFC 8058) (4825527)
- landing: засчитывать гостевую покупку в рекламную кампанию (1c46702)
- landing: засчитывать гостевые покупки в рекламные кампании (7203ca9)
- landing: сохранять слаг рекламной кампании в гостевой покупке (4a81705)
- promocodes: трафик в наборе бонусов промокода (93f5bff)
- remnawave: проксировать GeoCheck ноды из панели 3.3.0 в кабинет (134d8e4)
- show payment methods directly on extend insufficient-balance screen (45f81a8)
- skip tariff selection screen when only one tariff is available (5c74099)
- sort user list by soonest subscription expiry (e3713ce)
Bug Fixes
- align BanHammer notification contract (e84a9b0)
- bans: уважать общий выключатель уведомлений в типизированных банах (927790f)
- cabinet: id админа снимаем до цикла массового действия (9fba46f)
- cabinet: не отдавать пустую строку вместо null в /info/service (1707660)
- cabinet: отдавать в /info/service реальные настройки (0ba8fa5)
- cabinet: отдавать в /info/service реальные настройки (3329ec3)
- cabinet: удаление подписки адресует панельный аккаунт по режиму (1feef2c)
- cabinet: удаление подписки в массовых действиях падало MissingGreenlet (c99fd54)
- channel: не слать админам traceback на заблокировавших бота (d91caee)
- email: отписку применяет только POST + границы токена (091e2ee)
- grace: сессия без панельного id чинится при чтении, а не живёт вечно (5116848)
- make BroadcastResponse.message_text Optional[str] (9a23bc3), closes #3151
- migrations: перенумеровать миграцию кампании в 0105 (79b17f0)
- migrations: развести номер миграции гостевых покупок с 0105 (d4f529b)
- notifications: не отправлять отключённые уведомления пользователям (22370a8)
- oauth: per-origin redirect_uri so OAuth login works on mirror domains (1f873f9)
- oauth: привязка провайдера тоже возвращается на домен запроса (d99ab15)
- payments: дренаж фоновой обработки вебхуков при остановке (531060e)
- payments: не терять вебхуки платёжек, выключенных на момент старта (67532db)
- platega: распознавать camelCase-коллбеки рекуррентной СБП-подписки (4123ee8)
- promocodes: код с одним трафиком не сгорает на безлимитной подписке (7254930)
- promocodes: трафик доезжает до строки, а начисление возвращает доступ (0d70155)
- remnawave: не подставлять None вместо класса ошибки при провале импорта (775e507)
- rich: make usernames clickable (42ff65a)
- rich: ссылку на профиль ставим только настоящему Telegram-логину (accda9a)
- tickets: длинные сообщения обрезались и не показывались целиком (064e067)
- tickets: длинные сообщения обрезались и не показывались целиком (3be6da7)
- tickets: экранировать превью ответа в уведомлении пользователю (f898414)
- traffic: не читать даты периода вне ветки, которая их задаёт (993ea75)
Performance
- etoplatezhi: ack webhook instantly, process callback in background (2acea3f)
Refactoring
- campaigns: вынести привязку к кампании в сервис (a813083)