Files
DigitalData.MessagingService/IMPLEMENTATION_GUIDE.md
TekH e789afe26a docs: add comprehensive implementation guide for AI agents
Step-by-step guide covering all remaining phases:
- Phase 2: Application Layer (Repositories, Services, Commands, Queries, Validators)
- Phase 3: Infrastructure Layer (DbContext, Repositories, External Services)
- Phase 4: API Layer (Controllers, Workers, Middleware)
- Phase 5: Configuration (appsettings, Serilog, Scalar)
- Phase 6: Testing (Unit tests, Integration tests)
- Phase 7: Documentation (README.md in German)
- Phase 8: Build and deployment

Includes complete code examples, best practices, and verification steps.
Future AI agents can follow this guide to continue development systematically.
2026-07-07 18:59:46 +02:00

29 KiB

EmailProfiler - Implementation Guide for AI Agents

Overview

This guide provides step-by-step instructions for AI agents to continue the implementation of the EmailProfiler 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.EmailProfiler/
├── 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.EmailProfiler.Application/Interfaces/Repositories/

Create these files:

IEmailProfileRepository.cs:

using DigitalData.EmailProfiler.Domain.Entities;

namespace DigitalData.EmailProfiler.Application.Interfaces.Repositories;

public interface IEmailProfileRepository
{
    Task<EmailProfile?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
    Task<List<EmailProfile>> GetAllAsync(CancellationToken cancellationToken = default);
    Task<List<EmailProfile>> GetActiveProfilesAsync(CancellationToken cancellationToken = default);
    Task<List<EmailProfile>> GetProfilesDueForPollingAsync(CancellationToken cancellationToken = default);
    Task<int> AddAsync(EmailProfile profile, CancellationToken cancellationToken = default);
    Task UpdateAsync(EmailProfile profile, CancellationToken cancellationToken = default);
    Task DeleteAsync(int id, CancellationToken cancellationToken = default);
}

IEmailAccountRepository.cs:

using DigitalData.EmailProfiler.Domain.Entities;

namespace DigitalData.EmailProfiler.Application.Interfaces.Repositories;

public interface IEmailAccountRepository
{
    Task<EmailAccount?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
    Task<List<EmailAccount>> GetAllAsync(CancellationToken cancellationToken = default);
    Task<List<EmailAccount>> GetActiveAccountsAsync(CancellationToken cancellationToken = default);
    Task<int> AddAsync(EmailAccount account, CancellationToken cancellationToken = default);
    Task UpdateAsync(EmailAccount account, CancellationToken cancellationToken = default);
    Task DeleteAsync(int id, CancellationToken cancellationToken = default);
}

IEmailHistoryRepository.cs:

using DigitalData.EmailProfiler.Domain.Entities;

namespace DigitalData.EmailProfiler.Application.Interfaces.Repositories;

public interface IEmailHistoryRepository
{
    Task<EmailHistory?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
    Task<EmailHistory?> GetByMessageIdHashAsync(string hash, CancellationToken cancellationToken = default);
    Task<bool> ExistsAsync(string messageIdHash, CancellationToken cancellationToken = default);
    Task<List<EmailHistory>> GetByProfileIdAsync(int profileId, DateTime? from, DateTime? to, CancellationToken cancellationToken = default);
    Task<int> 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.EmailProfiler.Application/Interfaces/Services/

IEmailService.cs:

namespace DigitalData.EmailProfiler.Application.Interfaces.Services;

public interface IEmailService
{
    Task<List<EmailMessage>> FetchUnreadEmailsAsync(
        EmailAccount account, 
        CancellationToken cancellationToken = default);
    
    Task<bool> 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<EmailAttachmentData> Attachments { get; set; } = new();
    public byte[] RawEmailData { get; set; } = Array.Empty<byte>();
}

public class EmailAttachmentData
{
    public string FileName { get; set; } = string.Empty;
    public string ContentType { get; set; } = string.Empty;
    public byte[] Data { get; set; } = Array.Empty<byte>();
}

IPdfProcessingService.cs, IDmsService.cs, IEncryptionService.cs, IEmailQueue.cs - See agents.md for examples.

2.3. Create MediatR Commands

Location: src/DigitalData.EmailProfiler.Application/EmailProfiles/Commands/

CreateEmailProfileCommand.cs:

using MediatR;

namespace DigitalData.EmailProfiler.Application.EmailProfiles.Commands;

public record CreateEmailProfileCommand(
    string ProfileName,
    int EmailAccountId,
    int? ProcessId,
    int PollIntervalMinutes) : IRequest<int>;

public class CreateEmailProfileCommandHandler : IRequestHandler<CreateEmailProfileCommand, int>
{
    private readonly IEmailProfileRepository _repository;
    
