mcpField
M
mailpit-integration

mailpit-integration

github.com/aarononthewebテスト・品質1.2k431 回インストール今日更新
リポジトリ

mailpit-integration について

.NET Aspire の開発やテストで Mailpit をメール受信先として設定するスキルです。SMTP 設定、捕捉したメールの確認、Mailpit API を使った配信や内容の検証を扱います。

主な機能

  1. 01Aspire AppHost での Mailpit 設定
  2. 02SMTP 送信機能の設定
  3. 03捕捉したメールの閲覧
  4. 04Mailpit API のテストパターン
  5. 05非同期配信の確認

活用シーン

  1. 01実メールを送らずに配信をテスト
  2. 02メールの表示、ヘッダー、添付ファイルの確認
  3. 03Aspire 統合テストでメールを検証

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/aaronontheweb/dotnet-skills --skill mailpit-integration

インストール後、Claude Code で/mailpit-integrationを入力して読み込まれたか確認します。

詳細

Mailpit と .NET Aspire によるメールテスト

このスキルを使用する場面

次の場合にこのスキルを使用します。

  • 実際のメールを送信せずに、ローカルでメール配信をテストする場合
  • .NET Aspire でメールインフラストラクチャを設定する場合
  • メールが送信されることを検証する統合テストを作成する場合
  • メールのレンダリングやヘッダーをデバッグする場合

関連スキル:

  • aspnetcore/mjml-email-templates - MJML テンプレートの作成
  • testing/verify-email-snapshots - レンダリングされた HTML のスナップショットテスト
  • aspire/integration-testing - Aspire の一般的なテストパターン

Mailpit とは?

Mailpit は、軽量なメールテストツールです。次の機能があります。

  • メールを配信せずに、すべての SMTP トラフィックをキャプチャします
  • キャプチャしたメールを表示する Web UI を提供します
  • プログラムからアクセスするための API を公開します
  • HTML のレンダリング、ヘッダー、添付ファイルに対応しています

開発や統合テストに最適です。


Aspire AppHost の設定

AppHost に Mailpit をコンテナーとして追加します。

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

// Add Mailpit for email testing
var mailpit = builder.AddContainer("mailpit", "axllent/mailpit")
    .WithHttpEndpoint(port: 8025, targetPort: 8025, name: "ui")
    .WithEndpoint(port: 1025, targetPort: 1025, name: "smtp");

// Reference in your API project
var api = builder.AddProject<Projects.MyApp_Api>("api")
    .WithReference(mailpit.GetEndpoint("smtp"))
    .WithEnvironment("Smtp__Host", mailpit.GetEndpoint("smtp"));

builder.Build().Run();

SMTP の設定

appsettings.json
{
  "Smtp": {
    "Host": "localhost",
    "Port": 1025,
    "EnableSsl": false,
    "FromAddress": "[email protected]",
    "FromName": "MyApp"
  }
}
設定クラス
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: For production SMTP
    public string? Username { get; set; }
    public string? Password { get; set; }
}
サービスの登録
// In Program.cs or extension method
services.Configure<SmtpSettings>(configuration.GetSection("Smtp"));

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

メール送信機能の実装

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

MailKit パッケージが必要です。

dotnet add package MailKit

キャプチャしたメールの表示

Web UI

http://localhost:8025 にアクセスすると、次の内容を確認できます。

  • 受信トレイ - キャプチャしたすべてのメール
  • HTML 表示 - レンダリングされたメール
  • ソース表示 - HTML/MJML の生データ
  • ヘッダー - メールの完全なヘッダー
  • 添付ファイル - 添付されたファイル
Aspire ダッシュボード

Mailpit の UI エンドポイントは、Aspire ダッシュボードの[リソース]に表示されます。


統合テスト

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 - Trigger signup flow
        var response = await _client.PostAsJsonAsync("/api/auth/signup", new
        {
            Email = "[email protected]",
            Password = "SecurePassword123!"
        });
        response.EnsureSuccessStatusCode();

        // Assert - Verify email was sent
        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 クライアント
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 テストフィクスチャ

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

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

        // Disable persistence for clean tests
        appHost.Configuration["MyApp:UseVolumes"] = "false";

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

        // Get Mailpit URL from 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();
    }
}

よくあるテストパターン

非同期メールを待機する

メールが非同期で送信される場合があります。次のようにしてメールを待機します。

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

    // Trigger async workflow
    await _client.PostAsync("/api/workflows/start", null);

    // Wait for email (with timeout)
    var email = await _mailpit.WaitForMessageAsync(
        m => m.Subject.Contains("Workflow Complete"),
        timeout: TimeSpan.FromSeconds(10));

    email.Should().NotBeNull();
}
複数のメールを検証する
[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]");
}
メールの内容を検証する
[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));

    // Extract reset link from HTML
    var resetLink = Regex.Match(email!.HtmlBody, @"href=""([^""]+/reset/[^""]+)""")
        .Groups[1].Value;

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

    // Verify the link works
    var resetResponse = await _client.GetAsync(resetLink);
    resetResponse.StatusCode.Should().Be(HttpStatusCode.OK);
}

本番環境と開発環境

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

    if (env.IsDevelopment())
    {
        // Mailpit - no auth, no SSL
        return new SmtpEmailSender(settings);
    }
    else
    {
        // Production SMTP (SendGrid, Postmark, etc.)
        return new SmtpEmailSender(settings with
        {
            EnableSsl = true
        });
    }
});

トラブルシューティング

メールが表示されない
  1. Aspire ダッシュボードで Mailpit コンテナーが実行中であることを確認します
  2. SMTP ホスト/ポートの設定を確認します
  3. アプリケーションログに例外がないか確認します
接続が拒否される
# Verify Mailpit is listening
curl http://localhost:8025/api/v1/messages
Aspire エンドポイントが解決されない
// Ensure endpoint reference is correct
.WithEnvironment("Smtp__Host", mailpit.GetEndpoint("smtp").Property(EndpointProperty.Host))
.WithEnvironment("Smtp__Port", mailpit.GetEndpoint("smtp").Property(EndpointProperty.Port))

リソース

コメント