Provider Adapter: Email Messaging (заглушка)
Назначение
Доставка канала Email (../domain/channel.md) через внешнего email-провайдера
(SMTP/ESP). Заглушка: реализация отложена — контракт зафиксирован, отправка активируется per-канал
feature-флагом (ChannelConfig.IsEnabled) по мере интеграции вендора. Provider input (delivery-вебхуки)
считается недоверенным внешним вводом.
Границы Adapter-а
| Поле | Значение |
|---|
| Provider | Email ESP (SMTP / транзакционный ESP — вендор TBD) |
| Сервис | notifications |
| Capability | messaging (email) |
| Adapter class | EmailChannelProvider |
| Typed client | IEmailProviderClient (типизированный, вокруг вендорного SDK/HTTP) |
| Внутренняя операция | вызывается dispatcher-ом при Channel = Email, Status = Pending |
| Реализация | заглушка (отложено) — до включения канала отправка не выполняется и возвращается код NTF_PROVIDER_NOT_IMPLEMENTED |
Контракты Provider-а
| Операция Provider-а | Direction | Internal model | Provider-specific model |
|---|
send | outbound | NotificationDispatchRequest | EmailSendRequest (to/subject/html/text) |
| delivery/bounce/complaint | callback | ProviderDeliveryReceipt | EmailWebhookPayload (подписанный вебхук ESP) |
Модель Internal NotificationDispatchRequest
Общая входная модель адаптеров каналов доставки (дом — ../domain/notification.md).
| Поле | Тип | Обязательность | Описание | Mapping |
|---|
NotificationId | Guid | Да | Экземпляр доставки (идемпотентность) | → EmailSendRequest client-reference |
RecipientAddress.Email | string | Да | Адрес получателя | → to |
RenderedTitle | string? | Нет | Тема | → subject |
RenderedBody | string | Да | Тело (html/text по Format) | → html/text |
Locale | string | Да | Локаль | → заголовки/шаблонная часть |
Модель Provider-Specific EmailSendRequest
| Поле | Тип | Обязательность | Provider format | Validation | Internal mapping |
|---|
to | string | Да | RFC 5322 | валидный email | RecipientAddress.Email |
from | string | Да | verified sender | по ChannelConfig | конфиг |
subject | string | Да | ≤ 256 | — | RenderedTitle |
html/text | string | Да | mime | не пустое | RenderedBody |
clientReference | string | Да | idempotency | = NotificationId | NotificationId |
Предусловия диспатча и статусная семантика
- Когда адаптер вызывается. Только для
NotificationEntity с Channel = Email, Status = Pending и при
ChannelConfig.IsEnabled = true. Пока канал — заглушка/выключен, email не является кандидатом на enqueue
(../domain/notification.md «Резолв каналов»), поэтому Pending по email не
создаётся и адаптер не вызывается.
- Misconfig (включён, но не реализован). Если канал ошибочно включён при заглушке —
send возвращает
NTF_PROVIDER_NOT_IMPLEMENTED (non-retriable) → уведомление переводится в терминальный Failed + алерт
(retry не крутится).
Sent = принято ESP. Успешный send (accepted) переводит уведомление в Sent; для ESP, подтверждающих
доставку только вебхуком, Sent выставляется по delivered-квитанции. Sent — терминальный для email
(INV-NTF-5).
- Поздние
bounce/complaint по уже Sent. Трактуются как delivery-сигнал: пишутся в
LastError/ProviderMessageId (безопасные поля) и в аналитику dispatched; статус Sent в первой версии
не переоткрывается (без отката Sent → Failed).
Failed до accept. Retriable-коды (NTF_PROVIDER_TRANSIENT/NTF_PROVIDER_RATE_LIMITED) →
retry-worker повторяет до ChannelConfig.MaxAttempts (INV-NTF-7), после — терминальный Failed + алерт;
non-retriable (NTF_PROVIDER_INVALID_RECIPIENT/NTF_PROVIDER_NOT_IMPLEMENTED) → терминальный Failed без
повторов.
Безопасность
| Контроль | Правило |
|---|
| Authentication | api-key/OAuth ESP; verified sender-домен (SPF/DKIM/DMARC) |
| Signature validation | delivery-вебхук — HMAC-подпись ESP + timestamp + replay-window (≤ 5 мин) |
| Secrets | api-key/webhook-secret — в secret-store, ротация; не в ChannelConfig |
| PII | email/тело — Sensitive; в логах маска (a***@x.ru), тело не логируется |
| Tenant binding | получатель по RecipientUserId уведомления; sender/домен — платформенный |
Resilience И Устойчивость
| Аспект | Правило |
|---|
| Timeout | HTTP к ESP — 5с |
| Retry | retriable-коды (5xx/429/timeout) → перевод в Failed → повтор retry-worker-ом (backoff по ChannelConfig) |
| Circuit breaker | размыкание на клиенте провайдера (open при серии ошибок ESP) |
| Idempotency | clientReference = NotificationId (ESP-дедуп) |
| Rate limit | локальный throttle ChannelConfig.RateLimitPerMinute под лимит ESP |
Обработка Callback
| Шаг | Правило |
|---|
| Verify | HMAC-подпись вебхука + timestamp + sender-домен |
| Normalize | EmailWebhookPayload → ProviderDeliveryReceipt (delivered/bounced/complained) |
| Validate | clientReference = известный NotificationId; типизированные ошибки |
| Dispatch | применить delivery-receipt: перевод уведомления в Sent/Failed (+ ProviderMessageId) |
| Store | ProviderMessageId/LastError/статус на NotificationEntity (безопасные поля) |
Маппинг Ошибок
| Provider code | Internal error | Повторяемость | Видно пользователю |
|---|
| 5xx / timeout | NTF_PROVIDER_TRANSIENT | Да | Нет |
| 429 rate limit | NTF_PROVIDER_RATE_LIMITED | Да (backoff) | Нет |
| invalid recipient / hard bounce | NTF_PROVIDER_INVALID_RECIPIENT | Нет | Нет |
| не активирован (заглушка) | NTF_PROVIDER_NOT_IMPLEMENTED | Нет | Нет |
Наблюдаемость
| Тип | Правило |
|---|
| Logs | send-started/sent/provider-error/webhook-received/signature-failure (email/тело — маска) |
| Metrics | notifications_email_sent_total, notifications_email_error_rate, notifications_email_retry_total, circuit-state |
| Traces | correlationId, NotificationId, provider request id |
| Redaction | api-key/webhook-secret и raw email/тело — исключены |
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы