feat(bounces): захват DSN / фидбека о доставке#55
Merged
Conversation
Dependency-free i18n for the dashboard with an en/ru locale and a
language switcher (persisted in localStorage; defaults from navigator).
- i18n/: I18nProvider + useT()/useI18n(); dictionaries composed from
per-namespace `ns/*.ts` modules via import.meta.glob (so adding a
page's translations is a new file — conflict-free parallel work)
- ns/common, ns/nav, ns/login, ns/dashboard (en + ru)
- Layout: translated nav + language selector; Login + Dashboard translated
- t(key) falls back active-locale → English → key, with {var} interpolation
Stage 1 of 3 (framework + core); remaining pages translated next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the dashboard internationalization: every page and shared component now renders through `t()` with an English + Russian locale. 23 new namespace modules under i18n/ns/ (one per page/component), auto-discovered by the glob-composed dictionary — no shared registry. Translation of the remaining pages was fanned out across a team of subagents (one batch each: domains/mailboxes, spam+onboarding, observability/ops, sending/config, and the rest incl. ResourceTable), then validated by the lead: - en/ru key parity verified across all 29 namespaces (~470 keys) - every page wires useT(); protected core i18n files untouched - ui typecheck + build + prettier clean; backend suite unaffected (499) Technical tokens (DTO enum values, API paths, feature-flag keys, protocol names, example placeholders) are intentionally left untranslated. Closes #18 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Capture delivery-status notifications (bounces) and correlate them to
send jobs, surfaced in the dashboard and via webhooks.
- lib/dsn-parser: pure, dependency-free RFC 3464 parser — per-recipient
recipient/action/status/diagnostic (folded lines joined) + the original
Message-ID; classifies 5.x.x=hard / 4.x.x=soft (unit-tested)
- schema: bounce_events (sendJobId FK, recipient, type, classification,
statusCode, diagnostic, originalMessageId) + migration 0017
- SendJobRepository.findByMessageId — correlate a bounce to its send job
- domain/bounces: repository + service (ingest: parse → classify →
correlate → store → dispatch `send.bounced`) + serialize
- routes: POST /admin/api/bounces/ingest (raw text/* / message/rfc822 or
JSON {raw}) + GET /admin/api/bounces; admin-scoped; BounceEventDTO
- UI: Bounces page (en/ru i18n) + nav; MCP: list_bounces / ingest_bounce
- tests: dsn-parser unit + bounces integration (ingest, classify, correlate
by message id, JSON payload, send.bounced event, 422 non-DSN, list)
The ingest endpoint is the capture API; routing bounce mail to it (Postfix
pipe / forwarder) is a deployment step — no in-app mailbox poller here.
Closes #21
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Что это
Захват уведомлений о недоставке (bounce/DSN) и связывание их с задачами отправки, с выводом в дашборд и через вебхуки (закрывает #21). Вместо #16 (отложен — см. комментарий в issue).
Surfaces: REST API + dashboard + MCP.
Как работает
POST /admin/api/bounces/ingestпринимает сырое DSN-письмо (text/*/message/rfc822, либо JSON{ raw }).lib/dsn-parser(RFC 3464, без зависимостей) вытаскивает по каждому получателю recipient/action/status/diagnostic (склеивает свёрнутые строки) + оригинальныйMessage-ID.Message-IDкоррелируется сsend_jobsпо полюmessageId→ вbounce_eventsпишетсяsendJobId.5.x.x→ hard,4.x.x→ soft, иначе unknown.send.bouncedчерез существующий webhook-диспетчер.GET /admin/api/bounces— список.Изменения
bounce_events+ миграция0017;SendJobRepository.findByMessageId.domain/bounces: repository + service (parse→classify→correlate→store→dispatch) + serialize;BounceEventDTO.admin/bounces.ts(ingest + list), admin-scoped.list_bounces/ingest_bounce.send.bounced, 422 на не-DSN, list).Проверки
typecheck·lint·format:check·vitest(509 ✓) ·ui typecheck/build·mcp typecheck/build— всё зелёное.Заметка по интеграции
Ingest-эндпоинт — это API захвата. Как доводить bounce-почту до него — деплой-шаг (Postfix pipe-транспорт / внешний форвардер на
POST /admin/api/bounces/ingest); встроенного поллера почтового ящика в этом PR нет намеренно (он был бы deploy-fragile и завязан на DMS). Синхронные SMTP-фейлы по-прежнему видны в Send log (dead-задачи); этот PR добавляет именно асинхронный DSN-захват.Closes #21
🤖 Generated with Claude Code