CommunityCodierung & Entwicklunggithub.com

mailpit-integration

Testen Sie E-Mail-Versand lokal mit Mailpit und .NET Aspire. Erfasst alle ausgehenden E-Mails, ohne sie tatsächlich zu versenden. Zeigen Sie gerendertes HTML an, prüfen Sie Header und verifizieren Sie die Zustellung in Integrationstests.

Was ist mailpit-integration?

Diese Fähigkeit, `mailpit-integration`, ermöglicht es Entwicklern, E-Mail-Versand lokal zu testen, ohne echte E-Mails zu senden. Sie integriert Mailpit, ein leichtgewichtiges E-Mail-Testtool, in eine .NET Aspire-Anwendung. Mailpit fängt den gesamten SMTP-Verkehr ab und stellt eine Weboberfläche sowie eine API zur Verfügung, um gefangene E-Mails, deren HTML-Rendering, Header und Anhänge zu inspizieren. Die SQL-Fertigkeit beschreibt die Konfiguration von Mailpit als Container in der Aspire AppHost, die SMTP-Konfiguration in der App und die Implementierung eines E-Mail-Senders mit MailKit. Darüber hinaus enthält sie eine vollständige Testinfrastruktur mit einem MailpitClient und einem Aspire-Test-Framework, um Integrationstests zu schreiben, die asynchrone E-Mail-Zustellung verifizieren, mehrere E-Mails in einem Test prüfen und den Inhalt von E-Mails (z.B. Reset-Links) validieren können. Die Dokumentation behandelt auch die Unterscheidung zwischen Entwicklungs- und Produktions-SMTP-Konfiguration und bietet Hilfestellung zur Fehlerbehebung.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Aaronontheweb/dotnet-skills/tree/main/skills/aspire-mailpit-integration

Installed? Explore more Codierung & Entwicklung skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

E-Mail-Tests mit Mailpit und .NET Aspire

Wann diese Fähigkeit verwendet werden sollte

Verwenden Sie diese Fähigkeit, wenn:

  • Sie E-Mail-Zustellung lokal testen möchten, ohne echte E-Mails zu senden.
  • Sie E-Mail-Infrastruktur in .NET Aspire einrichten.
  • Sie Integrationstests schreiben, die den Versand von E-Mails überprüfen.
  • Sie HTML-Rendering und E-Mail-Header debuggen.

Verwandte Fähigkeiten:

  • aspnetcore/mjml-email-templates - MJML-Vorlagenbearbeitung
  • testing/verify-email-snapshots - Snapshot-Test von gerendertem HTML
  • aspire/integration-testing - Allgemeine Aspire-Testmuster

Was ist Mailpit?

Mailpit ist ein leichtgewichtiges E-Mail-Testtool, das:

  • Den gesamten SMTP-Verkehr abfängt, ohne E-Mails zuzustellen
  • Eine Weboberfläche zur Ansicht gefangener E-Mails bereitstellt
  • Eine API für den programmatischen Zugriff bereitstellt
  • HTML-Rendering, Header und Anhänge unterstützt

Ideal für Entwicklung und Integrationstests.


Aspire AppHost-Konfiguration

Fügen Sie Mailpit als Container in Ihrer AppHost hinzu:

// AppHost/Program.cs
var builder = DistributedApplication.CreateBuilder(args);

// Mailpit für E-Mail-Tests hinzufügen
var mailpit = builder.AddContainer("mailpit", "axllent/mailpit")
    .WithHttpEndpoint(port: 8025, targetPort: 8025, name: "ui")
    .WithEndpoint(port: 1025, targetPort: 1025, name: "smtp");

// Referenz im API-Projekt
var api = builder.AddProject<Projects.MyApp_Api>("api")
    .WithReference(mailpit.GetEndpoint("smtp"))
    .WithEnvironment("Smtp__Host", mailpit.GetEndpoint("smtp"));

builder.Build().Run();

SMTP-Konfiguration

appsettings.json

{
  "Smtp": {
    "Host": "localhost",
    "Port": 1025,
    "EnableSsl": false,
    "FromAddress": "[email protected]",
    "FromName": "MyApp"
  }
}

Konfigurationsklasse

public class SmtpSettings
{
    public string Host { get; set; } = "localhost";
    public int Port { get; set; } = 1025;
    public bool EnableSsl { get; set; } = false;
    public string FromAddress { get; set; } = "[email protected]";
    public string FromName { get; set; } = "MyApp";

