# MessagingService - Implementation Guide for AI Agents ## Overview This guide provides step-by-step instructions for AI agents to continue the implementation of the MessagingService application. The project is a modern .NET 8.0 rewrite of a legacy VB.NET email automation system. --- ## Current Status (2026-07-07) βœ… **COMPLETED**: - Domain Layer (100%) - All entities with proper `[Table]` and `[Column]` attributes - Value Objects (MessageId, EmailAddress) - Enums (ErrorCode, ProcessType, etc.) - Domain Services (MessageIdGenerator) - Domain Events (EmailProcessedEvent) - Exceptions (DomainException, ValidationException, AttachmentProcessingException) - agents.md documentation - Project builds successfully 🚧 **IN PROGRESS**: - Application Layer (5% - only DTOs created) ❌ **PENDING**: - Application Layer (95%) - Infrastructure Layer (0%) - API Layer (minimal structure only) - Testing (0%) - README.md documentation (0%) --- ## Architecture Overview ``` DigitalData.MessagingService/ β”œβ”€β”€ src/ β”‚ β”œβ”€β”€ Domain/ βœ… COMPLETE β”‚ β”œβ”€β”€ Application/ 🚧 IN PROGRESS (5%) β”‚ β”œβ”€β”€ Infrastructure/ ❌ TODO β”‚ └── API/ ❌ TODO (minimal structure exists) β”œβ”€β”€ tests/ β”‚ └── Tests/ ❌ TODO β”œβ”€β”€ legacy/ πŸ“– Reference only β”œβ”€β”€ agents.md βœ… COMPLETE β”œβ”€β”€ README.md ❌ TODO └── IMPLEMENTATION_GUIDE.md πŸ“„ This file ``` --- ## Phase-by-Phase Implementation Plan ### PHASE 2: Application Layer (Current Focus) #### 2.1. Create Repository Interfaces **Location**: `src/DigitalData.MessagingService.Application/Interfaces/Repositories/` Create these files: **IEmailProfileRepository.cs**: ```csharp using DigitalData.MessagingService.Domain.Entities; namespace DigitalData.MessagingService.Application.Interfaces.Repositories; public interface IEmailProfileRepository { Task GetByIdAsync(int id, CancellationToken cancellationToken = default); Task> GetAllAsync(CancellationToken cancellationToken = default); Task> GetActiveProfilesAsync(CancellationToken cancellationToken = default); Task> GetProfilesDueForPollingAsync(CancellationToken cancellationToken = default); Task AddAsync(EmailProfile profile, CancellationToken cancellationToken = default); Task UpdateAsync(EmailProfile profile, CancellationToken cancellationToken = default); Task DeleteAsync(int id, CancellationToken cancellationToken = default); } ``` **IEmailAccountRepository.cs**: ```csharp using DigitalData.MessagingService.Domain.Entities; namespace DigitalData.MessagingService.Application.Interfaces.Repositories; public interface IEmailAccountRepository { Task GetByIdAsync(int id, CancellationToken cancellationToken = default); Task> GetAllAsync(CancellationToken cancellationToken = default); Task> GetActiveAccountsAsync(CancellationToken cancellationToken = default); Task AddAsync(EmailAccount account, CancellationToken cancellationToken = default); Task UpdateAsync(EmailAccount account, CancellationToken cancellationToken = default); Task DeleteAsync(int id, CancellationToken cancellationToken = default); } ``` **IEmailHistoryRepository.cs**: ```csharp using DigitalData.MessagingService.Domain.Entities; namespace DigitalData.MessagingService.Application.Interfaces.Repositories; public interface IEmailHistoryRepository { Task GetByIdAsync(int id, CancellationToken cancellationToken = default); Task GetByMessageIdHashAsync(string hash, CancellationToken cancellationToken = default); Task ExistsAsync(string messageIdHash, CancellationToken cancellationToken = default); Task> GetByProfileIdAsync(int profileId, DateTime? from, DateTime? to, CancellationToken cancellationToken = default); Task AddAsync(EmailHistory history, CancellationToken cancellationToken = default); Task UpdateAsync(EmailHistory history, CancellationToken cancellationToken = default); } ``` **IEmailProcessRepository.cs**, **IEmailOutboxRepository.cs** - Similar patterns. #### 2.2. Create Service Interfaces **Location**: `src/DigitalData.MessagingService.Application/Interfaces/Services/` **IEmailService.cs**: ```csharp namespace DigitalData.MessagingService.Application.Interfaces.Services; public interface IEmailService { Task> FetchUnreadEmailsAsync( EmailAccount account, CancellationToken cancellationToken = default); Task TestConnectionAsync( EmailAccount account, CancellationToken cancellationToken = default); Task SendEmailAsync( EmailAccount account, string recipient, string subject, string body, bool isHtml = true, CancellationToken cancellationToken = default); Task DeleteEmailAsync(EmailAccount account, int imapUid, CancellationToken cancellationToken = default); Task MoveEmailAsync(EmailAccount account, int imapUid, string folderName, CancellationToken cancellationToken = default); } public class EmailMessage { public int ImapUid { get; set; } public string MessageId { get; set; } = string.Empty; public string From { get; set; } = string.Empty; public string Subject { get; set; } = string.Empty; public DateTime Date { get; set; } public string BodyHtml { get; set; } = string.Empty; public string BodyText { get; set; } = string.Empty; public List Attachments { get; set; } = new(); public byte[] RawEmailData { get; set; } = Array.Empty(); } public class EmailAttachmentData { public string FileName { get; set; } = string.Empty; public string ContentType { get; set; } = string.Empty; public byte[] Data { get; set; } = Array.Empty(); } ``` **IPdfProcessingService.cs**, **IDmsService.cs**, **IEncryptionService.cs**, **IEmailQueue.cs** - See agents.md for examples. #### 2.3. Create MediatR Commands **Location**: `src/DigitalData.MessagingService.Application/EmailProfiles/Commands/` **CreateEmailProfileCommand.cs**: ```csharp using MediatR; namespace DigitalData.MessagingService.Application.EmailProfiles.Commands; public record CreateEmailProfileCommand( string ProfileName, int EmailAccountId, int? ProcessId, int PollIntervalMinutes) : IRequest; public class CreateEmailProfileCommandHandler : IRequestHandler { private readonly IEmailProfileRepository _repository; public CreateEmailProfileCommandHandler(IEmailProfileRepository repository) { _repository = repository; } public async Task Handle(CreateEmailProfileCommand request, CancellationToken cancellationToken) { var profile = new EmailProfile { ProfileName = request.ProfileName, EmailAccountId = request.EmailAccountId, ProcessId = request.ProcessId, PollIntervalMinutes = request.PollIntervalMinutes, IsActive = true, AddedWhen = DateTime.UtcNow }; return await _repository.AddAsync(profile, cancellationToken); } } ``` **UpdateEmailProfileCommand.cs**, **DeleteEmailProfileCommand.cs**, **ActivateProfileCommand.cs** - Similar patterns. #### 2.4. Create MediatR Queries **Location**: `src/DigitalData.MessagingService.Application/EmailProfiles/Queries/` **GetEmailProfilesQuery.cs**: ```csharp using MediatR; using AutoMapper; using DigitalData.MessagingService.Application.Common.Dtos; namespace DigitalData.MessagingService.Application.EmailProfiles.Queries; public record GetEmailProfilesQuery : IRequest>; public class GetEmailProfilesQueryHandler : IRequestHandler> { private readonly IEmailProfileRepository _repository; private readonly IMapper _mapper; public GetEmailProfilesQueryHandler(IEmailProfileRepository repository, IMapper mapper) { _repository = repository; _mapper = mapper; } public async Task> Handle(GetEmailProfilesQuery request, CancellationToken cancellationToken) { var profiles = await _repository.GetAllAsync(cancellationToken); return _mapper.Map>(profiles); } } ``` **GetEmailProfileByIdQuery.cs**, **GetActiveProfilesQuery.cs**, **GetProfilesDueForPollingQuery.cs** - Similar patterns. #### 2.5. Create Validators **Location**: `src/DigitalData.MessagingService.Application/EmailProfiles/Validators/` **CreateEmailProfileCommandValidator.cs**: ```csharp using FluentValidation; using DigitalData.MessagingService.Application.EmailProfiles.Commands; namespace DigitalData.MessagingService.Application.EmailProfiles.Validators; public class CreateEmailProfileCommandValidator : AbstractValidator { public CreateEmailProfileCommandValidator() { RuleFor(x => x.ProfileName) .NotEmpty().WithMessage("Profile name is required") .MaximumLength(100).WithMessage("Profile name must not exceed 100 characters"); RuleFor(x => x.EmailAccountId) .GreaterThan(0).WithMessage("Email account ID must be greater than 0"); RuleFor(x => x.PollIntervalMinutes) .GreaterThan(0).WithMessage("Poll interval must be greater than 0") .LessThanOrEqualTo(1440).WithMessage("Poll interval must not exceed 1440 minutes (24 hours)"); } } ``` #### 2.6. Create AutoMapper Profiles **Location**: `src/DigitalData.MessagingService.Application/Common/Mappings/` **MappingProfile.cs**: ```csharp using AutoMapper; using DigitalData.MessagingService.Domain.Entities; using DigitalData.MessagingService.Application.Common.Dtos; namespace DigitalData.MessagingService.Application.Common.Mappings; public class MappingProfile : Profile { public MappingProfile() { // EmailProfile mappings CreateMap() .ForMember(d => d.EmailAccountName, opt => opt.MapFrom(s => s.EmailAccount != null ? s.EmailAccount.AccountName : null)) .ForMember(d => d.ProcessName, opt => opt.MapFrom(s => s.EmailProcess != null ? s.EmailProcess.ProcessName : null)); // EmailAccount mappings CreateMap(); // EmailHistory mappings CreateMap() .ForMember(d => d.ProfileName, opt => opt.MapFrom(s => s.Profile != null ? s.Profile.ProfileName : null)) .ForMember(d => d.Attachments, opt => opt.MapFrom(s => s.Attachments)); // EmailAttachment mappings CreateMap(); } } ``` #### 2.7. Create DependencyInjection.cs **Location**: `src/DigitalData.MessagingService.Application/DependencyInjection.cs` ```csharp using Microsoft.Extensions.DependencyInjection; using FluentValidation; using System.Reflection; namespace DigitalData.MessagingService.Application; public static class DependencyInjection { public static IServiceCollection AddApplication(this IServiceCollection services) { var assembly = Assembly.GetExecutingAssembly(); // MediatR services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(assembly)); // AutoMapper services.AddAutoMapper(assembly); // FluentValidation services.AddValidatorsFromAssembly(assembly); return services; } } ``` --- ### PHASE 3: Infrastructure Layer #### 3.1. Add NuGet Packages ```bash cd src/DigitalData.MessagingService.Infrastructure dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.EntityFrameworkCore.Tools dotnet add package MailKit dotnet add package MimeKit dotnet add package PdfSharp dotnet add package Microsoft.Identity.Client dotnet add package Microsoft.AspNetCore.DataProtection ``` #### 3.2. Create DbContext **Location**: `src/DigitalData.MessagingService.Infrastructure/Persistence/MessagingServiceDbContext.cs` ```csharp using Microsoft.EntityFrameworkCore; using DigitalData.MessagingService.Domain.Entities; using System.Reflection; namespace DigitalData.MessagingService.Infrastructure.Persistence; public class MessagingServiceDbContext : DbContext { public MessagingServiceDbContext(DbContextOptions options) : base(options) { } public DbSet EmailAccounts { get; set; } public DbSet EmailProfiles { get; set; } public DbSet EmailProcesses { get; set; } public DbSet ProcessSteps { get; set; } public DbSet IndexingSteps { get; set; } public DbSet EmailHistories { get; set; } public DbSet EmailAttachments { get; set; } public DbSet EmailOutbox { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // Apply configurations from assembly (if you create IEntityTypeConfiguration classes) // modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); // Note: All entity configurations are already done via attributes in Domain entities // This is important - DO NOT modify database schema here! } public override Task SaveChangesAsync(CancellationToken cancellationToken = default) { // Auto-populate audit fields var entries = ChangeTracker.Entries(); foreach (var entry in entries) { if (entry.State == EntityState.Added) { entry.Entity.CreatedDate = DateTime.UtcNow; entry.Entity.CreatedBy = "System"; // TODO: Get from current user context } if (entry.State == EntityState.Modified) { entry.Entity.ModifiedDate = DateTime.UtcNow; entry.Entity.ModifiedBy = "System"; // TODO: Get from current user context } } return base.SaveChangesAsync(cancellationToken); } } ``` #### 3.3. Create Repositories **Location**: `src/DigitalData.MessagingService.Infrastructure/Persistence/Repositories/` **EmailProfileRepository.cs**: ```csharp using Microsoft.EntityFrameworkCore; using DigitalData.MessagingService.Domain.Entities; using DigitalData.MessagingService.Application.Interfaces.Repositories; namespace DigitalData.MessagingService.Infrastructure.Persistence.Repositories; public class EmailProfileRepository : IEmailProfileRepository { private readonly MessagingServiceDbContext _context; public EmailProfileRepository(MessagingServiceDbContext context) { _context = context; } public async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) { return await _context.EmailProfiles .Include(p => p.EmailAccount) .Include(p => p.EmailProcess) .FirstOrDefaultAsync(p => p.Id == id, cancellationToken); } public async Task> GetAllAsync(CancellationToken cancellationToken = default) { return await _context.EmailProfiles .Include(p => p.EmailAccount) .Include(p => p.EmailProcess) .OrderBy(p => p.Sequence) .ToListAsync(cancellationToken); } public async Task> GetActiveProfilesAsync(CancellationToken cancellationToken = default) { return await _context.EmailProfiles .Include(p => p.EmailAccount) .Include(p => p.EmailProcess) .Where(p => p.IsActive && p.EmailAccount!.IsActive) .OrderBy(p => p.Sequence) .ToListAsync(cancellationToken); } public async Task> GetProfilesDueForPollingAsync(CancellationToken cancellationToken = default) { var now = DateTime.UtcNow; return await _context.EmailProfiles .Include(p => p.EmailAccount) .Include(p => p.EmailProcess) .Where(p => p.IsActive && p.EmailAccount!.IsActive && (!p.LastPollTime.HasValue || EF.Functions.DateDiffMinute(p.LastPollTime.Value, now) >= p.PollIntervalMinutes)) .OrderBy(p => p.Sequence) .ToListAsync(cancellationToken); } public async Task AddAsync(EmailProfile profile, CancellationToken cancellationToken = default) { _context.EmailProfiles.Add(profile); await _context.SaveChangesAsync(cancellationToken); return profile.Id; } public async Task UpdateAsync(EmailProfile profile, CancellationToken cancellationToken = default) { _context.EmailProfiles.Update(profile); await _context.SaveChangesAsync(cancellationToken); } public async Task DeleteAsync(int id, CancellationToken cancellationToken = default) { var profile = await GetByIdAsync(id, cancellationToken); if (profile != null) { _context.EmailProfiles.Remove(profile); await _context.SaveChangesAsync(cancellationToken); } } } ``` Create similar repositories for **EmailAccountRepository**, **EmailHistoryRepository**, etc. #### 3.4. Create External Services **MailKitEmailService.cs**, **PdfSharpProcessingService.cs**, **WindreamDmsService.cs**, **EncryptionService.cs**, **InMemoryEmailQueue.cs** (See agents.md for examples - these are complex services) #### 3.5. Create DependencyInjection.cs **Location**: `src/DigitalData.MessagingService.Infrastructure/DependencyInjection.cs` ```csharp using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; using Microsoft.EntityFrameworkCore; using DigitalData.MessagingService.Infrastructure.Persistence; using DigitalData.MessagingService.Application.Interfaces.Repositories; using DigitalData.MessagingService.Infrastructure.Persistence.Repositories; namespace DigitalData.MessagingService.Infrastructure; public static class DependencyInjection { public static IServiceCollection AddInfrastructure( this IServiceCollection services, IConfiguration configuration) { // DbContext services.AddDbContext(options => options.UseSqlServer( configuration.GetConnectionString("DefaultConnection"), sqlOptions => { sqlOptions.EnableRetryOnFailure( maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); sqlOptions.CommandTimeout(60); })); // Repositories services.AddScoped(); services.AddScoped(); services.AddScoped(); // ... add other repositories // External Services // services.AddScoped(); // services.AddScoped(); // services.AddScoped(); // services.AddScoped(); // services.AddSingleton(); return services; } } ``` --- ### PHASE 4: API Layer #### 4.1. Add NuGet Packages ```bash cd src/DigitalData.MessagingService.API dotnet add package Serilog.AspNetCore dotnet add package Serilog.Sinks.File dotnet add package Serilog.Sinks.MSSqlServer dotnet add package Scalar.AspNetCore ``` #### 4.2. Update Program.cs **Location**: `src/DigitalData.MessagingService.API/Program.cs` ```csharp using DigitalData.MessagingService.API; using DigitalData.MessagingService.Application; using DigitalData.MessagingService.Infrastructure; using Serilog; using Scalar.AspNetCore; var builder = WebApplication.CreateBuilder(args); // Configure Serilog Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(builder.Configuration) .Enrich.FromLogContext() .WriteTo.Console() .WriteTo.File("logs/emailprofiler-.log", rollingInterval: RollingInterval.Day) .CreateLogger(); builder.Host.UseSerilog(); // Check for Windows Service mode if (builder.Configuration["Hosting:Mode"] == "WindowsService") { builder.Host.UseWindowsService(); } // Add services builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); // Add Application and Infrastructure layers builder.Services.AddApplication(); builder.Services.AddInfrastructure(builder.Configuration); // Add Background Workers // builder.Services.AddHostedService(); // builder.Services.AddHostedService(); var app = builder.Build(); // Configure the HTTP request pipeline if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); // Add Scalar app.MapScalarApiReference(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); try { Log.Information("Starting MessagingService API"); app.Run(); } catch (Exception ex) { Log.Fatal(ex, "Application start-up failed"); } finally { Log.CloseAndFlush(); } ``` #### 4.3. Create Controllers **Location**: `src/DigitalData.MessagingService.API/Controllers/` **EmailProfilesController.cs**: ```csharp using Microsoft.AspNetCore.Mvc; using MediatR; using DigitalData.MessagingService.Application.EmailProfiles.Commands; using DigitalData.MessagingService.Application.EmailProfiles.Queries; namespace DigitalData.MessagingService.API.Controllers; [ApiController] [Route("api/[controller]")] public class EmailProfilesController : ControllerBase { private readonly IMediator _mediator; private readonly ILogger _logger; public EmailProfilesController(IMediator mediator, ILogger logger) { _mediator = mediator; _logger = logger; } [HttpGet] public async Task GetAll(CancellationToken cancellationToken) { var query = new GetEmailProfilesQuery(); var result = await _mediator.Send(query, cancellationToken); return Ok(result); } [HttpGet("{id}")] public async Task GetById(int id, CancellationToken cancellationToken) { var query = new GetEmailProfileByIdQuery(id); var result = await _mediator.Send(query, cancellationToken); if (result == null) return NotFound(); return Ok(result); } [HttpPost] public async Task Create(CreateEmailProfileCommand command, CancellationToken cancellationToken) { var id = await _mediator.Send(command, cancellationToken); return CreatedAtAction(nameof(GetById), new { id }, id); } // Add Update, Delete, Activate, Deactivate endpoints } ``` Create similar controllers for **EmailAccountsController**, **EmailHistoryController**, **DashboardController**. #### 4.4. Create Background Workers **Location**: `src/DigitalData.MessagingService.API/Workers/` **EmailPollingWorker.cs** and **EmailSenderWorker.cs** (See agents.md for implementation examples) #### 4.5. Update appsettings.json **Location**: `src/DigitalData.MessagingService.API/appsettings.json` ```json { "ConnectionStrings": { "DefaultConnection": "Server=(local);Database=DD_ECM;Integrated Security=true;TrustServerCertificate=true" }, "Serilog": { "MinimumLevel": { "Default": "Information", "Override": { "Microsoft": "Warning", "System": "Warning" } } }, "Workers": { "EmailPolling": { "Enabled": true, "IntervalSeconds": 60 }, "EmailSender": { "Enabled": true, "IntervalSeconds": 5 } }, "Hosting": { "Mode": "IIS" } } ``` --- ### PHASE 5: Testing #### 5.1. Add NuGet Packages ```bash cd tests/DigitalData.MessagingService.Tests dotnet add package FakeItEasy dotnet add package Bogus dotnet add package FluentAssertions dotnet add package Microsoft.AspNetCore.Mvc.Testing dotnet add package Testcontainers.MsSql ``` #### 5.2. Create Unit Tests **Location**: `tests/DigitalData.MessagingService.Tests/Unit/Domain/` **MessageIdGeneratorTests.cs**: ```csharp using Xunit; using FluentAssertions; using DigitalData.MessagingService.Domain.Services; namespace DigitalData.MessagingService.Tests.Unit.Domain; public class MessageIdGeneratorTests { [Fact] public void Generate_ShouldCreateValidMessageId() { // Arrange var generator = new MessageIdGenerator(); var original = "test-msg-123"; var sender = "sender@example.com"; var date = new DateTime(2026, 1, 1, 12, 0, 0); var subject = "Test Subject"; // Act var messageId = generator.Generate(original, sender, date, subject); // Assert messageId.Should().NotBeNull(); messageId.Hash.Should().NotBeNullOrEmpty(); messageId.Value.Should().Contain(original); messageId.Value.Should().Contain(sender); } [Fact] public void Generate_SameInput_ShouldProduceSameHash() { // Arrange var generator = new MessageIdGenerator(); var original = "test-msg-123"; var sender = "sender@example.com"; var date = new DateTime(2026, 1, 1, 12, 0, 0); var subject = "Test Subject"; // Act var messageId1 = generator.Generate(original, sender, date, subject); var messageId2 = generator.Generate(original, sender, date, subject); // Assert messageId1.Hash.Should().Be(messageId2.Hash); } } ``` #### 5.3. Create Integration Tests Use Testcontainers for database integration tests. --- ### PHASE 6: Documentation #### 6.1. Create README.md (in German) **Location**: `README.md` The README should include (in German): - Application overview - Architecture diagram - API endpoints documentation - Worker processes description - Database tables documentation - Configuration guide (appsettings.json) - Deployment instructions (IIS and Windows Service) - Troubleshooting guide **Template structure**: ```markdown # DigitalData MessagingService ## Übersicht [Application overview in German] ## Architektur [Architecture description] ## API Endpunkte ### Email Profile Management - GET /api/emailprofiles - Alle Profile abrufen - GET /api/emailprofiles/{id} - Profil nach ID abrufen - POST /api/emailprofiles - Neues Profil erstellen - PUT /api/emailprofiles/{id} - Profil aktualisieren - DELETE /api/emailprofiles/{id} - Profil lΓΆschen [... continue for all controllers] ## Background Workers ### EmailPollingWorker Überwacht E-Mail-Konten und verarbeitet eingehende E-Mails. **Konfiguration**: ```json "Workers": { "EmailPolling": { "Enabled": true, "IntervalSeconds": 60 } } ``` [... continue for all workers] ## Datenbank Tabellen ### TBDD_EMAIL_ACCOUNT [Table description] [... continue for all tables] ## Konfiguration [Detailed configuration guide] ## Deployment ### IIS Deployment [Step-by-step guide] ### Windows Service Deployment [Step-by-step guide] ``` --- ## Build and Test Commands ```bash # Build solution dotnet build # Run tests dotnet test # Run API cd src/DigitalData.MessagingService.API dotnet run # Create migration cd src/DigitalData.MessagingService.Infrastructure dotnet ef migrations add InitialCreate --startup-project ../DigitalData.MessagingService.API # Update database dotnet ef database update --startup-project ../DigitalData.MessagingService.API ``` --- ## Important Reminders for AI Agents 1. **NEVER modify database schema** - use `[Table]` and `[Column]` attributes 2. **NEVER commit to git** - wait for user instruction 3. **All code and comments in English** - except README.md (German) 4. **Use Serilog for logging** - structured logging 5. **Worker intervals configurable** - via appsettings.json 6. **Support IIS and Windows Service** - via configuration 7. **Check for database triggers** - add to DbContext if they exist 8. **RabbitMQ is future enhancement** - currently use InMemoryEmailQueue --- ## Next Steps for Continuation 1. Complete Application Layer (Commands, Queries, Validators) 2. Complete Infrastructure Layer (DbContext, Repositories, Services) 3. Complete API Layer (Controllers, Workers, Middleware) 4. Create comprehensive tests 5. Write README.md in German 6. Build and test the complete application --- **Document Version**: 1.0 **Last Updated**: 2026-07-07 **Status**: Phase 1 Complete, Phase 2-6 Pending