Integração com Mailpit

Teste o envio de e-mails localmente usando Mailpit com .NET Aspire. Captura todos os e-mails de saída sem enviá-los. Visualize HTML renderizado, inspecione cabeçalhos e verifique a entrega em testes de integração.

O que é Integração com Mailpit?

Esta skill fornece uma configuração completa para teste de envio de e-mails em ambientes de desenvolvimento e integração com .NET Aspire, utilizando o Mailpit como ferramenta de captura. O Mailpit intercepta todo o tráfego SMTP sem entregar os e-mails reais, permitindo que você visualize o HTML renderizado, inspecione cabeçalhos e anexos, e verifique o conteúdo programaticamente através de uma API REST. A skill abrange desde a configuração do contêiner Mailpit no AppHost do Aspire, passando pela implementação de um serviço de envio de e-mails com MailKit, até a criação de testes de integração com uma fixture personalizada que gerencia o ciclo de vida do Mailpit. Inclui padrões comuns de teste, como esperar por e-mails assíncronos, verificar múltiplos e-mails e extrair links de e-mails HTML, além de orientações sobre como alternar entre configurações de desenvolvimento e produção. É útil para desenvolvedores que precisam validar o fluxo de e-mails em suas aplicações sem depender de servidores SMTP reais.

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

Installed? Explore more Programação e Desenvolvimento skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Teste de E-mail com Mailpit e .NET Aspire

Quando Usar Esta Skill

Use esta skill quando:

  • Testar a entrega de e-mails localmente sem enviar e-mails reais
  • Configurar infraestrutura de e-mail no .NET Aspire
  • Escrever testes de integração que verifiquem o envio de e-mails
  • Depurar renderização de e-mails e cabeçalhos

Skills relacionadas:

  • aspnetcore/mjml-email-templates - Criação de templates MJML
  • testing/verify-email-snapshots - Teste de snapshot de HTML renderizado
  • aspire/integration-testing - Padrões gerais de teste no Aspire

O que é Mailpit?

Mailpit é uma ferramenta leve de teste de e-mail que:

  • Captura todo o tráfego SMTP sem entregar e-mails
  • Fornece uma interface web para visualizar e-mails capturados
  • Expõe uma API para acesso programático
  • Suporta renderização HTML, cabeçalhos e anexos

Perfeito para desenvolvimento e testes de integração.


Configuração do AppHost no Aspire

Adicione o Mailpit como um contêiner no seu AppHost:

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

// Adiciona Mailpit para teste de e-mail
var mailpit = builder.AddContainer("mailpit", "axllent/mailpit")
    .WithHttpEndpoint(port: 8025, targetPort: 8025, name: "ui")
    .WithEndpoint(port: 1025, targetPort: 1025, name: "smtp");

// Referencia no seu projeto de API
var api = builder.AddProject<Projects.MyApp_Api>("api")
    .WithReference(mailpit.GetEndpoint("smtp"))
    .WithEnvironment("Smtp__Host", mailpit.GetEndpoint("smtp"));

builder.Build().Run();

Configuração SMTP

appsettings.json

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

Classe de Configuração

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";

    // Opcional: Para SMTP de produção
    public string? Username { get; set; }
    public string? Password { get; set; }
}

Registro do Serviço

// Em Program.cs ou método de extensão
services.Configure<SmtpSettings>(configuration.GetSection("Smtp"));

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

Implementação do Envio de E-mail

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);
    }
}

Requer o pacote MailKit:

dotnet add package MailKit

Visualizando E-mails Capturados

Interface Web

Navegue para http://localhost:8025 para ver:

  • Caixa de entrada - Todos os e-mails capturados
  • Visualização HTML - E-mail renderizado
  • Visualização do código-fonte - Saída bruta de HTML/MJML
  • Cabeçalhos - Cabeçalhos completos do e-mail
  • Anexos - Quaisquer arquivos anexados

Painel do Aspire

O endpoint da interface do Mailpit aparece no painel do Aspire sob Recursos.


Testes de Integração

Fixture de Teste com 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 - Organizar
        await _mailpit.ClearMessagesAsync();

        // Act - Agir: Disparar fluxo de cadastro
        var response = await _client.PostAsJsonAsync("/api/auth/signup", new
        {
            Email = "[email protected]",
            Password = "SecurePassword123!"
        });
        response.EnsureSuccessStatusCode();

        // Assert - Afirmar: Verificar se o e-mail foi enviado
        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");
    }
}

Cliente da API do Mailpit

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; } = "";
}

Fixture de Teste do Aspire

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

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

        // Desabilitar persistência para testes limpos
        appHost.Configuration["MyApp:UseVolumes"] = "false";

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

        // Obter URL do Mailpit do Aspire
        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();
    }
}

Padrões Comuns de Teste

Aguardar E-mail Assíncrono

Alguns e-mails são enviados de forma assíncrona. Aguarde por eles:

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

    // Disparar fluxo de trabalho assíncrono
    await _client.PostAsync("/api/workflows/start", null);

    // Aguardar e-mail (com timeout)
    var email = await _mailpit.WaitForMessageAsync(
        m => m.Subject.Contains("Workflow Complete"),
        timeout: TimeSpan.FromSeconds(10));

    email.Should().NotBeNull();
}

Verificar Múltiplos E-mails

[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]");
}

Verificar Conteúdo do E-mail

[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));

    // Extrair link de redefinição do HTML
    var resetLink = Regex.Match(email!.HtmlBody, @"href=""([^""]+/reset/[^""]+)""")
        .Groups[1].Value;

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

    // Verificar se o link funciona
    var resetResponse = await _client.GetAsync(resetLink);
    resetResponse.StatusCode.Should().Be(HttpStatusCode.OK);
}

Produção vs Desenvolvimento

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

    if (env.IsDevelopment())
    {
        // Mailpit - sem autenticação, sem SSL
        return new SmtpEmailSender(settings);
    }
    else
    {
        // SMTP de produção (SendGrid, Postmark, etc.)
        return new SmtpEmailSender(settings with
        {
            EnableSsl = true
        });
    }
});

Solução de Problemas

E-mails Não Aparecem

  1. Verifique se o contêiner Mailpit está em execução no painel do Aspire
  2. Verifique a configuração de host/porta SMTP
  3. Verifique se há exceções nos logs da aplicação

Conexão Recusada

# Verifique se Mailpit está ouvindo
curl http://localhost:8025/api/v1/messages

Endpoint do Aspire Não Resolve

// Certifique-se de que a referência ao endpoint está correta
.WithEnvironment("Smtp__Host", mailpit.GetEndpoint("smtp").Property(EndpointProperty.Host))
.WithEnvironment("Smtp__Port", mailpit.GetEndpoint("smtp").Property(EndpointProperty.Port))

Recursos

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.

Habilidades Relacionadas