Перейти к основному содержимому

Provider Adapter: Telegram Messaging (заглушка)

Назначение

Доставка канала Telegram (../domain/channel.md) через Telegram Bot API. Заглушка: реализация отложена — контракт зафиксирован, отправка активируется per-канал флагом. Привязку UserId → telegram chat-id ведёт адаптер (bot deep-link /start); уведомление хранит только RecipientUserId. Provider input (bot-updates) — недоверенный внешний ввод.

Границы Adapter-а

ПолеЗначение
ProviderTelegram Bot API
Сервисnotifications
Capabilitymessaging (telegram)
Adapter classTelegramChannelProvider
Typed clientITelegramBotClient + IMessengerHandleStore (chat-id по UserId)
Внутренняя операцияdispatcher при Channel = Telegram, Status = Pending
Реализациязаглушка (отложено) — до включения канала отправка не выполняется и возвращается код NTF_PROVIDER_NOT_IMPLEMENTED

Контракты Provider-а

ОперацияDirectionInternal modelProvider-specific model
sendMessageoutboundNotificationDispatchRequestTelegramSendMessage (chat_id/text/reply_markup)
bot updates (/start, block)callbackпривязка / отвязка messenger-handleTelegramUpdate (подписанный webhook secret-token)

Модель Internal NotificationDispatchRequest

Дом — ../domain/notification.md. Ключевые: NotificationId (idempotency), RecipientUserId → chat-id из IMessengerHandleStore (RecipientAddress.MessengerHandle при наличии), RenderedTitle+RenderedBodytext (Markdown/HTML по Format), DeepLink → inline-кнопка.

Модель Provider-Specific TelegramSendMessage

ПолеТипОбязательностьProvider formatValidationInternal mapping
chat_idlongДаtelegram chat idпривязка есть (иначе skip)IMessengerHandleStore.Get(RecipientUserId)
textstringДа≤ 4096, Markdown/HTMLне пустоеRenderedTitle+RenderedBody
reply_markupобъект?Нетinline keyboardDeepLink (кнопка)
disable_notificationboolНеттихие часы/LowUrgency/quiet-hours

Безопасность

КонтрольПравило
Authenticationbot-token Telegram
Signature validationwebhook — заголовок X-Telegram-Bot-Api-Secret-Token + allow-list IP
Secretsbot-token/webhook secret-token — secret-store, ротация
PIIтело — Sensitive (маска в логах); chat-id — Sensitive, не логируется
Tenant bindingпривязка chat-id к RecipientUserId; отправка только в привязанный chat

Resilience И Устойчивость

АспектПравило
Timeout5с к Bot API
Retry5xx/429(retry_after)/timeout → перевод в Failed → повтор retry-worker-ом; 403 blocked → unlink handle, не retry
Circuit breakerразмыкание на клиенте провайдера
Idempotencyлокальный dedup по NotificationId (Bot API без client-ref — адаптер хранит отметку)
Rate limit≤ ~30 msg/s глобально + ChannelConfig.RateLimitPerMinute

Обработка Callback

ШагПравило
Verifysecret-token заголовка + IP allow-list
NormalizeTelegramUpdate → привязка handle (/start deep-link с UserId) / отвязка (block)
Validatedeep-link payload = валидный UserId; типизированные ошибки
Dispatchпривязка/отвязка chat-id в IMessengerHandleStore
Storehandle в store (безопасно); статус доставки — на NotificationEntity

Маппинг Ошибок

Provider codeInternal errorПовторяемостьВидно пользователю
5xx / timeoutNTF_PROVIDER_TRANSIENTДаНет
429 (retry_after)NTF_PROVIDER_RATE_LIMITEDДаНет
403 bot blockedNTF_TELEGRAM_BLOCKEDНет (unlink)Нет
нет привязки chat-idNTF_MESSENGER_NOT_LINKEDНет (skip)Нет
не активирован (заглушка)NTF_PROVIDER_NOT_IMPLEMENTEDНетНет

Поток доставки