    public CreateEmailProfileCommandHandler(IEmailProfileRepository repository)
    {
        _repository = repository;
    }
    
    public async Task<int> 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.EmailProfiler.Application/EmailProfiles/Queries/

GetEmailProfilesQuery.cs:

using MediatR;
using AutoMapper;
using DigitalData.EmailProfiler.Application.Common.Dtos;

namespace DigitalData.EmailProfiler.Application.EmailProfiles.Queries;

public record GetEmailProfilesQuery : IRequest<List<EmailProfileDto>>;

public class GetEmailProfilesQueryHandler : IRequestHandler<GetEmailProfilesQuery, List<EmailProfileDto>>
{
    private readonly IEmailProfileRepository _repository;
    private readonly IMapper _mapper;
    
    public GetEmailProfilesQueryHandler(IEmailProfileRepository repository, IMapper mapper)
    {
        _repository = repository;
        _mapper = mapper;
    }
    
    public async Task<List<EmailProfileDto>> Handle(GetEmailProfilesQuery request, CancellationToken cancellationToken)
    {
        var profiles = await _repository.GetAllAsync(cancellationToken);
        return _mapper.Map<List<EmailProfileDto>>(profiles);
    }
}

GetEmailProfileByIdQuery.cs, GetActiveProfilesQuery.cs, GetProfilesDueForPollingQuery.cs - Similar patterns.

2.5. Create Validators

Location: src/DigitalData.EmailProfiler.Application/EmailProfiles/Validators/

CreateEmailProfileCommandValidator.cs:

using FluentValidation;
using DigitalData.EmailProfiler.Application.EmailProfiles.Commands;

namespace DigitalData.EmailProfiler.Application.EmailProfiles.Validators;

public class CreateEmailProfileCommandValidator : AbstractValidator<CreateEmailProfileCommand>
{
    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.EmailProfiler.Application/Common/Mappings/

MappingProfile.cs:

using AutoMapper;
using DigitalData.EmailProfiler.Domain.Entities;
using DigitalData.EmailProfiler.Application.Common.Dtos;

namespace DigitalData.EmailProfiler.Application.Common.Mappings;

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        // EmailProfile mappings
        CreateMap<EmailProfile, EmailProfileDto>()
            .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<EmailAccount, EmailAccountDto>();
        
        // EmailHistory mappings
        CreateMap<EmailHistory, EmailHistoryDto>()
            .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<EmailAttachment, EmailAttachmentDto>();
    }
}

2.7. Create DependencyInjection.cs

Location: src/DigitalData.EmailProfiler.Application/DependencyInjection.cs

using Microsoft.Extensions.DependencyInjection;
using FluentValidation;
using System.Reflection;

namespace DigitalData.EmailProfiler.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

cd src/DigitalData.EmailProfiler.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.EmailProfiler.Infrastructure/Persistence/EmailProfilerDbContext.cs

using Microsoft.EntityFrameworkCore;
using DigitalData.EmailProfiler.Domain.Entities;
using System.Reflection;

namespace DigitalData.EmailProfiler.Infrastructure.Persistence;

public class EmailProfilerDbContext : DbContext
{
    public EmailProfilerDbContext(DbContextOptions<EmailProfilerDbContext> options) : base(options) { }
    
    public DbSet<EmailAccount> EmailAccounts { get; set; }
    public DbSet<EmailProfile> EmailProfiles { get; set; }
    public DbSet<EmailProcess> EmailProcesses { get; set; }
    public DbSet<ProcessStep> ProcessSteps { get; set; }
    public DbSet<IndexingStep> IndexingSteps { get; set; }
    public DbSet<EmailHistory> EmailHistories { get; set; }
    public DbSet<EmailAttachment> EmailAttachments { get; set; }
    public DbSet<EmailOutbox> 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<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        // Auto-populate audit fields
        var entries = ChangeTracker.Entries<BaseEntity>();
        
        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.EmailProfiler.Infrastructure/Persistence/Repositories/

EmailProfileRepository.cs:

using Microsoft.EntityFrameworkCore;
using DigitalData.EmailProfiler.Domain.Entities;
using DigitalData.EmailProfiler.Application.Interfaces.Repositories;

namespace DigitalData.EmailProfiler.Infrastructure.Persistence.Repositories;

public class EmailProfileRepository : IEmailProfileRepository
{
    private readonly EmailProfilerDbContext _context;
    