    // Optional: Für Produktions-SMTP
    public string? Username { get; set; }
    public string? Password { get; set; }
}

Dienstregistrierung

// In Program.cs oder einer Erweiterungsmethode
services.Configure<SmtpSettings>(configuration.GetSection("Smtp"));

services.AddSingleton<IEmailSender>(sp =>
{
    var settings = sp.GetRequiredService<IOptions<SmtpSettings>>().Value;
    return new SmtpEmailSender(settings);
});

Implementierung des E-Mail-Versands

public interface IEmailSender
{
    Task SendEmailAsync(EmailMessage message, CancellationToken ct = default);
}

public sealed class SmtpEmailSender : IEmailSender
{
    private readonly SmtpSettings _settings;

    public SmtpEmailSender(SmtpSettings settings)
    {
        _settings = settings;
    }

    public async Task SendEmailAsync(EmailMessage message, CancellationToken ct = default)
    {
        using var client = new SmtpClient();

        await client.ConnectAsync(
            _settings.Host,
            _settings.Port,
            _settings.EnableSsl ? SecureSocketOptions.StartTls : SecureSocketOptions.None,
            ct);

        if (!string.IsNullOrEmpty(_settings.Username))
        {
            await client.AuthenticateAsync(_settings.Username, _settings.Password, ct);
        }

        var mailMessage = new MimeMessage();
        mailMessage.From.Add(new MailboxAddress(_settings.FromName, _settings.FromAddress));
        mailMessage.To.Add(new MailboxAddress(message.ToName, message.To));
        mailMessage.Subject = message.Subject;

        var bodyBuilder = new BodyBuilder { HtmlBody = message.HtmlBody };
        mailMessage.Body = bodyBuilder.ToMessageBody();

        await client.SendAsync(mailMessage, ct);
        await client.DisconnectAsync(true, ct);
    }
}

Erfordert das NuGet-Paket MailKit:

dotnet add package MailKit

Anzeigen gefangener E-Mails

Weboberfläche

Navigieren Sie zu http://localhost:8025, um folgendes zu sehen:

  • Posteingang - Alle gefangenen E-Mails
  • HTML-Ansicht - Gerenderte E-Mail
  • Quellansicht - Roh-HTML/MJML
  • Header - Vollständige E-Mail-Header
  • Anhänge - Alle angehängten Dateien

Aspire Dashboard

Der Mailpit-UI-Endpunkt wird im Aspire-Dashboard unter Ressourcen angezeigt.


Integrationstests

Test-Framework mit Aspire

public class EmailIntegrationTests : IClassFixture<AspireFixture>
{
    private readonly HttpClient _client;
    private readonly MailpitClient _mailpit;

    public EmailIntegrationTests(AspireFixture fixture)
    {
        _client = fixture.CreateClient();
        _mailpit = new MailpitClient(fixture.GetMailpitUrl());
    }

    [Fact]
    public async Task SignupFlow_SendsWelcomeEmail()
    {
        // Arrange
        await _mailpit.ClearMessagesAsync();

        // Act - Signup-Vorgang auslösen
        var response = await _client.PostAsJsonAsync("/api/auth/signup", new
        {
            Email = "[email protected]",
            Password = "SecurePassword123!"
        });
        response.EnsureSuccessStatusCode();

        // Assert - Überprüfen, ob E-Mail gesendet wurde
        var messages = await _mailpit.GetMessagesAsync();

        var welcomeEmail = messages.Should().ContainSingle()
            .Which;

        welcomeEmail.To.Should().Contain("[email protected]");
        welcomeEmail.Subject.Should().Contain("Welcome");
        welcomeEmail.HtmlBody.Should().Contain("Thank you for signing up");
    }
}

Mailpit API-Client

public class MailpitClient
{
    private readonly HttpClient _client;

    public MailpitClient(string baseUrl)
    {
        _client = new HttpClient { BaseAddress = new Uri(baseUrl) };
    }

    public async Task<List<MailpitMessage>> GetMessagesAsync()
    {
        var response = await _client.GetFromJsonAsync<MailpitResponse>("/api/v1/messages");
        return response?.Messages ?? new List<MailpitMessage>();
    }

    public async Task ClearMessagesAsync()
    {
        await _client.DeleteAsync("/api/v1/messages");
    }

