# Notification Service Implementation Plan This plan has been already implemented and stays here for historical reasons. It should NOT be threated as source of truth for service functionality. ## Summary This plan builds `Notification Service` as a durable asynchronous orchestration service between domain producers, `Gateway`, `Mail Service`, and `User Service`. The implementation must keep business-audience resolution in the producer, contact enrichment in `Notification Service`, client push delivery in `Gateway`, and email execution in `Mail Service`. ## Global Rules - Keep `Notification Service` orchestration-only. - Preserve direct auth-code email flow from `Auth / Session Service` to `Mail Service`. - Use one dedicated Redis Stream for normalized notification intents. - Keep route retries independent per channel. - Do not make notification delivery a correctness dependency for gameplay or geo review state. - Keep user-facing push payloads lightweight. ## ~~Stage 01.~~ Freeze Vocabulary And Cross-Service Ownership Status: implemented. Note: - Later-stage artifacts may already exist in the repository as draft or pre-staged documentation. - Their presence does not mark the corresponding later stages as implemented. Goal: - remove ambiguity before runtime work starts Tasks: - Freeze `notification:intents` as the dedicated ingress stream. - Freeze that producers publish concrete `recipient_user_id` values for user-targeted intents. - Freeze that `Notification Service` resolves user email and locale from `User Service`. - Freeze that admin-only notifications use type-specific configured email lists. - Freeze that `template_id == notification_type`. - Freeze that private-game invites in v1 are user-bound by internal `user_id`. Exit criteria: - `ARCHITECTURE.md`, `TESTING.md`, and service READMEs no longer contradict the agreed notification model ## ~~Stage 02.~~ Define The Intent Contract Status: implemented. Goal: - publish one stable producer-to-notification contract Tasks: - Add `notification/api/intents-asyncapi.yaml`. - Freeze envelope fields: - `notification_type` - `producer` - `audience_kind` - `recipient_user_ids_json` - `idempotency_key` - `occurred_at_ms` - `request_id` - `trace_id` - `payload_json` - Freeze duplicate and conflict rules on `(producer, idempotency_key)`. - Freeze `audience_kind=user|admin_email`. Exit criteria: - every producer can publish normalized intents without service-specific side agreements ## ~~Stage 03.~~ Freeze The Notification Catalog Status: implemented. Goal: - turn product decisions into one exact type catalog Tasks: - Freeze v1 types and channel matrix. - Freeze which types are user-targeted versus admin-only. - Freeze that `lobby.application.submitted` is user-targeted for private games and admin-email-only for public games. - Freeze that `lobby.invite.revoked` produces no notification. - Freeze payload requirements per type. Exit criteria: - no notification type remains partially specified ## ~~Stage 04.~~ Define Push Payload Schemas Status: implemented. Goal: - freeze lightweight client-facing payloads Tasks: - Add `pkg/schema/fbs/notification.fbs`. - Define one table per user-facing push type. - Generate Go bindings under `pkg/schema/fbs/notification`. - Document the mapping from `notification_type` to FlatBuffers table. Exit criteria: - `Gateway` and future client code have one stable schema file for user-facing notification payloads ## ~~Stage 05.~~ Freeze Mail Template Contracts Status: implemented. Goal: - make email handoff deterministic Tasks: - Freeze `payload_mode=template` for notification-generated email. - Add initial `en` templates for all supported email types in `mail/templates//en`. - Update `mail` documentation so notification template IDs align with `notification_type`. - Keep `Auth / Session Service` auth-code mail unchanged. Exit criteria: - every supported email notification type has a documented template directory ## ~~Stage 06.~~ Define Redis State And Retry Model Status: implemented. Goal: - freeze durable service-local storage before runtime code Tasks: - Define `notification_record`, `notification_route`, `notification_idempotency_record`, `notification_dead_letter_entry`, and malformed-intent storage. - Freeze Redis keys and schedule structures. - Freeze route status vocabulary: - `pending` - `published` - `failed` - `dead_letter` - `skipped` - Freeze retry budgets: - `push=3` - `email=7` Exit criteria: - the runtime can restart without losing accepted-or-retryable work ## ~~Stage 07.~~ Build The Runnable Service Skeleton Status: implemented. Goal: - create the initial process shape Tasks: - Add `cmd/notification`. - Add `internal/app`, `internal/config`, `internal/api`, `internal/service`, and `internal/adapters` packages. - Wire Redis startup checks, graceful shutdown, logger setup, and telemetry. - Do not add an operator REST API in v1. Exit criteria: - the process boots with Redis and configuration validation only ## ~~Stage 08.~~ Implement Intent Acceptance And Idempotency Status: implemented. Goal: - durably accept valid intents and reject invalid or conflicting duplicates Tasks: - Consume `notification:intents`. - Validate the envelope and normalized payload. - Persist idempotency records and accepted notification records. - Record malformed intents separately. - Materialize channel routes according to the type catalog and `audience_kind`. Exit criteria: - valid intents are durable and replay-safe before downstream publication begins ## ~~Stage 09.~~ Implement User Enrichment And Locale Resolution Status: implemented. Goal: - make user-targeted routes self-sufficient for later publication Tasks: - Read users by `user_id` from `User Service`. - Extract `email` and `preferred_language`. - Apply `en` fallback when locale is missing or unsupported. - Keep admin-email routes independent from `User Service`. Exit criteria: - every user-targeted route can be published without additional producer input ## ~~Stage 10.~~ Implement Push Publication Status: implemented. Goal: - hand off user-facing notification events to `Gateway` Tasks: - Encode the correct FlatBuffers table per `notification_type`. - Publish client events into the configured `Gateway` stream with `user_id` targeting only. - Apply independent `push` retry policy and route-level dead-letter handling. Exit criteria: - user-targeted push notifications survive temporary `Gateway` stream failures ## ~~Stage 11.~~ Implement Mail Publication Status: implemented. Goal: - hand off non-auth email notifications to `Mail Service` Tasks: - Build template-mode generic mail commands. - Set `template_id == notification_type`. - Pass through normalized template variables from `payload_json`. - Apply independent `email` retry policy and route-level dead-letter handling. Exit criteria: - user and admin email notifications are durably handed off to `Mail Service` ## ~~Stage 12.~~ Integrate Producers Status: implemented. Note: - Implemented as the shared Go producer contract module `galaxy/notificationintent` because `Game Lobby` and `Geo Profile Service` code modules are not present in this repository yet. Goal: - move upstream services onto the new notification contract Tasks: - `Game Master` publishes: - `game.turn.ready` - `game.finished` - `game.generation_failed` - `Game Lobby` publishes: - `lobby.runtime_paused_after_start` - `lobby.application.submitted` - `lobby.membership.approved` - `lobby.membership.rejected` - `lobby.invite.created` - `lobby.invite.redeemed` - `lobby.invite.expired` - `Geo Profile Service` publishes: - `geo.review_recommended` - Update `Game Lobby` architecture and later implementation plan to use user-bound private invites by `user_id`. Exit criteria: - producers no longer rely on ad hoc notification-side audience inference ## ~~Stage 13.~~ Add Observability And Recovery Coverage Status: implemented. Goal: - make the async runtime supportable in operations Tasks: - Add metrics for intake, duplicates, enrichment, publish attempts, retries, dead letters, and lag. - Add structured logging fields shared across intake and route publishers. - Document manual recovery steps for dead-letter inspection and replay. Exit criteria: - the runtime exposes enough signals to detect stuck, noisy, or broken delivery ## ~~Stage 14.~~ Complete Test Coverage And Documentation Alignment Status: implemented. Goal: - close the loop across service tests, boundary tests, and docs Tasks: - Add service tests for malformed intents, duplicates, locale fallback, retry budgets, and route isolation. - Add inter-service tests with `Gateway`, `Mail Service`, `Game Master`, `Game Lobby`, and `Geo Profile Service`. - Update `TESTING.md`. - Update `ARCHITECTURE.md`, `mail/README.md`, `geoprofile/README.md`, and gateway examples. - Verify docs still state that auth-code mail bypasses `Notification Service`. Exit criteria: - the implementation and the cross-service documentation describe the same contracts ## Final Acceptance Criteria The implementation is complete only when all of the following hold: - valid intents are consumed from `notification:intents` - duplicates are idempotent and conflicting duplicates are rejected - user enrichment resolves email and locale from `User Service` - `push` and `email` routes are persisted and retried independently - route dead letters are isolated per channel and per recipient - `Gateway` fan-out remains user-wide, not session-specific - `Mail Service` receives template-mode commands whose template IDs match notification types - admin notifications remain `email`-only - auth-code email still bypasses `Notification Service`