    public EmailProfileRepository(EmailProfilerDbContext context)
    {
        _context = context;
    }
    
    public async Task<EmailProfile?> 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<List<EmailProfile>> 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<List<EmailProfile>> 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<List<EmailProfile>> 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<int> 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.EmailProfiler.Infrastructure/DependencyInjection.cs

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.EntityFrameworkCore;
using DigitalData.EmailProfiler.Infrastructure.Persistence;
using DigitalData.EmailProfiler.Application.Interfaces.Repositories;
using DigitalData.EmailProfiler.Infrastructure.Persistence.Repositories;

namespace DigitalData.EmailProfiler.Infrastructure;

public static class DependencyInjection
{
    public static IServiceCollection AddInfrastructure(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        // DbContext
        services.AddDbContext<EmailProfilerDbContext>(options =>
            options.UseSqlServer(
                configuration.GetConnectionString("DefaultConnection"),
                sqlOptions =>
                {
                    sqlOptions.EnableRetryOnFailure(
                        maxRetryCount: 5,
                        maxRetryDelay: TimeSpan.FromSeconds(30),
                        errorNumbersToAdd: null);
                    sqlOptions.CommandTimeout(60);
                }));
        
        // Repositories
        services.AddScoped<IEmailProfileRepository, EmailProfileRepository>();
        services.AddScoped<IEmailAccountRepository, EmailAccountRepository>();
        services.AddScoped<IEmailHistoryRepository, EmailHistoryRepository>();
        // ... add other repositories
        
        // External Services
        // services.AddScoped<IEmailService, MailKitEmailService>();
        // services.AddScoped<IPdfProcessingService, PdfSharpProcessingService>();
        // services.AddScoped<IDmsService, WindreamDmsService>();
        // services.AddScoped<IEncryptionService, DataProtectionEncryptionService>();
        // services.AddSingleton<IEmailQueue, InMemoryEmailQueue>();
        
        return services;
    }
}

PHASE 4: API Layer

4.1. Add NuGet Packages

cd src/DigitalData.EmailProfiler.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.EmailProfiler.API/Program.cs

using DigitalData.EmailProfiler.API;
using DigitalData.EmailProfiler.Application;
using DigitalData.EmailProfiler.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<EmailPollingWorker>();
// builder.Services.AddHostedService<EmailSenderWorker>();

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 EmailProfiler API");
    app.Run();
}
catch (Exception ex)
{
    Log.Fatal(ex, "Application start-up failed");
}
finally
{
    Log.CloseAndFlush();
}

4.3. Create Controllers

Location: src/DigitalData.EmailProfiler.API/Controllers/

EmailProfilesController.cs:

using Microsoft.AspNetCore.Mvc;
using MediatR;
using DigitalData.EmailProfiler.Application.EmailProfiles.Commands;
using DigitalData.EmailProfiler.Application.EmailProfiles.Queries;

namespace DigitalData.EmailProfiler.API.Controllers;

[ApiController]
[Route("api/[controller]")]
public class EmailProfilesController : ControllerBase
{
    private readonly IMediator _mediator;
    private readonly ILogger<EmailProfilesController> _logger;
    
    public EmailProfilesController(IMediator mediator, ILogger<EmailProfilesController> logger)
    {
        _mediator = mediator;
        _logger = logger;
    }
    
    [HttpGet]
    public async Task<IActionResult> GetAll(CancellationToken cancellationToken)
    {
        var query = new GetEmailProfilesQuery();
        var result = await _mediator.Send(query, cancellationToken);
        return Ok(result);
    }
    
    [HttpGet("{id}")]
    public async Task<IActionResult> 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<IActionResult> 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.EmailProfiler.API/Workers/

EmailPollingWorker.cs and EmailSenderWorker.cs (See agents.md for implementation examples)

4.5. Update appsettings.json

Location: src/DigitalData.EmailProfiler.API/appsettings.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

cd tests/DigitalData.EmailProfiler.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.EmailProfiler.Tests/Unit/Domain/

MessageIdGeneratorTests.cs:

using Xunit;
using FluentAssertions;
using DigitalData.EmailProfiler.Domain.Services;

namespace DigitalData.EmailProfiler.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:

# DigitalData EmailProfiler

## Ü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.EmailProfiler.API
dotnet run

# Create migration
cd src/DigitalData.EmailProfiler.Infrastructure
dotnet ef migrations add InitialCreate --startup-project ../DigitalData.EmailProfiler.API

# Update database
dotnet ef database update --startup-project ../DigitalData.EmailProfiler.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