    public async Task<MailpitMessage?> WaitForMessageAsync(
        Func<MailpitMessage, bool> predicate,
        TimeSpan timeout)
    {
        var deadline = DateTime.UtcNow + timeout;

        while (DateTime.UtcNow < deadline)
        {
            var messages = await GetMessagesAsync();
            var match = messages.FirstOrDefault(predicate);

            if (match != null)
                return match;

            await Task.Delay(100);
        }

        return null;
    }
}

public class MailpitResponse
{
    public List<MailpitMessage> Messages { get; set; } = new();
}

public class MailpitMessage
{
    public string Id { get; set; } = "";
    public List<string> To { get; set; } = new();
    public string Subject { get; set; } = "";
    public string HtmlBody { get; set; } = "";
}

Aspire Test-Framework

public class AspireFixture : IAsyncLifetime
{
    private DistributedApplication? _app;
    private string _mailpitUrl = "";

    public async Task InitializeAsync()
    {
        var appHost = await DistributedApplicationTestingBuilder
            .CreateAsync<Projects.MyApp_AppHost>();

        // Persistenz für saubere Tests deaktivieren
        appHost.Configuration["MyApp:UseVolumes"] = "false";

        _app = await appHost.BuildAsync();
        await _app.StartAsync();

        // Mailpit-URL von Aspire abrufen
        var mailpit = _app.GetContainerResource("mailpit");
        _mailpitUrl = await mailpit.GetEndpointAsync("ui");
    }

    public HttpClient CreateClient()
    {
        var api = _app!.GetProjectResource("api");
        return api.CreateHttpClient();
    }

    public string GetMailpitUrl() => _mailpitUrl;

    public async Task DisposeAsync()
    {
        if (_app != null)
            await _app.DisposeAsync();
    }
}

Häufige Testmuster

Auf asynchrone E-Mail warten

Manche E-Mails werden asynchron versendet. Warten Sie darauf:

[Fact]
public async Task AsyncWorkflow_EventuallySendsEmail()
{
    await _mailpit.ClearMessagesAsync();

    // Asynchronen Workflow auslösen
    await _client.PostAsync("/api/workflows/start", null);

    // Auf E-Mail warten (mit Timeout)
    var email = await _mailpit.WaitForMessageAsync(
        m => m.Subject.Contains("Workflow Complete"),
        timeout: TimeSpan.FromSeconds(10));

    email.Should().NotBeNull();
}

Mehrere E-Mails überprüfen

[Fact]
public async Task BulkOperation_SendsMultipleEmails()
{
    await _mailpit.ClearMessagesAsync();

    await _client.PostAsJsonAsync("/api/invitations/bulk", new
    {
        Emails = new[] { "[email protected]", "[email protected]", "[email protected]" }
    });

    var messages = await _mailpit.WaitForMessagesAsync(
        expectedCount: 3,
        timeout: TimeSpan.FromSeconds(10));

    messages.Should().HaveCount(3);
    messages.Select(m => m.To.First())
        .Should().BeEquivalentTo("[email protected]", "[email protected]", "[email protected]");
}

E-Mail-Inhalt überprüfen

[Fact]
public async Task PasswordReset_ContainsValidResetLink()
{
    await _mailpit.ClearMessagesAsync();

    await _client.PostAsJsonAsync("/api/auth/forgot-password", new
    {
        Email = "[email protected]"
    });

    var email = await _mailpit.WaitForMessageAsync(
        m => m.Subject.Contains("Password Reset"),
        timeout: TimeSpan.FromSeconds(5));

    // Reset-Link aus HTML extrahieren
    var resetLink = Regex.Match(email!.HtmlBody, @"href=""([^""]+/reset/[^""]+)""")
        .Groups[1].Value;

    resetLink.Should().StartWith("https://myapp.com/reset/");

    // Überprüfen, ob der Link funktioniert
    var resetResponse = await _client.GetAsync(resetLink);
    resetResponse.StatusCode.Should().Be(HttpStatusCode.OK);
}

Produktion vs. Entwicklung

services.AddSingleton<IEmailSender>(sp =>
{
    var settings = sp.GetRequiredService<IOptions<SmtpSettings>>().Value;
    var env = sp.GetRequiredService<IHostEnvironment>();

    if (env.IsDevelopment())
    {
        // Mailpit - keine Authentifizierung, kein SSL
        return new SmtpEmailSender(settings);
    }
    else
    {
        // Produktions-SMTP (SendGrid, Postmark, etc.)
        return new SmtpEmailSender(settings with
        {
            EnableSsl = true
        });
    }
});

Fehlerbehebung

