$npx -y skills add wshaddix/dotnet-skills --skill aspire-mailpit-integrationTest email sending locally using Mailpit with .NET Aspire. Captures all outgoing emails without sending them. View rendered HTML, inspect headers, and verify delivery in integration tests. Use when testing email delivery locally without sending real emails, setting up email infra
| 1 | # Email Testing with Mailpit and .NET Aspire |
| 2 | |
| 3 | ## When to Use This Skill |
| 4 | |
| 5 | Use this skill when: |
| 6 | - Testing email delivery locally without sending real emails |
| 7 | - Setting up email infrastructure in .NET Aspire |
| 8 | - Writing integration tests that verify emails are sent |
| 9 | - Debugging email rendering and headers |
| 10 | |
| 11 | **Related skills:** |
| 12 | - `aspnetcore/mjml-email-templates` - MJML template authoring |
| 13 | - `testing/verify-email-snapshots` - Snapshot test rendered HTML |
| 14 | - `aspire/integration-testing` - General Aspire testing patterns |
| 15 | |
| 16 | --- |
| 17 | |
| 18 | ## What is Mailpit? |
| 19 | |
| 20 | [Mailpit](https://github.com/axllent/mailpit) is a lightweight email testing tool that: |
| 21 | - Captures all SMTP traffic without delivering emails |
| 22 | - Provides a web UI to view captured emails |
| 23 | - Exposes an API for programmatic access |
| 24 | - Supports HTML rendering, headers, and attachments |
| 25 | |
| 26 | Perfect for development and integration testing. |
| 27 | |
| 28 | --- |
| 29 | |
| 30 | ## Aspire AppHost Configuration |
| 31 | |
| 32 | Add Mailpit as a container in your AppHost: |
| 33 | |
| 34 | ```csharp |
| 35 | // AppHost/Program.cs |
| 36 | var builder = DistributedApplication.CreateBuilder(args); |
| 37 | |
| 38 | // Add Mailpit for email testing |
| 39 | var mailpit = builder.AddContainer("mailpit", "axllent/mailpit") |
| 40 | .WithHttpEndpoint(port: 8025, targetPort: 8025, name: "ui") |
| 41 | .WithEndpoint(port: 1025, targetPort: 1025, name: "smtp"); |
| 42 | |
| 43 | // Reference in your API project |
| 44 | var api = builder.AddProject<Projects.MyApp_Api>("api") |
| 45 | .WithReference(mailpit.GetEndpoint("smtp")) |
| 46 | .WithEnvironment("Smtp__Host", mailpit.GetEndpoint("smtp")); |
| 47 | |
| 48 | builder.Build().Run(); |
| 49 | ``` |
| 50 | |
| 51 | --- |
| 52 | |
| 53 | ## SMTP Configuration |
| 54 | |
| 55 | ### appsettings.json |
| 56 | |
| 57 | ```json |
| 58 | { |
| 59 | "Smtp": { |
| 60 | "Host": "localhost", |
| 61 | "Port": 1025, |
| 62 | "EnableSsl": false, |
| 63 | "FromAddress": "noreply@myapp.com", |
| 64 | "FromName": "MyApp" |
| 65 | } |
| 66 | } |
| 67 | ``` |
| 68 | |
| 69 | ### Configuration Class |
| 70 | |
| 71 | ```csharp |
| 72 | public class SmtpSettings |
| 73 | { |
| 74 | public string Host { get; set; } = "localhost"; |
| 75 | public int Port { get; set; } = 1025; |
| 76 | public bool EnableSsl { get; set; } = false; |
| 77 | public string FromAddress { get; set; } = "noreply@myapp.com"; |
| 78 | public string FromName { get; set; } = "MyApp"; |
| 79 | |
| 80 | // Optional: For production SMTP |
| 81 | public string? Username { get; set; } |
| 82 | public string? Password { get; set; } |
| 83 | } |
| 84 | ``` |
| 85 | |
| 86 | ### Service Registration |
| 87 | |
| 88 | ```csharp |
| 89 | // In Program.cs or extension method |
| 90 | services.Configure<SmtpSettings>(configuration.GetSection("Smtp")); |
| 91 | |
| 92 | services.AddSingleton<IEmailSender>(sp => |
| 93 | { |
| 94 | var settings = sp.GetRequiredService<IOptions<SmtpSettings>>().Value; |
| 95 | return new SmtpEmailSender(settings); |
| 96 | }); |
| 97 | ``` |
| 98 | |
| 99 | --- |
| 100 | |
| 101 | ## Email Sender Implementation |
| 102 | |
| 103 | ```csharp |
| 104 | public interface IEmailSender |
| 105 | { |
| 106 | Task SendEmailAsync(EmailMessage message, CancellationToken ct = default); |
| 107 | } |
| 108 | |
| 109 | public sealed class SmtpEmailSender : IEmailSender |
| 110 | { |
| 111 | private readonly SmtpSettings _settings; |
| 112 | |
| 113 | public SmtpEmailSender(SmtpSettings settings) |
| 114 | { |
| 115 | _settings = settings; |
| 116 | } |
| 117 | |
| 118 | public async Task SendEmailAsync(EmailMessage message, CancellationToken ct = default) |
| 119 | { |
| 120 | using var client = new SmtpClient(); |
| 121 | |
| 122 | await client.ConnectAsync( |
| 123 | _settings.Host, |
| 124 | _settings.Port, |
| 125 | _settings.EnableSsl ? SecureSocketOptions.StartTls : SecureSocketOptions.None, |
| 126 | ct); |
| 127 | |
| 128 | if (!string.IsNullOrEmpty(_settings.Username)) |
| 129 | { |
| 130 | await client.AuthenticateAsync(_settings.Username, _settings.Password, ct); |
| 131 | } |
| 132 | |
| 133 | var mailMessage = new MimeMessage(); |
| 134 | mailMessage.From.Add(new MailboxAddress(_settings.FromName, _settings.FromAddress)); |
| 135 | mailMessage.To.Add(new MailboxAddress(message.ToName, message.To)); |
| 136 | mailMessage.Subject = message.Subject; |
| 137 | |
| 138 | var bodyBuilder = new BodyBuilder { HtmlBody = message.HtmlBody }; |
| 139 | mailMessage.Body = bodyBuilder.ToMessageBody(); |
| 140 | |
| 141 | await client.SendAsync(mailMessage, ct); |
| 142 | await client.DisconnectAsync(true, ct); |
| 143 | } |
| 144 | } |
| 145 | ``` |
| 146 | |
| 147 | Requires `MailKit` package: |
| 148 | |
| 149 | ```bash |
| 150 | dotnet add package MailKit |
| 151 | ``` |
| 152 | |
| 153 | --- |
| 154 | |
| 155 | ## Viewing Captured Emails |
| 156 | |
| 157 | ### Web UI |
| 158 | |
| 159 | Navigate to `http://localhost:8025` to see: |
| 160 | |
| 161 | - **Inbox** - All captured emails |
| 162 | - **HTML view** - Rendered email |
| 163 | - **Source view** - Raw HTML/MJML output |
| 164 | - **Headers** - Full email headers |
| 165 | - **Attachments** - Any attached files |
| 166 | |
| 167 | ### Aspire Dashboard |
| 168 | |
| 169 | The Mailpit UI endpoint appears in the Aspire dashboard under Resources. |
| 170 | |
| 171 | --- |
| 172 | |
| 173 | ## Integration Testing |
| 174 | |
| 175 | ### Test Fixture with Aspire |
| 176 | |
| 177 | ```csharp |
| 178 | public class EmailIntegrationTests : IClassFixture<AspireFixture> |
| 179 | { |
| 180 | private readonly HttpClient _client; |
| 181 | private readonly MailpitClient _mailpit; |
| 182 | |
| 183 | public EmailIntegrationTests(AspireFixture fixture) |
| 184 | { |
| 185 | _client = fi |