Architecture
Mursil follows Vertical Slice Architecture inside Clean Architecture. Dependencies point inward. Presentation calls Application. Application coordinates Domain. Infrastructure implements what Application defines. Domain references nothing in the outer layers.
Use cases go through MediatR. HTTP endpoints do not contain business rules. Persistence goes through repositories and specifications. Expected business failures use the Result pattern rather than exceptions thrown for validation.
Source layout
src/
├── Host/
│ ├── AppHost/ .NET Aspire: PostgreSQL, Redis, Mailpit, Migrator, Server, Admin
│ └── ServiceDefaults/ health, OpenTelemetry, shared service defaults
└── Mursil/
├── Domain/ aggregates, strongly typed ids, smart enums
├── Application/ handlers, commands, validators
├── Contracts/ DTOs and request types
├── Client/ transactional email client seed
├── Integration/ host package: send, draft template push, redacted status
├── Infrastructure/ EF Core, dispatch, providers
└── Presentation/
├── Mursil.Server/ Minimal API host
├── Mursil.Admin/ Blazor admin portal
└── Mursil.Migrator/ migrations and first-party seeding
Tests live under src/Tests/Mursil/. The solution file is Mursil.slnx.
Dependency flow
Mursil.Server → Mursil.Application → Mursil.Domain
↓
CleanArch Pro Communication module
(low-level dispatch, consumed as NuGet)
The admin portal talks to Server over HTTP through Refit. It does not open the database.
Two delivery layers
| Layer | Owns | Does not own |
|---|---|---|
| Mursil platform | Acceptance, render, idempotency, history, retries, dead-letter, audit, integration events | The provider’s SMTP or HTTP call |
| CleanArch Pro Communication | Encrypted queue persistence, provider adapter, background execution | Template lifecycle and portal policy |
Product send persists the message first. MursilDeliveryDispatchHostedService then sends it and records Sent, retry, or dead-letter. Handlers do not call a provider SDK directly.
Data stores
PostgreSQL is the system of record, accessed through EF Core. Redis is the cache. Local development also runs Mailpit, which captures SMTP and is not an inbox product. The dev issuer, when enabled, mints local JWTs and has no user database.
Runtime processes
| Process | Role |
|---|---|
| Migrator | Applies migrations and first-party seed, then exits |
| Server | HTTP API, preference center, tracking pixels, delivery worker |
| Admin | Operator portal |
The migrator must finish successfully before Server and Admin serve traffic. Published images are three tags on one private Docker Hub repository: server, admin, and migrator. Postgres, Redis, Mailpit, and the dev issuer are not in that repository.