diff --git a/CommunityToolkit.Aspire.sln b/CommunityToolkit.Aspire.sln
index 10ba15dc..670ea438 100644
--- a/CommunityToolkit.Aspire.sln
+++ b/CommunityToolkit.Aspire.sln
@@ -325,6 +325,18 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "redis-ext", "redis-ext", "{
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hosting.Redis.Extensions.Tests", "tests\CommunityToolkit.Aspire.Hosting.Redis.Extensions.Tests\CommunityToolkit.Aspire.Hosting.Redis.Extensions.Tests.csproj", "{958A251E-9D2A-4D67-9D1C-47D6A9E36B3A}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "mailpit", "mailpit", "{734EF69D-EE4D-4E9B-96BD-A44E53C5D2EC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hosting.MailPit", "src\CommunityToolkit.Aspire.Hosting.MailPit\CommunityToolkit.Aspire.Hosting.MailPit.csproj", "{73E7BC19-A626-4C55-990B-A878BF84D4CA}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hosting.MailPit.Tests", "tests\CommunityToolkit.Aspire.Hosting.MailPit.Tests\CommunityToolkit.Aspire.Hosting.MailPit.Tests.csproj", "{9C66ECFF-8077-4D5A-9CEB-6E9AB6CCBE94}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults", "examples\mailpit\CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults\CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults.csproj", "{08EDD2B6-FFC1-42F9-AE62-479178A381FF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi", "examples\mailpit\CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi\CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi.csproj", "{547ED5DB-CD15-42B8-99C7-30274C5D843E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hosting.MailPit.AppHost", "examples\mailpit\CommunityToolkit.Aspire.Hosting.MailPit.AppHost\CommunityToolkit.Aspire.Hosting.MailPit.AppHost.csproj", "{69343427-D50D-4600-ADC0-C1B1910C0287}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -855,6 +867,26 @@ Global
{958A251E-9D2A-4D67-9D1C-47D6A9E36B3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{958A251E-9D2A-4D67-9D1C-47D6A9E36B3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{958A251E-9D2A-4D67-9D1C-47D6A9E36B3A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {73E7BC19-A626-4C55-990B-A878BF84D4CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {73E7BC19-A626-4C55-990B-A878BF84D4CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {73E7BC19-A626-4C55-990B-A878BF84D4CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {73E7BC19-A626-4C55-990B-A878BF84D4CA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9C66ECFF-8077-4D5A-9CEB-6E9AB6CCBE94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9C66ECFF-8077-4D5A-9CEB-6E9AB6CCBE94}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9C66ECFF-8077-4D5A-9CEB-6E9AB6CCBE94}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9C66ECFF-8077-4D5A-9CEB-6E9AB6CCBE94}.Release|Any CPU.Build.0 = Release|Any CPU
+ {08EDD2B6-FFC1-42F9-AE62-479178A381FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {08EDD2B6-FFC1-42F9-AE62-479178A381FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {08EDD2B6-FFC1-42F9-AE62-479178A381FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {08EDD2B6-FFC1-42F9-AE62-479178A381FF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {547ED5DB-CD15-42B8-99C7-30274C5D843E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {547ED5DB-CD15-42B8-99C7-30274C5D843E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {547ED5DB-CD15-42B8-99C7-30274C5D843E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {547ED5DB-CD15-42B8-99C7-30274C5D843E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {69343427-D50D-4600-ADC0-C1B1910C0287}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {69343427-D50D-4600-ADC0-C1B1910C0287}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {69343427-D50D-4600-ADC0-C1B1910C0287}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {69343427-D50D-4600-ADC0-C1B1910C0287}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1018,6 +1050,12 @@ Global
{CB491C52-A3D5-4F36-97C4-75C482F90A30} = {734EF69D-EE4D-4E9B-96BD-A44E53C5D1EC}
{734EF69D-EE4D-4E9B-96BD-A44E53C5D1EC} = {8519CC01-1370-47C8-AD94-B0F326B1563F}
{958A251E-9D2A-4D67-9D1C-47D6A9E36B3A} = {899F0713-7FC6-4750-BAFC-AC650B35B453}
+ {73E7BC19-A626-4C55-990B-A878BF84D4CA} = {414151D4-7009-4E78-A5C6-D99EBD1E67D1}
+ {9C66ECFF-8077-4D5A-9CEB-6E9AB6CCBE94} = {899F0713-7FC6-4750-BAFC-AC650B35B453}
+ {734EF69D-EE4D-4E9B-96BD-A44E53C5D2EC} = {8519CC01-1370-47C8-AD94-B0F326B1563F}
+ {08EDD2B6-FFC1-42F9-AE62-479178A381FF} = {734EF69D-EE4D-4E9B-96BD-A44E53C5D2EC}
+ {547ED5DB-CD15-42B8-99C7-30274C5D843E} = {734EF69D-EE4D-4E9B-96BD-A44E53C5D2EC}
+ {69343427-D50D-4600-ADC0-C1B1910C0287} = {734EF69D-EE4D-4E9B-96BD-A44E53C5D2EC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {08B1D4B8-D2C5-4A64-BB8B-E1A2B29525F0}
diff --git a/README.md b/README.md
index b5ba88a0..e812d532 100644
--- a/README.md
+++ b/README.md
@@ -13,10 +13,10 @@ All features are contributed by you, our amazing .NET community, and maintained
This repository contains the source code for the .NET Aspire Community Toolkit, a collection of community created Integrations and extensions for [.NET Aspire](https://aka.ms/dotnet/aspire).
| Package | Description |
-| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| - **Learn More**: [`Hosting.Azure.StaticWebApps`][swa-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps][swa-shields]][swa-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps][swa-shields-preview]][swa-nuget-preview] | A hosting integration for the [Azure Static Web Apps emulator](https://learn.microsoft.com/azure/static-web-apps/static-web-apps-cli-overview) (Note: this does not support deployment of a project to Azure Static Web Apps). |
| - **Learn More**: [`Hosting.Golang`][golang-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.Hosting.Golang][golang-shields]][golang-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.Hosting.Golang][golang-shields-preview]][golang-nuget-preview] | A hosting integration Golang apps. |
-| **Learn More**: [`Hosting.Java`][java-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.Hosting.Java][java-shields]][java-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.Hosting.Java][java-shields-preview]][java-nuget-preview] | An integration for running Java code in .NET Aspire either using the local JDK or using a container. |
+| - **Learn More**: [`Hosting.Java`][java-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.Hosting.Java][java-shields]][java-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.Hosting.Java][java-shields-preview]][java-nuget-preview] | An integration for running Java code in .NET Aspire either using the local JDK or using a container. |
| - **Learn More**: [`Hosting.NodeJS.Extensions`][nodejs-ext-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.NodeJS.Extensions][nodejs-ext-shields]][nodejs-ext-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.Hosting.NodeJS.Extensions][nodejs-ext-shields-preview]][nodejs-ext-nuget-preview] | An integration that contains some additional extensions for running Node.js applications |
| - **Learn More**: [`Hosting.Ollama`][ollama-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.Hosting.Ollama][ollama-shields]][ollama-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.Hosting.Ollama][ollama-shields-preview]][ollama-nuget-preview] | An Aspire hosting integration leveraging the [Ollama](https://ollama.com) container with support for downloading a model on startup. |
| - **Learn More**: [`OllamaSharp`][ollama-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.OllamaSharp][ollamasharp-shields]][ollamasharp-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.OllamaSharp][ollamasharp-shields-preview]][ollama-nuget-preview] | An Aspire client integration for the [OllamaSharp](https://github.com/awaescher/OllamaSharp) package. |
@@ -36,13 +36,15 @@ This repository contains the source code for the .NET Aspire Community Toolkit,
| - **Learn More**: [`Microsoft.EntityFrameworkCore.Sqlite`][sqlite-ef-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.Microsoft.EntityFrameworkCore.Sqlite][sqlite-ef-shields]][sqlite-ef-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.Microsoft.EntityFrameworkCore.Sqlite][sqlite-ef-shields-preview]][sqlite-ef-nuget-preview] | An Aspire client integration for the Microsoft.EntityFrameworkCore.Sqlite NuGet package. |
| - **Learn More**: [`Hosting.Dapr`][dapr-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.Hosting.Dapr][dapr-shields]][dapr-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.Hosting.Dapr][dapr-shields-preview]][dapr-nuget-preview] | An Aspire hosting integration for Dapr. |
| - **Learn More**: [`Hosting.Dapr.AzureRedis`][dapr-azureredis-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.Hosting.Dapr.AzureRedis][dapr-azureredis-shields]][dapr-azureredis-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.Hosting.Dapr.AzureRedis][dapr-azureredis-shields-preview]][dapr-azureredis-nuget-preview] | An extension for the Dapr hosting integration for using Dapr with Azure Redis cache. |
-| - **Learn More**: [`Hosting.RavenDB`][ravendb-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.Hosting.RavenDB][ravendb-shields]][ravendb-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.Hosting.RavenDB][ravendb-shields-preview]][ravendb-nuget-preview] | An Aspire integration leveraging the [RavenDB](https://ravendb.net/) container. |
-| - **Learn More**: [`RavenDB.Client`][ravendb-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.RavenDB.Client][ravendb-client-shields]][ravendb-client-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.RavenDB.Client][ravendb-client-shields-preview]][ravendb-client-nuget-preview] | An Aspire client integration for the [RavenDB.Client](https://www.nuget.org/packages/RavenDB.client) package. |
+| - **Learn More**: [`Hosting.RavenDB`][ravendb-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.Hosting.RavenDB][ravendb-shields]][ravendb-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.Hosting.RavenDB][ravendb-shields-preview]][ravendb-nuget-preview] | An Aspire integration leveraging the [RavenDB](https://ravendb.net/) container. |
+| - **Learn More**: [`RavenDB.Client`][ravendb-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.RavenDB.Client][ravendb-client-shields]][ravendb-client-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.RavenDB.Client][ravendb-client-shields-preview]][ravendb-client-nuget-preview] | An Aspire client integration for the [RavenDB.Client](https://www.nuget.org/packages/RavenDB.client) package. |
| - **Learn More**: [`Hosting.GoFeatureFlag`][go-feature-flag-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.Hosting.GoFeatureFlag][go-feature-flag-shields]][go-feature-flag-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.Hosting.GoFeatureFlag][go-feature-flag-shields-preview]][go-feature-flag-nuget-preview] | An Aspire hosting integration leveraging the [GoFeatureFlag](https://gofeatureflag.org/) container. |
| - **Learn More**: [`GoFeatureFlag`][go-feature-flag-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.GoFeatureFlag][go-feature-flag-client-shields]][go-feature-flag-client-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.GoFeatureFlag][go-feature-flag-client-shields-preview]][go-feature-flag-client-nuget-preview] | An Aspire client integration for the [GoFeatureFlag](https://github.com/open-feature/dotnet-sdk-contrib/tree/main/src/OpenFeature.Contrib.Providers.GOFeatureFlag) package. |
-| - **Learn More**: [`Hosting.MongoDB.Extensions`][mongodb-ext-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.MongoDB.Extensions][mongodb-ext-shields]][mongodb-ext-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.Hosting.MongoDB.Extensions][mongodb-ext-shields-preview]][mongodb-ext-nuget-preview] | An integration that contains some additional extensions for hosting MongoDB container. |
-| - **Learn More**: [`Hosting.PostgreSQL.Extensions`][postgres-ext-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.PostgreSQL.Extensions][postgres-ext-shields]][postgres-ext-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.Hosting.PostgreSQL.Extensions][postgres-ext-shields-preview]][postgres-ext-nuget-preview] | An integration that contains some additional extensions for hosting PostgreSQL container. |
-| - **Learn More**: [`Hosting.Redis.Extensions`][redis-ext-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.Redis.Extensions][redis-ext-shields]][redis-ext-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.Hosting.Redis.Extensions][redis-ext-shields-preview]][redis-ext-nuget-preview] | An integration that contains some additional extensions for hosting Redis container. |
+| - **Learn More**: [`Hosting.MongoDB.Extensions`][mongodb-ext-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.MongoDB.Extensions][mongodb-ext-shields]][mongodb-ext-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.Hosting.MongoDB.Extensions][mongodb-ext-shields-preview]][mongodb-ext-nuget-preview] | An integration that contains some additional extensions for hosting MongoDB container. |
+| - **Learn More**: [`Hosting.PostgreSQL.Extensions`][postgres-ext-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.PostgreSQL.Extensions][postgres-ext-shields]][postgres-ext-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.Hosting.PostgreSQL.Extensions][postgres-ext-shields-preview]][postgres-ext-nuget-preview] | An integration that contains some additional extensions for hosting PostgreSQL container. |
+| - **Learn More**: [`Hosting.Redis.Extensions`][redis-ext-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.Redis.Extensions][redis-ext-shields]][redis-ext-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.Hosting.Redis.Extensions][redis-ext-shields-preview]][redis-ext-nuget-preview] | An integration that contains some additional extensions for hosting Redis container. |
+| - **Learn More**: [`Hosting.MailPit`][mailpit-ext-integration-docs]
- Stable π¦: [![CommunityToolkit.Aspire.Hosting.MailPit][mailpit-ext-shields]][mailpit-ext-nuget]
- Preview π¦: [![CommunityToolkit.Aspire.Hosting.MailPit][mailpit-ext-shields-preview]][mailpit-ext-nuget-preview] | An Aspire integration leveraging the [MailPit](https://mailpit.axllent.org/) container. |
+
## π Getting Started
@@ -221,4 +223,9 @@ This project is supported by the [.NET Foundation](https://dotnetfoundation.org)
[redis-ext-shields]: https://img.shields.io/nuget/v/CommunityToolkit.Aspire.Hosting.Redis.Extensions
[redis-ext-nuget]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.Redis.Extensions/
[redis-ext-shields-preview]: https://img.shields.io/nuget/vpre/CommunityToolkit.Aspire.Hosting.Redis.Extensions?label=nuget%20(preview)
-[redis-ext-nuget-preview]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.Redis.Extensions/absoluteLatest
\ No newline at end of file
+[redis-ext-nuget-preview]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.Redis.Extensions/absoluteLatest
+[mailpit-ext-integration-docs]: https://learn.microsoft.com/dotnet/aspire/community-toolkit/hosting-mailpit
+[mailpit-ext-shields]: https://img.shields.io/nuget/v/CommunityToolkit.Aspire.Hosting.MailPit
+[mailpit-ext-nuget]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.MailPit/
+[mailpit-ext-shields-preview]: https://img.shields.io/nuget/vpre/CommunityToolkit.Aspire.Hosting.MailPit?label=nuget%20(preview)
+[mailpit-ext-nuget-preview]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.MailPit/absoluteLatest
\ No newline at end of file
diff --git a/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.AppHost/CommunityToolkit.Aspire.Hosting.MailPit.AppHost.csproj b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.AppHost/CommunityToolkit.Aspire.Hosting.MailPit.AppHost.csproj
new file mode 100644
index 00000000..edf88d7e
--- /dev/null
+++ b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.AppHost/CommunityToolkit.Aspire.Hosting.MailPit.AppHost.csproj
@@ -0,0 +1,23 @@
+
+
+
+
+
+ Exe
+ enable
+ enable
+ true
+ f60c6ce9-5628-467c-a6fc-2fc7938b39ad
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.AppHost/Program.cs b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.AppHost/Program.cs
new file mode 100644
index 00000000..5c2945b4
--- /dev/null
+++ b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.AppHost/Program.cs
@@ -0,0 +1,11 @@
+using Projects;
+
+var builder = DistributedApplication.CreateBuilder(args);
+
+var mailPit = builder.AddMailPit("mailpit");
+
+var sendmail = builder.AddProject("sendmail")
+ .WithReference(mailPit)
+ .WaitFor(mailPit);
+
+builder.Build().Run();
diff --git a/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.AppHost/appsettings.json b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.AppHost/appsettings.json
new file mode 100644
index 00000000..31c092aa
--- /dev/null
+++ b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.AppHost/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning",
+ "Aspire.Hosting.Dcp": "Warning"
+ }
+ }
+}
diff --git a/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi.csproj b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi.csproj
new file mode 100644
index 00000000..fa3ea638
--- /dev/null
+++ b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi.csproj
@@ -0,0 +1,16 @@
+
+
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi.http b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi.http
new file mode 100644
index 00000000..fe6ae012
--- /dev/null
+++ b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi.http
@@ -0,0 +1,13 @@
+@CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi_HostAddress = http://localhost:5075
+
+POST http://localhost:5075/send
+Accept: application/json
+Content-Type: application/json
+
+{
+ "From": "test@test.nl",
+ "To": "toTest@test.nl",
+ "Body": "Hello World",
+ "Subject": "Test"
+}
+###
diff --git a/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/MailData.cs b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/MailData.cs
new file mode 100644
index 00000000..c0f08d8a
--- /dev/null
+++ b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/MailData.cs
@@ -0,0 +1,15 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi;
+
+public class MailData
+{
+ [Required]
+ public required string From { get; set; }
+ [Required]
+ public required string To { get; set; }
+ [Required]
+ public required string Body { get; set; }
+ [Required]
+ public required string Subject { get; set; }
+}
\ No newline at end of file
diff --git a/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/Program.cs b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/Program.cs
new file mode 100644
index 00000000..8e879689
--- /dev/null
+++ b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/Program.cs
@@ -0,0 +1,44 @@
+using CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi;
+using Microsoft.AspNetCore.Mvc;
+using System.Data.Common;
+using System.Net.Mail;
+
+WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
+
+string? mailPitConnectionString = builder.Configuration.GetConnectionString("mailpit");
+DbConnectionStringBuilder connectionBuilder = new()
+{
+ ConnectionString = mailPitConnectionString
+};
+
+Uri endpoint = new(connectionBuilder["Endpoint"].ToString()!, UriKind.Absolute);
+builder.Services.AddScoped(_ => new SmtpClient(endpoint.Host, endpoint.Port));
+builder.AddServiceDefaults();
+WebApplication app = builder.Build();
+
+app.MapPost("/send", ([FromBody]MailData mailData, [FromServices] SmtpClient smtpClient) =>
+ {
+ MailMessage myMail = CreateMailMessage(mailData);
+ smtpClient.Send(myMail);
+ })
+.WithName("SendMail");
+
+app.MapGet("/health", () => "OK");
+
+app.MapDefaultEndpoints();
+app.Run();
+return;
+
+MailMessage CreateMailMessage(MailData mailData)
+{
+ MailAddress from = new(mailData.From, "TestFromName");
+ MailAddress to = new(mailData.To, "TestToName");
+ MailMessage mailMessage = new(from, to);
+ MailAddress replyTo = new(mailData.From);
+ mailMessage.ReplyToList.Add(replyTo);
+ mailMessage.Subject = mailData.Subject;
+ mailMessage.SubjectEncoding = System.Text.Encoding.UTF8;
+ mailMessage.Body = mailData.Body;
+ mailMessage.BodyEncoding = System.Text.Encoding.UTF8;
+ return mailMessage;
+}
\ No newline at end of file
diff --git a/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/appsettings.json b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/appsettings.json
new file mode 100644
index 00000000..10f68b8c
--- /dev/null
+++ b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults/CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults.csproj b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults/CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults.csproj
new file mode 100644
index 00000000..caa6344d
--- /dev/null
+++ b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults/CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults.csproj
@@ -0,0 +1,21 @@
+
+
+
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults/Extensions.cs b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults/Extensions.cs
new file mode 100644
index 00000000..8cf2bbd1
--- /dev/null
+++ b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults/Extensions.cs
@@ -0,0 +1,118 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Diagnostics.HealthChecks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Microsoft.Extensions.Logging;
+using OpenTelemetry;
+using OpenTelemetry.Metrics;
+using OpenTelemetry.Trace;
+
+namespace Microsoft.Extensions.Hosting;
+
+// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry.
+// This project should be referenced by each service project in your solution.
+// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults
+public static class Extensions
+{
+ public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder
+ {
+ builder.ConfigureOpenTelemetry();
+
+ builder.AddDefaultHealthChecks();
+
+ builder.Services.AddServiceDiscovery();
+
+ builder.Services.ConfigureHttpClientDefaults(http =>
+ {
+ // Turn on resilience by default
+ http.AddStandardResilienceHandler();
+
+ // Turn on service discovery by default
+ http.AddServiceDiscovery();
+ });
+
+ // Uncomment the following to restrict the allowed schemes for service discovery.
+ // builder.Services.Configure(options =>
+ // {
+ // options.AllowedSchemes = ["https"];
+ // });
+
+ return builder;
+ }
+
+ public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder
+ {
+ builder.Logging.AddOpenTelemetry(logging =>
+ {
+ logging.IncludeFormattedMessage = true;
+ logging.IncludeScopes = true;
+ });
+
+ builder.Services.AddOpenTelemetry()
+ .WithMetrics(metrics =>
+ {
+ metrics.AddAspNetCoreInstrumentation()
+ .AddHttpClientInstrumentation()
+ .AddRuntimeInstrumentation();
+ })
+ .WithTracing(tracing =>
+ {
+ tracing.AddSource(builder.Environment.ApplicationName)
+ .AddAspNetCoreInstrumentation()
+ // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package)
+ //.AddGrpcClientInstrumentation()
+ .AddHttpClientInstrumentation();
+ });
+
+ builder.AddOpenTelemetryExporters();
+
+ return builder;
+ }
+
+ private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder
+ {
+ var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
+
+ if (useOtlpExporter)
+ {
+ builder.Services.AddOpenTelemetry().UseOtlpExporter();
+ }
+
+ // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package)
+ //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]))
+ //{
+ // builder.Services.AddOpenTelemetry()
+ // .UseAzureMonitor();
+ //}
+
+ return builder;
+ }
+
+ public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder
+ {
+ builder.Services.AddHealthChecks()
+ // Add a default liveness check to ensure app is responsive
+ .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
+
+ return builder;
+ }
+
+ public static WebApplication MapDefaultEndpoints(this WebApplication app)
+ {
+ // Adding health checks endpoints to applications in non-development environments has security implications.
+ // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments.
+ if (app.Environment.IsDevelopment())
+ {
+ // All health checks must pass for app to be considered ready to accept traffic after starting
+ app.MapHealthChecks("/health");
+
+ // Only health checks tagged with the "live" tag must pass for app to be considered alive
+ app.MapHealthChecks("/alive", new HealthCheckOptions
+ {
+ Predicate = r => r.Tags.Contains("live")
+ });
+ }
+
+ return app;
+ }
+}
diff --git a/src/CommunityToolkit.Aspire.Hosting.MailPit/CommunityToolkit.Aspire.Hosting.MailPit.csproj b/src/CommunityToolkit.Aspire.Hosting.MailPit/CommunityToolkit.Aspire.Hosting.MailPit.csproj
new file mode 100644
index 00000000..068e607a
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.MailPit/CommunityToolkit.Aspire.Hosting.MailPit.csproj
@@ -0,0 +1,15 @@
+ο»Ώ
+
+
+ An Aspire component leveraging the MailPit container.
+ mailpit smtp hosting
+
+
+
+
+
+
+
+
+
+
diff --git a/src/CommunityToolkit.Aspire.Hosting.MailPit/MailPitContainerImageTags.cs b/src/CommunityToolkit.Aspire.Hosting.MailPit/MailPitContainerImageTags.cs
new file mode 100644
index 00000000..bee66efe
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.MailPit/MailPitContainerImageTags.cs
@@ -0,0 +1,13 @@
+namespace CommunityToolkit.Aspire.Hosting.MailPit;
+
+internal static class MailPitContainerImageTags
+{
+ /// docker.io
+ public const string Registry = "docker.io";
+
+ /// axllent/mailpit
+ public const string Image = "axllent/mailpit";
+
+ /// v1.22.3
+ public const string Tag = "v1.22.3";
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Aspire.Hosting.MailPit/MailPitContainerResource.cs b/src/CommunityToolkit.Aspire.Hosting.MailPit/MailPitContainerResource.cs
new file mode 100644
index 00000000..63534cf4
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.MailPit/MailPitContainerResource.cs
@@ -0,0 +1,23 @@
+ο»Ώnamespace Aspire.Hosting.ApplicationModel;
+
+///
+/// Resource for the MailPit server.
+///
+///
+public class MailPitContainerResource(string name) : ContainerResource(name), IResourceWithConnectionString
+{
+ internal const int HttpEndpointPort = 8025;
+ internal const int SmtpEndpointPort = 1025;
+ internal const string SmtpEndpointName = "smtp";
+ internal const string HttpEndpointName = "http";
+ internal const string DatabaseEnvVar = "MP_DATABASE";
+
+ private EndpointReference? _smtpEndpoint;
+ private EndpointReference SmtpEndpoint => _smtpEndpoint ??= new EndpointReference(this, SmtpEndpointName);
+
+ ///
+ /// ConnectionString for MailPit smtp endpoint in the form of smtp://host:port.
+ ///
+ public ReferenceExpression ConnectionStringExpression => ReferenceExpression.Create(
+ $"Endpoint={SmtpEndpoint.Scheme}://{SmtpEndpoint.Property(EndpointProperty.Host)}:{SmtpEndpoint.Property(EndpointProperty.Port)}");
+}
diff --git a/src/CommunityToolkit.Aspire.Hosting.MailPit/MailPitHostingExtension.cs b/src/CommunityToolkit.Aspire.Hosting.MailPit/MailPitHostingExtension.cs
new file mode 100644
index 00000000..c87eb1bd
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.MailPit/MailPitHostingExtension.cs
@@ -0,0 +1,81 @@
+ο»Ώusing Aspire.Hosting.ApplicationModel;
+using CommunityToolkit.Aspire.Hosting.MailPit;
+
+namespace Aspire.Hosting;
+
+///
+/// Provides extension methods for adding MailPit to an .
+///
+public static class MailPitHostingExtension
+{
+ ///
+ /// Adds a MailPit container resource to the .
+ ///
+ /// The to which the MailPit resource will be added.
+ /// The name of the MailPit container resource.
+ /// Optional. The HTTP port on which MailPit will listen.
+ /// Optional. The SMTP port on which MailPit will listen.
+ /// A reference to the for further resource configuration.
+ public static IResourceBuilder AddMailPit(this IDistributedApplicationBuilder builder,
+ [ResourceName] string name,
+ int? httpPort = null,
+ int? smtpPort = null)
+ {
+ ArgumentNullException.ThrowIfNull("Service name must be specified.", nameof(name));
+ MailPitContainerResource resource = new(name);
+
+ IResourceBuilder rb = builder.AddResource(resource)
+ .WithImage(MailPitContainerImageTags.Image)
+ .WithImageTag(MailPitContainerImageTags.Tag)
+ .WithImageRegistry(MailPitContainerImageTags.Registry)
+ .WithEndpoint(
+ targetPort: MailPitContainerResource.SmtpEndpointPort,
+ port: smtpPort,
+ name: MailPitContainerResource.SmtpEndpointName,
+ scheme: "smtp")
+ .WithHttpEndpoint(
+ targetPort: MailPitContainerResource.HttpEndpointPort,
+ port: httpPort,
+ name: MailPitContainerResource.HttpEndpointName)
+ .WithHttpHealthCheck("/", httpPort, MailPitContainerResource.HttpEndpointName);
+
+ return rb;
+ }
+
+ ///
+ /// Configures a data volume for the MailPit container resource.
+ ///
+ /// The used to configure the resource.
+ /// The name of the data volume to be mounted.
+ /// A boolean indicating whether the volume should be mounted as read-only.
+ /// A reference to the for further configuration.
+ public static IResourceBuilder WithDataVolume(this IResourceBuilder builder, string name,
+ bool isReadOnly = false)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ return builder.WithEnvironment(context =>
+ {
+ context.EnvironmentVariables[MailPitContainerResource.DatabaseEnvVar] = "/data/mailpit.db";
+ }).WithVolume(name, "/data", isReadOnly);
+ }
+
+ ///
+ /// Configures a bind mount for the data directory of the MailPit container resource.
+ ///
+ /// The to configure the bind mount on.
+ /// The source path on the host system to bind to the container.
+ /// A value indicating whether the bind mount should be read-only. Default is false.
+ /// A reference to the with the configured bind mount.
+ public static IResourceBuilder WithDataBindMount(this IResourceBuilder builder, string source,
+ bool isReadOnly = false)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(source);
+
+ return builder.WithEnvironment(context =>
+ {
+ context.EnvironmentVariables[MailPitContainerResource.DatabaseEnvVar] = "/data/mailpit.db";
+ }).WithBindMount(source, "/data", isReadOnly);
+ }
+}
diff --git a/src/CommunityToolkit.Aspire.Hosting.MailPit/PublicAPI.Shipped.txt b/src/CommunityToolkit.Aspire.Hosting.MailPit/PublicAPI.Shipped.txt
new file mode 100644
index 00000000..7dc5c581
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.MailPit/PublicAPI.Shipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/CommunityToolkit.Aspire.Hosting.MailPit/PublicAPI.Unshipped.txt b/src/CommunityToolkit.Aspire.Hosting.MailPit/PublicAPI.Unshipped.txt
new file mode 100644
index 00000000..bae61cc4
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.MailPit/PublicAPI.Unshipped.txt
@@ -0,0 +1,8 @@
+#nullable enable
+Aspire.Hosting.ApplicationModel.MailPitContainerResource
+Aspire.Hosting.ApplicationModel.MailPitContainerResource.ConnectionStringExpression.get -> Aspire.Hosting.ApplicationModel.ReferenceExpression!
+Aspire.Hosting.ApplicationModel.MailPitContainerResource.MailPitContainerResource(string! name) -> void
+Aspire.Hosting.MailPitHostingExtension
+static Aspire.Hosting.MailPitHostingExtension.AddMailPit(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, int? httpPort = null, int? smtpPort = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
+static Aspire.Hosting.MailPitHostingExtension.WithDataBindMount(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! source, bool isReadOnly = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
+static Aspire.Hosting.MailPitHostingExtension.WithDataVolume(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! name, bool isReadOnly = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
\ No newline at end of file
diff --git a/src/CommunityToolkit.Aspire.Hosting.MailPit/README.md b/src/CommunityToolkit.Aspire.Hosting.MailPit/README.md
new file mode 100644
index 00000000..a2293851
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.MailPit/README.md
@@ -0,0 +1,39 @@
+# CommunityToolkit.Hosting.MailPit
+
+## Overview
+
+This .NET Aspire Integration runs [MailPit](https://github.com/axllent/mailpit) in a container.
+
+
+## Usage
+
+The MailPit integration exposes a connection string with the format `endpoint=smtp://:`.
+This connection string can be used to with a DbConnectionStringBuilder to get the smtp endpoint.
+
+### Example 1: Add MailPit with generated ports
+
+```csharp
+var builder = DistributedApplication.CreateBuilder(args);
+
+var mailpit = builder.AddMailPit("mailpit");
+
+var xyz = builder.AddProject("application")
+ .WithReference(mailpit)
+ .WaitFor(mailpit);
+
+builder.Build().Run();
+```
+
+### Example 2: Add MailPit with user-defined ports
+
+```csharp
+var builder = DistributedApplication.CreateBuilder(args);
+
+var mailpit = builder.AddMailPit("mailpit", 80, 25);
+
+var xyz = builder.AddProject("application")
+ .WithReference(mailpit)
+ .WaitFor(mailpit);
+
+builder.Build().Run();
+```
diff --git a/tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests/AppHostTests.cs b/tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests/AppHostTests.cs
new file mode 100644
index 00000000..19c947cb
--- /dev/null
+++ b/tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests/AppHostTests.cs
@@ -0,0 +1,66 @@
+using Aspire.Components.Common.Tests;
+using CommunityToolkit.Aspire.Testing;
+using System.Data.Common;
+using System.Net.Http.Json;
+using System.Net.Mail;
+using System.Text.Json;
+
+namespace CommunityToolkit.Aspire.Hosting.MailPit.Tests;
+
+[RequiresDocker]
+public class AppHostTests(AspireIntegrationTestFixture fixture) : IClassFixture>
+{
+ private const string ResourceName = "mailpit";
+
+ [Fact]
+ public async Task ResourceStartsAndMailShouldBeReceived()
+ {
+ await fixture.ResourceNotificationService.WaitForResourceHealthyAsync(ResourceName).WaitAsync(TimeSpan.FromMinutes(2));
+ SmtpClient smtpClient = await CreateSmtpClient();
+ MailMessage myMail = CreateMailMessage();
+ HttpClient httpClient = CreateHttpClientForMailPit();
+
+ Exception? exception = Record.Exception(() => smtpClient.Send(myMail));
+
+ Assert.Null(exception);
+ HttpResponseMessage response = await httpClient.GetAsync("/api/v1/messages");
+ response.EnsureSuccessStatusCode();
+
+ JsonDocument? responseDocument = await response.Content.ReadFromJsonAsync();
+ Assert.NotNull(responseDocument);
+ int totalMessageCount = responseDocument.RootElement.GetProperty("messages_count").GetInt32();
+ Assert.Equal(1, totalMessageCount);
+ }
+
+ private HttpClient CreateHttpClientForMailPit()
+ {
+ var httpEndpoint = fixture.GetEndpoint(ResourceName, "http");
+ return new HttpClient { BaseAddress = httpEndpoint };
+ }
+
+ private static MailMessage CreateMailMessage()
+ {
+ MailAddress from = new("test@test.com", "TestFromName");
+ MailAddress to = new("to@test.com", "TestToName");
+ MailMessage myMail = new(from, to);
+ MailAddress replyTo = new("reply@test.com");
+ myMail.ReplyToList.Add(replyTo);
+ myMail.Subject = "Subject";
+ myMail.SubjectEncoding = System.Text.Encoding.UTF8;
+ myMail.Body = "Hello world!";
+ myMail.BodyEncoding = System.Text.Encoding.UTF8;
+ return myMail;
+ }
+
+ private async Task CreateSmtpClient()
+ {
+ string? connectionString = await fixture.GetConnectionString(ResourceName);
+ DbConnectionStringBuilder connectionBuilder = new()
+ {
+ ConnectionString = connectionString
+ };
+ Uri connectionUri = new(connectionBuilder["Endpoint"].ToString()!, UriKind.Absolute);
+ SmtpClient smtpClient = new(connectionUri.Host, connectionUri.Port);
+ return smtpClient;
+ }
+}
\ No newline at end of file
diff --git a/tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests.csproj b/tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests.csproj
new file mode 100644
index 00000000..f34f78da
--- /dev/null
+++ b/tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests.csproj
@@ -0,0 +1,14 @@
+ο»Ώ
+
+
+ false
+ true
+
+
+
+
+
+
+
+
+
diff --git a/tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests/ContainerResourceCreationTests.cs b/tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests/ContainerResourceCreationTests.cs
new file mode 100644
index 00000000..0dd80bcd
--- /dev/null
+++ b/tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests/ContainerResourceCreationTests.cs
@@ -0,0 +1,96 @@
+using Aspire.Hosting;
+
+namespace CommunityToolkit.Aspire.Hosting.MailPit.Tests;
+
+public class ContainerResourceCreationTests
+{
+ [Fact]
+ public void AddMailPitBuilderShouldNotBeNull()
+ {
+ IDistributedApplicationBuilder builder = null!;
+ Assert.Throws(() => builder.AddMailPit("mailpit"));
+ }
+
+ [Fact]
+ public void AddMailPitBuilderNameShouldNotBeNullOrWhiteSpace()
+ {
+ IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder();
+
+ Assert.Throws(() => builder.AddMailPit(null!));
+ }
+
+ [Fact]
+ public void AddMailPitBuilderContainerDetailsSetOnResource()
+ {
+ IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder();
+
+ builder.AddMailPit("mailpit");
+
+ using var app = builder.Build();
+ var appModel = app.Services.GetRequiredService();
+
+ var resource = appModel.Resources.OfType().SingleOrDefault();
+
+ Assert.NotNull(resource);
+ Assert.Equal("mailpit", resource.Name);
+
+ Assert.True(resource.TryGetLastAnnotation(out ContainerImageAnnotation? imageAnnotations));
+ Assert.Equal(MailPitContainerImageTags.Tag, imageAnnotations.Tag);
+ Assert.Equal(MailPitContainerImageTags.Image, imageAnnotations.Image);
+ Assert.Equal(MailPitContainerImageTags.Registry, imageAnnotations.Registry);
+ }
+
+ [Fact]
+ public void AddMailPitBuilderContainerWithDataVolumeDetailsSetOnResource()
+ {
+ IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder();
+
+ builder.AddMailPit("mailpit")
+ .WithDataVolume("mailpit-data", isReadOnly: false);
+
+ using var app = builder.Build();
+ var appModel = app.Services.GetRequiredService();
+
+ var resource = appModel.Resources.OfType().SingleOrDefault();
+
+ Assert.NotNull(resource);
+ Assert.Equal("mailpit", resource.Name);
+
+ Assert.True(resource.TryGetLastAnnotation(out ContainerImageAnnotation? imageAnnotations));
+ Assert.Equal(MailPitContainerImageTags.Tag, imageAnnotations.Tag);
+ Assert.Equal(MailPitContainerImageTags.Image, imageAnnotations.Image);
+ Assert.Equal(MailPitContainerImageTags.Registry, imageAnnotations.Registry);
+
+ Assert.True(resource.TryGetLastAnnotation(out ContainerMountAnnotation? mountAnnotations));
+ Assert.Equal(ContainerMountType.Volume, mountAnnotations.Type);
+ Assert.Equal("/data", mountAnnotations.Target);
+ }
+
+ [Fact]
+ public void AddMailPitBuilderContainerWithDataMountDetailsSetOnResource()
+ {
+ IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder();
+
+ builder.AddMailPit("mailpit")
+ .WithDataBindMount("mailpit-data", isReadOnly: false);
+
+ using var app = builder.Build();
+ var appModel = app.Services.GetRequiredService();
+
+ var resource = appModel.Resources.OfType().SingleOrDefault();
+
+ Assert.NotNull(resource);
+ Assert.Equal("mailpit", resource.Name);
+
+ Assert.True(resource.TryGetLastAnnotation(out ContainerImageAnnotation? imageAnnotations));
+ Assert.Equal(MailPitContainerImageTags.Tag, imageAnnotations.Tag);
+ Assert.Equal(MailPitContainerImageTags.Image, imageAnnotations.Image);
+ Assert.Equal(MailPitContainerImageTags.Registry, imageAnnotations.Registry);
+
+ Assert.True(resource.TryGetLastAnnotation(out ContainerMountAnnotation? mountAnnotations));
+ Assert.Equal(ContainerMountType.BindMount, mountAnnotations.Type);
+ Assert.Equal("/data", mountAnnotations.Target);
+ Assert.NotNull(mountAnnotations.Source);
+ }
+}
+