WhatsApp and email confirmation messaging service built with .NET 10, RabbitMQ, and PostgreSQL.
- .NET 10 SDK or later (required)
- Git (required)
- Docker & Docker Compose (optional, for local application development; integration tests use Testcontainers)
Unit tests (Docker-free, ~5–8 seconds):
dotnet test MessageBridge.UnitTests.slnf -c ReleaseIntegration tests (Testcontainers-backed, ~35–50 seconds, requires Docker):
dotnet test tests/MessageBridge.IntegrationTests/MessageBridge.IntegrationTests.csproj -c ReleaseSee Testing Guide for complete instructions, coverage validation, single-test filters, and CI behavior.
Start optional Docker Compose services for manual application testing (not required for automated tests):
# Start RabbitMQ and PostgreSQL for local development
docker-compose up -d
# Build the solution
dotnet build MessageBridge.slnServices available at:
- RabbitMQ AMQP:
localhost:5672(credentials:guest/guest) - RabbitMQ Management:
http://localhost:15672 - PostgreSQL:
localhost:5432(database:messagebridge_dev, credentials:dev/dev)
Stop services with docker-compose down.
┌─────────────────────────────────────────────────────┐
│ Application Code │
│ └─ IMessageBridgePublisher (DI) │
└─────────────────────────────────────────────────────┘
│
┌──────▼──────┐
│ Publisher │ (DI registration, routing key setup)
└──────┬──────┘
│
┌──────▼──────────────────────────┐
│ Transport Layer │
│ ├─ Direct Mode │
│ │ └─ MassTransit/AMQP │
│ └─ Outbox Mode │
│ ├─ Database Outbox Table │
│ └─ Async Dispatcher │
└──────┬──────────────────────────┘
│
┌──────▼──────────────────────────┐
│ RabbitMQ │
│ ├─ Commands Exchange │
│ ├─ WhatsApp Queue │
│ └─ Email Confirmation Queue │
└──────────────────────────────────┘
│
┌──────▼──────────────────────────┐
│ Worker Service │
│ ├─ Message Handlers │
│ ├─ Health Checks │
│ └─ Observability │
└──────────────────────────────────┘
- Testing Guide — Unit tests, integration tests, Testcontainers, coverage validation, CI behavior
- Local Development — Docker Compose setup for application development, service verification
- Message Contracts — Protobuf definitions, versioning, breaking-change checks
- Publisher Guide — Direct & outbox modes, registration, usage examples
- Sample Client — Complete working example (direct & outbox)
- Operations — Health checks, retries, error queues, idempotency, cleanup
- Deployment — Container configuration, CloudAMQP setup, secrets, production decisions
Publishes directly to RabbitMQ (lowest latency):
services.AddMessageBridgePublisher(opts =>
{
opts.DefaultTenantId = "tenant-1";
opts.ExchangeName = "messagebridge.commands";
opts.WhatsAppRoutingKey = "whatsapp.send";
opts.EmailRoutingKey = "email.confirmation";
});
var publisher = serviceProvider.GetRequiredService<IMessageBridgePublisher>();
await publisher.PublishWhatsAppMessageAsync(new SendWhatsAppMessageRequest
{
TenantId = "tenant-1",
PhoneNumber = "+1234567890",
TemplateId = "welcome",
Body = "Hello!"
});Transactional outbox pattern (guarantees delivery):
services.AddDbContext<AppDbContext>(opts => opts.UseNpgsql(...));
services.AddMessageBridgeOutboxPublisher<AppDbContext>(opts =>
{
opts.BatchSize = 100;
opts.Concurrency = 4;
opts.PollIntervalMilliseconds = 500;
opts.MaxRetryAttempts = 3;
opts.RetryDelayMilliseconds = 50;
opts.RetryBackoffMultiplier = 2.0;
opts.CleanupEnabled = true;
opts.CleanupRetentionHours = 24;
opts.CleanupBatchSize = 500;
});Messages are written to the outbox table atomically with your business transaction, then dispatched asynchronously by a background worker.
- Typed API — request DTOs validated before publish
- Multi-tenant — tenant ID required on every publish call
- Transport-agnostic — direct or outbox modes, all via same interface
- Protobuf contracts — versioned, breaking-change detected at build time
- Built-in retries — exponential backoff with configurable limits
- Idempotent — message IDs and correlation IDs generated automatically
- Observable — structured logging, distributed tracing, health endpoints
The registered WhatsApp and email senders are placeholders. A successful sender result is a simulated acknowledgement only; it does not mean a provider accepted or delivered a message. Logs and metadata mark these results with delivery_status=simulated, and do not include recipient or credential data.
MessageBridge:Providers:WhatsAppProviderName and MessageBridge:Providers:EmailProviderName are reserved diagnostic labels. Loading values for them from Azure Key Vault or another secrets provider does not enable delivery. Real provider adapters and provider API integration are out of scope.
- Never commit credentials to version control (
.gitignoreprotects.envfiles) - Local development uses placeholder credentials (
guest/guestfor RabbitMQ,dev/devfor PostgreSQL) - CloudAMQP credentials → use a secrets provider (e.g. Azure Key Vault, Kubernetes Secrets, environment variables)
- Credential rotation → update secrets in your provider; running instances must restart to pick up new values
- No credential logging — the framework never logs connection strings, tokens, or sensitive payload data
See Deployment for secrets configuration in production.
├── src/
│ ├── MessageBridge.Contracts/ # Protobuf definitions
│ ├── MessageBridge.Domain/ # Business logic (dependency-free)
│ ├── MessageBridge.Application/ # Use cases & handlers
│ ├── MessageBridge.Infrastructure/ # RabbitMQ, PostgreSQL, outbox
│ ├── MessageBridge.Publisher/ # Public NuGet package
│ └── MessageBridge.Worker/ # Hosted Worker Service
├── samples/
│ └── MessageBridge.SampleClient/ # Example usage (direct & outbox)
├── tests/
│ ├── MessageBridge.*.Tests/ # Unit tests per module
│ └── MessageBridge.IntegrationTests/ # End-to-end tests
├── docs/ # Developer & operations guides
├── docker-compose.yml # Local dev services
├── MessageBridge.sln # Solution file
└── README.md # This file
dotnet build MessageBridge.slndotnet test MessageBridge.sln# Unit tests only
dotnet test tests/MessageBridge.Domain.Tests/MessageBridge.Domain.Tests.csproj --filter Category=Unit --logger "console;verbosity=detailed"
# Integration tests only (requires a running Docker daemon; Testcontainers starts its own services)
dotnet test tests/MessageBridge.IntegrationTests/MessageBridge.IntegrationTests.csprojdotnet run --project samples/MessageBridge.SampleClient/MessageBridge.SampleClient.csprojmarkdownlint README.md docs/*.md samples/MessageBridge.SampleClient/README.md
lychee README.md docs/*.md samples/MessageBridge.SampleClient/README.mdUse the markdown and link checks above before publishing doc-only changes.
| Command | Purpose |
|---|---|
docker-compose up -d |
Start local RabbitMQ & PostgreSQL |
docker-compose down |
Stop services |
docker-compose ps |
Check service status & health |
dotnet build MessageBridge.sln |
Compile all projects |
dotnet test MessageBridge.sln |
Run all tests |
dotnet publish src/MessageBridge.Worker -c Release |
Build worker for deployment |
buf lint |
Check protobuf contract syntax |
buf breaking --against '.git#branch=main' |
Detect breaking contract changes |
Build fails with "project not found"
→ Ensure Docker Compose is running: docker-compose ps
RabbitMQ management UI unreachable
→ Check container: docker-compose logs rabbitmq
PostgreSQL connection timeout
→ Wait 10–15 seconds for health check, then retry. Check logs: docker-compose logs postgres
Tests fail with "address already in use"
→ Ports 5432, 5672, 15672 in use. Run lsof -i :5432 (macOS/Linux) or netstat -ano | findstr :5432 (Windows)
- Follow Clean Architecture
- Keep modules focused & dependency-free where possible
- Always write tests before implementation (red → green → refactor)
- Verify contract changes:
buf breaking --against '.git#branch=main'
MessageBridge.Publisher is published to a private NuGet feed. See Deployment for feed configuration.
See LICENSE file.