E-Mails erscheinen nicht

  1. Überprüfen Sie, ob der Mailpit-Container im Aspire-Dashboard läuft
  2. Überprüfen Sie die SMTP-Host/Port-Konfiguration
  3. Suchen Sie nach Ausnahmen in den Anwendungsprotokollen

Verbindung abgelehnt

# Überprüfen, ob Mailpit lauscht
curl http://localhost:8025/api/v1/messages

Aspire-Endpunkt wird nicht aufgelöst

// Sicherstellen, dass die Endpunktreferenz korrekt ist
.WithEnvironment("Smtp__Host", mailpit.GetEndpoint("smtp").Property(EndpointProperty.Host))
.WithEnvironment("Smtp__Port", mailpit.GetEndpoint("smtp").Property(EndpointProperty.Port))

Ressourcen

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

akka-hosting-actor-patterns

Patterns for building entity actors with Akka.Hosting - GenericChildPerEntityParent, message extractors, cluster sharding abstraction, akka-reminders, and ITimeProvider. Supports both local testing and clustered production modes.

akka-net-aspire-configuration

Configure Akka.NET with .NET Aspire for local development and production deployments. Covers actor system setup, clustering, persistence, Akka.Management integration, and Aspire orchestration patterns.

akka-net-best-practices

Critical Akka.NET best practices including EventStream vs DistributedPubSub, supervision strategies, error handling, Props vs DependencyResolver, work distribution patterns, and cluster/local mode abstractions for testability.

akka-net-management

Akka.Management for cluster bootstrapping, service discovery (Kubernetes, Azure, Config), health checks, and dynamic cluster formation without static seed nodes.

akka-net-testing-patterns

Write unit and integration tests for Akka.NET actors using modern Akka.Hosting.TestKit patterns. Covers dependency injection, TestProbes, persistence testing, and actor interaction verification. Includes guidance on when to use traditional TestKit.

api-design

Design stable, compatible public APIs using extend-only design principles. Manage API compatibility, wire compatibility, and versioning for NuGet packages and distributed systems.

aspire-configuration

Configure Aspire AppHost to emit explicit app config via environment variables; keep app code free of Aspire clients and service discovery.

aspire-integration-testing

Write integration tests using .NET Aspire

aspire-service-defaults

Create a shared ServiceDefaults project for Aspire applications. Centralizes OpenTelemetry, health checks, resilience, and service discovery configuration across all services.

crap-analysis

Analyze code coverage and CRAP (Change Risk Anti-Patterns) scores to identify high-risk code. Use OpenCover format with ReportGenerator for Risk Hotspots showing cyclomatic complexity and untested code paths.

csharp-concurrency-patterns

Choosing the right concurrency abstraction in .NET - from async/await for I/O to Channels for producer/consumer to Akka.NET for stateful entity management. Avoid locks and manual synchronization unless absolutely necessary.

database-performance

Database access patterns for performance. Separate read/write models, avoid N+1 queries, use AsNoTracking, apply row limits, and never do application-side joins. Works with EF Core and Dapper.

dependency-injection-patterns

Organize DI registrations using IServiceCollection extension methods. Group related services into composable Add* methods for clean Program.cs and reusable configuration in tests.

dotnet-devcert-trust

Diagnose and fix .NET HTTPS dev certificate trust issues on Linux. Covers the full certificate lifecycle from generation to system CA bundle inclusion, with distro-specific guidance for Ubuntu, Fedora, Arch, and WSL2.

dotnet-local-tools

Managing local .NET tools with dotnet-tools.json for consistent tooling across development environments and CI/CD pipelines.

dotnet-project-structure

Modern .NET project structure including .slnx solution format, Directory.Build.props, central package management, SourceLink, version management with RELEASE_NOTES.md, and SDK pinning with global.json.

dotnet-slopwatch

Use Slopwatch to detect LLM reward hacking in .NET code changes. Run after every code modification to catch disabled tests, suppressed warnings, empty catch blocks, and other shortcuts that mask real problems.

efcore-patterns

Entity Framework Core best practices including NoTracking by default, query splitting for navigation collections, migration management, dedicated migration services, and common pitfalls to avoid.

ilspy-decompile

Understand implementation details of .NET code by decompiling assemblies. Use when you want to see how a .NET API works internally, inspect NuGet package source, view framework implementation, or understand compiled .NET binaries.

marketplace-publishing

Workflow for publishing skills and agents to the dotnet-skills Claude Code marketplace. Covers adding new content, updating plugin.json, validation, and release tagging.

Verwandte Skills