| Product Service | Owns one bounded domain decision, transition, or operational capability. In this design it participates in: Resolve recipient identity, consent, quiet hours, and required channels. | Separates a distinct scale, ownership, policy, or failure boundary. It prevents the external caller from also owning the contract and failure behavior of Notification API. | Keep this behavior inside an adjacent service. Prefer that simpler option until independent scale, ownership, policy, or failure isolation is measurable. Here that simpler choice would couple “Provider returns success after worker timeout” to the external caller. | Costs an interface, deployment, telemetry, ownership, and failure mode. Keep it in-process first; extract when independent scale or reliability pays for the boundary. Measure the latency, saturation, lag, or mismatch at the Product Service boundary before extracting or scaling it. |
| Notification API | Owns authentication, validation, quotas, deadlines, and routing. In this design it participates in: Render a versioned template and persist the notification before acknowledgment. | Separates a distinct scale, ownership, policy, or failure boundary. It prevents Product Service from also owning the contract and failure behavior of Preference Service. | Put protocol, authentication, quotas, and business transitions in one process. Acceptable at low scale, but it couples external traffic risk to domain releases. Here that simpler choice would couple “Bulk campaign starves password resets” to Product Service. | Costs another hop, policy distribution, and a high-blast-radius fleet. Introduce it when many clients or services need one protocol and protection boundary. Measure the latency, saturation, lag, or mismatch at the Notification API boundary before extracting or scaling it. |
| Preference Service | Owns one bounded domain decision, transition, or operational capability. In this design it participates in: Route by priority and channel to replayable partitions. | Separates a distinct scale, ownership, policy, or failure boundary. It prevents Notification API from also owning the contract and failure behavior of Template Renderer. | Keep this behavior inside an adjacent service. Prefer that simpler option until independent scale, ownership, policy, or failure isolation is measurable. Here that simpler choice would couple “Preference changes after scheduling” to Notification API. | Costs an interface, deployment, telemetry, ownership, and failure mode. Keep it in-process first; extract when independent scale or reliability pays for the boundary. Measure the latency, saturation, lag, or mismatch at the Preference Service boundary before extracting or scaling it. |
| Template Renderer | Owns one bounded domain decision, transition, or operational capability. In this design it participates in: A channel worker claims a delivery lease, applies provider rate limits, and sends. | Separates a distinct scale, ownership, policy, or failure boundary. It prevents Preference Service from also owning the contract and failure behavior of Schedule Store. | Keep this behavior inside an adjacent service. Prefer that simpler option until independent scale, ownership, policy, or failure isolation is measurable. Here that simpler choice would couple “Poison template repeatedly fails rendering” to Preference Service. | Costs an interface, deployment, telemetry, ownership, and failure mode. Keep it in-process first; extract when independent scale or reliability pays for the boundary. Measure the latency, saturation, lag, or mismatch at the Template Renderer boundary before extracting or scaling it. |
| Schedule Store | Owns authoritative versioned state or immutable evidence. In this design it participates in: Verify provider callbacks, dedupe by provider event ID, and update the ledger. | Defines the source of truth and commit boundary. It prevents Template Renderer from also owning the contract and failure behavior of Priority Router. | Let each service persist its own interpretation. That removes one clear commit boundary and makes retries, support, and reconciliation ambiguous. Here that simpler choice would couple “Provider outage fills retry queues” to Template Renderer. | Costs schema migration, replication, backup, and shard ownership. Split storage only when access patterns, retention, or compliance boundaries materially differ. Measure the latency, saturation, lag, or mismatch at the Schedule Store boundary before extracting or scaling it. |
| Priority Router | Owns one bounded domain decision, transition, or operational capability. In this design it participates in: Resolve recipient identity, consent, quiet hours, and required channels. | Separates a distinct scale, ownership, policy, or failure boundary. It prevents Schedule Store from also owning the contract and failure behavior of Kafka Topics. | Keep this behavior inside an adjacent service. Prefer that simpler option until independent scale, ownership, policy, or failure isolation is measurable. Here that simpler choice would couple “Provider returns success after worker timeout” to Schedule Store. | Costs an interface, deployment, telemetry, ownership, and failure mode. Keep it in-process first; extract when independent scale or reliability pays for the boundary. Measure the latency, saturation, lag, or mismatch at the Priority Router boundary before extracting or scaling it. |
| Kafka Topics | Preserves durable ordering, replay, and consumer checkpoints. In this design it participates in: Render a versioned template and persist the notification before acknowledgment. | Decouples bursts and failure from user acknowledgment. It prevents Priority Router from also owning the contract and failure behavior of Email Worker. | Call every downstream consumer synchronously. That couples user latency and availability to analytics, notification, indexing, and audit dependencies. Here that simpler choice would couple “Bulk campaign starves password resets” to Priority Router. | Costs eventual consistency, schema evolution, replay tooling, idempotent consumers, and lag operations. Earn it when bursts or downstream failure must not block acknowledgment. Measure the latency, saturation, lag, or mismatch at the Kafka Topics boundary before extracting or scaling it. |
| Email Worker | Executes idempotent asynchronous work under lease and retry policy. In this design it participates in: Route by priority and channel to replayable partitions. | Separates a distinct scale, ownership, policy, or failure boundary. It prevents Kafka Topics from also owning the contract and failure behavior of Push Worker. | Perform the work before acknowledging the caller. This gives simpler consistency but unbounded tail latency and fragile retries. Here that simpler choice would couple “Preference changes after scheduling” to Kafka Topics. | Costs leases, checkpoints, retries, DLQs, and lag operations. Separate once execution time or burstiness no longer fits the request deadline. Measure the latency, saturation, lag, or mismatch at the Email Worker boundary before extracting or scaling it. |
| Push Worker | Executes idempotent asynchronous work under lease and retry policy. In this design it participates in: A channel worker claims a delivery lease, applies provider rate limits, and sends. | Separates a distinct scale, ownership, policy, or failure boundary. It prevents Email Worker from also owning the contract and failure behavior of SMS Worker. | Perform the work before acknowledging the caller. This gives simpler consistency but unbounded tail latency and fragile retries. Here that simpler choice would couple “Poison template repeatedly fails rendering” to Email Worker. | Costs leases, checkpoints, retries, DLQs, and lag operations. Separate once execution time or burstiness no longer fits the request deadline. Measure the latency, saturation, lag, or mismatch at the Push Worker boundary before extracting or scaling it. |
| SMS Worker | Executes idempotent asynchronous work under lease and retry policy. In this design it participates in: Verify provider callbacks, dedupe by provider event ID, and update the ledger. | Separates a distinct scale, ownership, policy, or failure boundary. It prevents Push Worker from also owning the contract and failure behavior of Provider Adapters. | Perform the work before acknowledging the caller. This gives simpler consistency but unbounded tail latency and fragile retries. Here that simpler choice would couple “Provider outage fills retry queues” to Push Worker. | Costs leases, checkpoints, retries, DLQs, and lag operations. Separate once execution time or burstiness no longer fits the request deadline. Measure the latency, saturation, lag, or mismatch at the SMS Worker boundary before extracting or scaling it. |
| Provider Adapters | Owns one bounded domain decision, transition, or operational capability. In this design it participates in: Resolve recipient identity, consent, quiet hours, and required channels. | Separates a distinct scale, ownership, policy, or failure boundary. It prevents SMS Worker from also owning the contract and failure behavior of Delivery Ledger. | Keep this behavior inside an adjacent service. Prefer that simpler option until independent scale, ownership, policy, or failure isolation is measurable. Here that simpler choice would couple “Provider returns success after worker timeout” to SMS Worker. | Costs an interface, deployment, telemetry, ownership, and failure mode. Keep it in-process first; extract when independent scale or reliability pays for the boundary. Measure the latency, saturation, lag, or mismatch at the Provider Adapters boundary before extracting or scaling it. |
| Delivery Ledger | Owns authentication, validation, quotas, deadlines, and routing. In this design it participates in: Render a versioned template and persist the notification before acknowledgment. | Defines the source of truth and commit boundary. It prevents Provider Adapters from also owning the contract and failure behavior of DLQ / Replay. | Let each service persist its own interpretation. That removes one clear commit boundary and makes retries, support, and reconciliation ambiguous. Here that simpler choice would couple “Bulk campaign starves password resets” to Provider Adapters. | Costs schema migration, replication, backup, and shard ownership. Split storage only when access patterns, retention, or compliance boundaries materially differ. Measure the latency, saturation, lag, or mismatch at the Delivery Ledger boundary before extracting or scaling it. |
| DLQ / Replay | Owns one bounded domain decision, transition, or operational capability. In this design it participates in: Route by priority and channel to replayable partitions. | Separates a distinct scale, ownership, policy, or failure boundary. It prevents Delivery Ledger from also owning the contract and failure behavior of Webhook Ingest. | Keep this behavior inside an adjacent service. Prefer that simpler option until independent scale, ownership, policy, or failure isolation is measurable. Here that simpler choice would couple “Preference changes after scheduling” to Delivery Ledger. | Costs an interface, deployment, telemetry, ownership, and failure mode. Keep it in-process first; extract when independent scale or reliability pays for the boundary. Measure the latency, saturation, lag, or mismatch at the DLQ / Replay boundary before extracting or scaling it. |
| Webhook Ingest | Owns one bounded domain decision, transition, or operational capability. In this design it participates in: A channel worker claims a delivery lease, applies provider rate limits, and sends. | Separates a distinct scale, ownership, policy, or failure boundary. It prevents DLQ / Replay from also owning the contract and failure behavior of operators and repair tooling. | Keep this behavior inside an adjacent service. Prefer that simpler option until independent scale, ownership, policy, or failure isolation is measurable. Here that simpler choice would couple “Poison template repeatedly fails rendering” to DLQ / Replay. | Costs an interface, deployment, telemetry, ownership, and failure mode. Keep it in-process first; extract when independent scale or reliability pays for the boundary. Measure the latency, saturation, lag, or mismatch at the Webhook Ingest boundary before extracting or scaling it. |