diff --git a/Testcontainers.sln b/Testcontainers.sln
index 9595905ed..f020821b3 100644
--- a/Testcontainers.sln
+++ b/Testcontainers.sln
@@ -1,4 +1,4 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
+Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
@@ -195,6 +195,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Tests", "tes
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.WebDriver.Tests", "tests\Testcontainers.WebDriver.Tests\Testcontainers.WebDriver.Tests.csproj", "{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Mailpit", "src\Testcontainers.Mailpit\Testcontainers.Mailpit.csproj", "{2FD800AA-7015-48B2-8A31-CB28E229F3F6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Mailpit.Tests", "tests\Testcontainers.Mailpit.Tests\Testcontainers.Mailpit.Tests.csproj", "{A7F19A31-41A9-4934-8353-BF26446DA1FE}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -568,6 +572,14 @@ Global
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2FD800AA-7015-48B2-8A31-CB28E229F3F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2FD800AA-7015-48B2-8A31-CB28E229F3F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2FD800AA-7015-48B2-8A31-CB28E229F3F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2FD800AA-7015-48B2-8A31-CB28E229F3F6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A7F19A31-41A9-4934-8353-BF26446DA1FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A7F19A31-41A9-4934-8353-BF26446DA1FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A7F19A31-41A9-4934-8353-BF26446DA1FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A7F19A31-41A9-4934-8353-BF26446DA1FE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{5365F780-0E6C-41F0-B1B9-7DC34368F80C} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
@@ -661,5 +673,7 @@ Global
{1A1983E6-5297-435F-B467-E8E1F11277D6} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{27CDB869-A150-4593-958F-6F26E5391E7C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
+ {2FD800AA-7015-48B2-8A31-CB28E229F3F6} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
+ {A7F19A31-41A9-4934-8353-BF26446DA1FE} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
EndGlobalSection
EndGlobal
diff --git a/docs/modules/index.md b/docs/modules/index.md
index e533faecf..149c70391 100644
--- a/docs/modules/index.md
+++ b/docs/modules/index.md
@@ -46,6 +46,7 @@ await moduleNameContainer.StartAsync();
| Keycloak | `quay.io/keycloak/keycloak:21.1` | [NuGet](https://www.nuget.org/packages/Testcontainers.Keycloak) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Keycloak) |
| Kusto emulator | `mcr.microsoft.com/azuredataexplorer/kustainer-linux:latest` | [NuGet](https://www.nuget.org/packages/Testcontainers.Kusto) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Kusto) |
| LocalStack | `localstack/localstack:2.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.LocalStack) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.LocalStack) |
+| Mailpit | `axllent/mailpit` | [NuGet](https://www.nuget.org/packages/Testcontainers.Mailpit) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Mailpit) |
| MariaDB | `mariadb:10.10` | [NuGet](https://www.nuget.org/packages/Testcontainers.MariaDb) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.MariaDb) |
| MinIO | `minio/minio:RELEASE.2023-01-31T02-24-19Z` | [NuGet](https://www.nuget.org/packages/Testcontainers.Minio) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Minio) |
| MongoDB | `mongo:6.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.MongoDb) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.MongoDb) |
diff --git a/src/Testcontainers.Mailpit/.editorconfig b/src/Testcontainers.Mailpit/.editorconfig
new file mode 100644
index 000000000..6f066619d
--- /dev/null
+++ b/src/Testcontainers.Mailpit/.editorconfig
@@ -0,0 +1 @@
+root = true
\ No newline at end of file
diff --git a/src/Testcontainers.Mailpit/Testcontainers.Mailpit.csproj b/src/Testcontainers.Mailpit/Testcontainers.Mailpit.csproj
new file mode 100644
index 000000000..a108060b3
--- /dev/null
+++ b/src/Testcontainers.Mailpit/Testcontainers.Mailpit.csproj
@@ -0,0 +1,12 @@
+
+
+ netstandard2.0;netstandard2.1
+ latest
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Testcontainers.Mailpit/Testcontainers.MailpitBuilder.cs b/src/Testcontainers.Mailpit/Testcontainers.MailpitBuilder.cs
new file mode 100644
index 000000000..e945856e2
--- /dev/null
+++ b/src/Testcontainers.Mailpit/Testcontainers.MailpitBuilder.cs
@@ -0,0 +1,148 @@
+using System.Net;
+
+namespace Testcontainers.Mailpit;
+
+///
+[PublicAPI]
+public sealed class MailpitBuilder
+ : ContainerBuilder
+{
+ public const string MAILPIT_IMAGE = "axllent/mailpit";
+ public const ushort MAILPIT_SMTP_PORT = 1025;
+ public const ushort MAILPIT_API_PORT = 8025;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public MailpitBuilder()
+ : this(new MailpitConfiguration())
+ {
+ DockerResourceConfiguration = Init().DockerResourceConfiguration;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ private MailpitBuilder(MailpitConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ DockerResourceConfiguration = resourceConfiguration;
+ }
+
+ ///
+ protected override MailpitConfiguration DockerResourceConfiguration { get; }
+
+ ///
+ /// Sets the Mailpit MP_SMTP_AUTH config. Also sets MP_SMTP_AUTH_ALLOW_INSECURE to true.
+ ///
+ /// List of credentials to be used in SMTP authentication
+ /// A configured instance of .
+ public MailpitBuilder WithSmtpAuthCredentials(
+ List authCredentials
+ )
+ {
+ return Merge(
+ DockerResourceConfiguration,
+ new MailpitConfiguration(
+ smtpAuthCredentials: authCredentials,
+ smtpAuthAllowInsecure: true
+ )
+ )
+ .WithEnvironment(
+ "MP_SMTP_AUTH",
+ string.Join(" ", authCredentials.Select(e => $"{e.Username}:{e.Password}"))
+ )
+ .WithEnvironment("MP_SMTP_AUTH_ALLOW_INSECURE", "1");
+ }
+
+ ///
+ /// Sets the Mailpit MP_SMTP_AUTH_ALLOW_INSECURE config.
+ /// Typically STARTTLS is enforced for all SMTP authentication. This option allows insecure PLAIN & LOGIN SMTP authentication.
+ ///
+ /// Whether or not to allow PLAIN & LOGIN SMTP authentication
+ /// A configured instance of .
+ public MailpitBuilder WithSmtpAuthAllowInsecure(bool allowInsecure)
+ {
+ return Merge(
+ DockerResourceConfiguration,
+ new MailpitConfiguration(smtpAuthAllowInsecure: allowInsecure)
+ )
+ .WithEnvironment("MP_SMTP_AUTH_ALLOW_INSECURE", allowInsecure ? "1" : "0");
+ }
+
+ ///
+ /// Sets the Mailpit MP_MAX_MESSAGES config.
+ /// Maximum number of messages to store. Mailpit will periodically delete the oldest messages if greater than this. Set to 0 to disable auto-deletion.
+ ///
+ /// Maximum number to set
+ /// A configured instance of .
+ public MailpitBuilder WithMaxMessages(uint maxMessages)
+ {
+ return Merge(
+ DockerResourceConfiguration,
+ new MailpitConfiguration(maxMessages: maxMessages)
+ )
+ .WithEnvironment("MP_MAX_MESSAGES", maxMessages.ToString());
+ }
+
+ ///
+ public override MailpitContainer Build()
+ {
+ Validate();
+ return new MailpitContainer(DockerResourceConfiguration, TestcontainersSettings.Logger);
+ }
+
+ ///
+ protected override MailpitBuilder Init()
+ {
+ return base.Init()
+ .WithImage(MAILPIT_IMAGE)
+ .WithPortBinding(MAILPIT_SMTP_PORT, true)
+ .WithPortBinding(MAILPIT_API_PORT, true)
+ .WithWaitStrategy(
+ Wait.ForUnixContainer()
+ .UntilHttpRequestIsSucceeded(r =>
+ r.ForPort(MAILPIT_API_PORT)
+ .ForPath("/api/v1/info")
+ .ForStatusCode(HttpStatusCode.OK)
+ )
+ );
+ }
+
+ ///
+ protected override void Validate()
+ {
+ base.Validate();
+
+ _ = Guard
+ .Argument(
+ DockerResourceConfiguration.SmtpAuthCredentials,
+ nameof(DockerResourceConfiguration.SmtpAuthCredentials)
+ )
+ .NotNull();
+ }
+
+ ///
+ protected override MailpitBuilder Clone(
+ IResourceConfiguration resourceConfiguration
+ )
+ {
+ return Merge(DockerResourceConfiguration, new MailpitConfiguration(resourceConfiguration));
+ }
+
+ ///
+ protected override MailpitBuilder Clone(IContainerConfiguration resourceConfiguration)
+ {
+ return Merge(DockerResourceConfiguration, new MailpitConfiguration(resourceConfiguration));
+ }
+
+ ///
+ protected override MailpitBuilder Merge(
+ MailpitConfiguration oldValue,
+ MailpitConfiguration newValue
+ )
+ {
+ return new MailpitBuilder(new MailpitConfiguration(oldValue, newValue));
+ }
+}
diff --git a/src/Testcontainers.Mailpit/Testcontainers.MailpitConfiguration.cs b/src/Testcontainers.Mailpit/Testcontainers.MailpitConfiguration.cs
new file mode 100644
index 000000000..117f937ea
--- /dev/null
+++ b/src/Testcontainers.Mailpit/Testcontainers.MailpitConfiguration.cs
@@ -0,0 +1,99 @@
+namespace Testcontainers.Mailpit;
+
+///
+[PublicAPI]
+public sealed class MailpitConfiguration : ContainerConfiguration
+{
+ public sealed class AuthCredentials
+ {
+ public string Username { get; }
+ public string Password { get; }
+
+ public AuthCredentials(string username, string password)
+ {
+ Username = username;
+ Password = password;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Testcontainers.Mailpit config.
+ public MailpitConfiguration(
+ List smtpAuthCredentials = null,
+ bool smtpAuthAllowInsecure = true,
+ uint maxMessages = 100
+ )
+ {
+ SmtpAuthCredentials = smtpAuthCredentials;
+ SmtpAuthAllowInsecure = smtpAuthAllowInsecure;
+ MaxMessages = maxMessages;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public MailpitConfiguration(
+ IResourceConfiguration resourceConfiguration
+ )
+ : base(resourceConfiguration)
+ {
+ // Passes the configuration upwards to the base implementations to create an updated immutable copy.
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public MailpitConfiguration(IContainerConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ // Passes the configuration upwards to the base implementations to create an updated immutable copy.
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public MailpitConfiguration(MailpitConfiguration resourceConfiguration)
+ : this(new MailpitConfiguration(), resourceConfiguration)
+ {
+ // Passes the configuration upwards to the base implementations to create an updated immutable copy.
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The old Docker resource configuration.
+ /// The new Docker resource configuration.
+ public MailpitConfiguration(MailpitConfiguration oldValue, MailpitConfiguration newValue)
+ : base(oldValue, newValue)
+ {
+ SmtpAuthCredentials = BuildConfiguration.Combine(
+ oldValue.SmtpAuthCredentials,
+ newValue.SmtpAuthCredentials
+ );
+ SmtpAuthAllowInsecure = BuildConfiguration.Combine(
+ oldValue.SmtpAuthAllowInsecure,
+ newValue.SmtpAuthAllowInsecure
+ );
+ }
+
+ ///
+ /// A list of usernames and passwords for SMTP authentication.
+ /// See Mailpit docs for more information.
+ ///
+ public List SmtpAuthCredentials { get; }
+
+ ///
+ /// Typically STARTTLS is enforced for all SMTP authentication. This option allows insecure PLAIN & LOGIN SMTP authentication.
+ ///
+ public bool SmtpAuthAllowInsecure { get; }
+
+ ///
+ /// Maximum number of messages to store. Mailpit will periodically delete the oldest messages if greater than this. Set to 0 to disable auto-deletion.
+ ///
+ public uint MaxMessages { get; }
+}
diff --git a/src/Testcontainers.Mailpit/Testcontainers.MailpitContainer.cs b/src/Testcontainers.Mailpit/Testcontainers.MailpitContainer.cs
new file mode 100644
index 000000000..151f5876a
--- /dev/null
+++ b/src/Testcontainers.Mailpit/Testcontainers.MailpitContainer.cs
@@ -0,0 +1,30 @@
+namespace Testcontainers.Mailpit;
+
+///
+[PublicAPI]
+public sealed class MailpitContainer : DockerContainer
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The container configuration.
+ /// The logger.
+ public MailpitContainer(MailpitConfiguration configuration, ILogger logger)
+ : base(configuration, logger) { }
+
+ ///
+ /// SMTP server port.
+ ///
+ public ushort SmtpPort
+ {
+ get => GetMappedPublicPort(MailpitBuilder.MAILPIT_SMTP_PORT);
+ }
+
+ ///
+ /// Web API server port.
+ ///
+ public ushort ApiPort
+ {
+ get => GetMappedPublicPort(MailpitBuilder.MAILPIT_API_PORT);
+ }
+}
diff --git a/src/Testcontainers.Mailpit/Usings.cs b/src/Testcontainers.Mailpit/Usings.cs
new file mode 100644
index 000000000..f889bad0a
--- /dev/null
+++ b/src/Testcontainers.Mailpit/Usings.cs
@@ -0,0 +1,10 @@
+global using System;
+global using System.Collections.Generic;
+global using System.Linq;
+global using Docker.DotNet.Models;
+global using DotNet.Testcontainers;
+global using DotNet.Testcontainers.Builders;
+global using DotNet.Testcontainers.Configurations;
+global using DotNet.Testcontainers.Containers;
+global using JetBrains.Annotations;
+global using Microsoft.Extensions.Logging;
\ No newline at end of file
diff --git a/tests/Testcontainers.Mailpit.Tests/.editorconfig b/tests/Testcontainers.Mailpit.Tests/.editorconfig
new file mode 100644
index 000000000..6f066619d
--- /dev/null
+++ b/tests/Testcontainers.Mailpit.Tests/.editorconfig
@@ -0,0 +1 @@
+root = true
\ No newline at end of file
diff --git a/tests/Testcontainers.Mailpit.Tests/MailpitContainerTest.cs b/tests/Testcontainers.Mailpit.Tests/MailpitContainerTest.cs
new file mode 100644
index 000000000..76b77be7b
--- /dev/null
+++ b/tests/Testcontainers.Mailpit.Tests/MailpitContainerTest.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Net.Mail;
+using System.Web;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Testcontainers.Mailpit;
+
+public sealed class MailpitContainerTest : IAsyncLifetime
+{
+ private readonly MailpitContainer _mailpitContainer = new MailpitBuilder()
+ .WithSmtpAuthCredentials(
+ new List([GetTestCredentials()])
+ )
+ .Build();
+
+ public Task InitializeAsync()
+ {
+ return _mailpitContainer.StartAsync();
+ }
+
+ public Task DisposeAsync()
+ {
+ return _mailpitContainer.DisposeAsync().AsTask();
+ }
+
+ private static MailpitConfiguration.AuthCredentials GetTestCredentials() => new("test", "test");
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task MailSentAndApiReturnsSuccessful()
+ {
+ // Given
+ const string to = "receiver@mailpit-testcontainers.com";
+ const string from = "sender@mailpit-testcontainers.com";
+ var message = new MailMessage(from, to)
+ {
+ Subject = "Hey there from Mailpit!",
+ Body =
+ "This is just a test message, it doesn't have much going on.\n\nCheers,\n\nSender"
+ };
+ var credentials = GetTestCredentials();
+ var smtpClient = new SmtpClient(_mailpitContainer.Hostname, _mailpitContainer.SmtpPort)
+ {
+ Credentials = new NetworkCredential(credentials.Username, credentials.Password)
+ };
+
+ // When
+ await smtpClient.SendMailAsync(message);
+
+ // Then
+ var client = new HttpClient { Timeout = TimeSpan.FromSeconds(2) };
+ client.DefaultRequestHeaders.Accept.Clear();
+ client.DefaultRequestHeaders.Accept.Add(
+ new MediaTypeWithQualityHeaderValue("application/json")
+ );
+
+ var host = _mailpitContainer.Hostname;
+ var port = _mailpitContainer.ApiPort;
+ var queryParams = HttpUtility.ParseQueryString(string.Empty);
+ queryParams.Add("query", $"to:\"{to}\"");
+ var url = $"http://{host}:{port}/api/v1/search?{queryParams}";
+ var response = await client.GetAsync(url);
+
+ var jsonString = await response.Content.ReadAsStringAsync();
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ var responseBody = JsonConvert.DeserializeObject(jsonString);
+
+ Assert.Equal(1, responseBody["messages_count"]);
+ }
+}
diff --git a/tests/Testcontainers.Mailpit.Tests/Testcontainers.Mailpit.Tests.csproj b/tests/Testcontainers.Mailpit.Tests/Testcontainers.Mailpit.Tests.csproj
new file mode 100644
index 000000000..3edb6b57d
--- /dev/null
+++ b/tests/Testcontainers.Mailpit.Tests/Testcontainers.Mailpit.Tests.csproj
@@ -0,0 +1,18 @@
+
+
+ net8.0
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/Testcontainers.Mailpit.Tests/Usings.cs b/tests/Testcontainers.Mailpit.Tests/Usings.cs
new file mode 100644
index 000000000..8148d1e1f
--- /dev/null
+++ b/tests/Testcontainers.Mailpit.Tests/Usings.cs
@@ -0,0 +1,5 @@
+global using System.Data;
+global using System.Data.Common;
+global using System.Threading.Tasks;
+global using DotNet.Testcontainers.Commons;
+global using Xunit;