Happy path и все ветки отказа отправки уведомления Channel = Telegram. Статусы и переходы — по notification.md (INV-NTF-5/7); коды — таблица «Маппинг Ошибок».

  1. Выбор к отправке. Dispatcher берёт свежее Pending, retry-worker — Failed с наступившим NextAttemptAt и Attempts < MaxAttempts; уведомление Channel = Telegram. Канал выключен (ChannelConfig.IsEnabled = false, заглушка, INV-CHN-2) → попытки нет, статус Pending, исход NTF_PROVIDER_NOT_IMPLEMENTED (skip, метрика channel-disabled).
  2. Резолв chat-id. chat-id, привязанный к RecipientUserId (реестр привязок ведёт адаптер; RecipientAddress.MessengerHandle при наличии). Привязки нет → NTF_MESSENGER_NOT_LINKED: канал пропускается (non-retriable); получатель покрыт другими разрешёнными каналами (in-app — всегда).
  3. Сборка сообщения. RenderedTitle+RenderedBodytext (≤ 4096, Markdown/HTML по Format; усечение по MaxBodyLength, если задан, INV-CHN-3), DeepLink → inline-кнопка; тихие часы/Urgency = Lowdisable_notification.
  4. Отправка (синхронно, timeout 5с). Bot API без client-ref — идемпотентность обеспечивает локальный dedup по NotificationId (адаптер хранит отметку): повтор той же отправки не шлёт второе сообщение.
  5. Классификация ответа:
    • успех → Pending/Failed → Sent, ProviderMessageId; вклад в NotificationDispatchedEvent(Sent) (топик pin.notifications.dispatched.v1);
    • 5xx/timeout → NTF_PROVIDER_TRANSIENT (retriable): → Failed, Attempts++, NextAttemptAt = now + backoff (ChannelConfig.RetryBackoff);
    • 429 с retry_afterNTF_PROVIDER_RATE_LIMITED (retriable): дефер не раньше retry_after;
    • 403 blockedNTF_TELEGRAM_BLOCKED (non-retriable): терминальный Failed, unlink chat-id.
  6. Порог повторов. Attempts >= MaxAttempts (INV-NTF-7) → терминальный Failed + NotificationDispatchedEvent(Failed) + алерт; запись выбывает из retry-цикла.

Обратный вызов и согласованность

Bot-updates — асинхронная привязка/отвязка handle, не delivery-receipt:

  • /start c deep-link payload UserId → после проверки secret-token заголовка + IP allow-list и валидности UserId chat-id привязывается; канал становится доставляем.
  • block → отвязка chat-id; отправки до повторного /start завершаются NTF_MESSENGER_NOT_LINKED.
  • Повтор того же update идемпотентен; неизвестный/невалидный UserId игнорируется (лог webhook-received).
  • Окно согласованности: сообщение, ушедшее до обработки block, считается Sent; отвязка влияет только на будущие отправки.

Пред- и постусловия

  • Предусловие: канал Telegram включён; есть уведомление Channel = Telegram в Pending/Failed(due); для доставки — привязан chat-id (/start).
  • Постусловие: уведомление в терминальном Sent (есть ProviderMessageId) либо Failed (терминально после MaxAttempts или 403-unlink); на финальном исходе эмитирован NotificationDispatchedEvent. Без привязки канал пропущен, прочие каналы не затронуты.

Кросс-сервисные ожидания и гарантии

НаправлениеКонтракт
Ожидает от ChannelConfig (channel.md)IsEnabled, ProviderKey, MaxAttempts, RetryBackoff, RateLimitPerMinute, MaxBodyLength
Ожидает от рендера шаблонанепустой RenderedBody (INV-NTF-2); опц. RenderedTitle/DeepLink; Format (Markdown/HTML)
Ожидает от привязкиchat-id по RecipientUserId (deep-link /start); отсутствует → skip
Предоставляет агрегатупереход статуса Sent/Failed, ProviderMessageId, LastError (безопасный код)
Предоставляет наружувклад в NotificationDispatchedEvent (ключ партиционирования NotificationId, ContractVersion = 1)
Репликисвоих реплик нет; недоступность Bot API — transient-retry, не потеря

Acceptance Criteria

#Given / When / Then
AC1Given канал Telegram выключен, When dispatcher берёт Pending, Then попытки нет, статус Pending, метрика channel-disabled (NTF_PROVIDER_NOT_IMPLEMENTED).
AC2Given привязан chat-id и канал включён, When отправка успешна, Then Sent + ProviderMessageId + вклад в NotificationDispatchedEvent(Sent).
AC3Given 5xx/429(retry_after), When обработка ответа, Then Failed, Attempts++, NextAttemptAt по backoff (не раньше retry_after); повтор до успеха/MaxAttempts.
AC4Given MaxAttempts исчерпан, When очередная ошибка, Then терминальный Failed + NotificationDispatchedEvent(Failed) + алерт.
AC5Given 403 blocked, When ответ провайдера, Then терминальный Failed + unlink chat-id (NTF_TELEGRAM_BLOCKED), без повторов.
AC6Given нет привязки chat-id, When отправка, Then канал пропущен (NTF_MESSENGER_NOT_LINKED), in-app доставлен независимо.
AC7Given повтор той же отправки (NotificationId), When локальный dedup, Then у провайдера одно сообщение (без дубля).

Наблюдаемость

ТипПравило
Logssend-started/sent/blocked/not-linked/provider-error/webhook-received (тело/chat-id — маска)
Metricsnotifications_telegram_sent_total, notifications_telegram_error_rate, notifications_telegram_linked_total, circuit-state
TracescorrelationId, NotificationId, provider request id
Redactionbot-token/secret и raw тело/chat-id — исключены

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы