diff --git a/Testcontainers.sln b/Testcontainers.sln
index ac471c14e..176958bfa 100644
--- a/Testcontainers.sln
+++ b/Testcontainers.sln
@@ -63,6 +63,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Neo4j", "src
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Oracle", "src\Testcontainers.Oracle\Testcontainers.Oracle.csproj", "{596EAFC1-0496-495C-B382-D57415FA456A}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Papercut", "src\Testcontainers.Papercut\Testcontainers.Papercut.csproj", "{464F1120-A0DA-462B-B9E8-45176D883625}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.PostgreSql", "src\Testcontainers.PostgreSql\Testcontainers.PostgreSql.csproj", "{8AB91636-9055-4900-A72A-7CFFACDFDBF0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.PubSub", "src\Testcontainers.PubSub\Testcontainers.PubSub.csproj", "{E6642255-667D-476B-B584-089AA5E6C0B1}"
@@ -135,6 +137,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Neo4j.Tests"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Oracle.Tests", "tests\Testcontainers.Oracle.Tests\Testcontainers.Oracle.Tests.csproj", "{4AC1088B-9965-4497-AC8E-570F1AD5631F}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Papercut.Tests", "tests\Testcontainers.Papercut.Tests\Testcontainers.Papercut.Tests.csproj", "{904C8476-FCEF-41F0-8948-9EFA7C08712E}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Platform.Linux.Tests", "tests\Testcontainers.Platform.Linux.Tests\Testcontainers.Platform.Linux.Tests.csproj", "{DA1D7ADE-452C-4369-83CC-56289176EACD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Platform.Windows.Tests", "tests\Testcontainers.Platform.Windows.Tests\Testcontainers.Platform.Windows.Tests.csproj", "{3E55CBE8-AFE8-426D-9470-49D63CD1051C}"
@@ -268,6 +272,10 @@ Global
{596EAFC1-0496-495C-B382-D57415FA456A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{596EAFC1-0496-495C-B382-D57415FA456A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{596EAFC1-0496-495C-B382-D57415FA456A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {464F1120-A0DA-462B-B9E8-45176D883625}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {464F1120-A0DA-462B-B9E8-45176D883625}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {464F1120-A0DA-462B-B9E8-45176D883625}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {464F1120-A0DA-462B-B9E8-45176D883625}.Release|Any CPU.Build.0 = Release|Any CPU
{8AB91636-9055-4900-A72A-7CFFACDFDBF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8AB91636-9055-4900-A72A-7CFFACDFDBF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8AB91636-9055-4900-A72A-7CFFACDFDBF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -412,6 +420,10 @@ Global
{4AC1088B-9965-4497-AC8E-570F1AD5631F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4AC1088B-9965-4497-AC8E-570F1AD5631F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4AC1088B-9965-4497-AC8E-570F1AD5631F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {904C8476-FCEF-41F0-8948-9EFA7C08712E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {904C8476-FCEF-41F0-8948-9EFA7C08712E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {904C8476-FCEF-41F0-8948-9EFA7C08712E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {904C8476-FCEF-41F0-8948-9EFA7C08712E}.Release|Any CPU.Build.0 = Release|Any CPU
{DA1D7ADE-452C-4369-83CC-56289176EACD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DA1D7ADE-452C-4369-83CC-56289176EACD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DA1D7ADE-452C-4369-83CC-56289176EACD}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -487,6 +499,7 @@ Global
{BF37BEA1-0816-4326-B1E0-E82290F8FCE0} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{ADC2372B-6FE0-421D-8277-BB628E8EFC22} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{596EAFC1-0496-495C-B382-D57415FA456A} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
+ {464F1120-A0DA-462B-B9E8-45176D883625} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{8AB91636-9055-4900-A72A-7CFFACDFDBF0} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{E6642255-667D-476B-B584-089AA5E6C0B1} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{A6D480BC-FDE8-4B92-A2A6-FF16BEE486AE} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
@@ -523,6 +536,7 @@ Global
{87A3F137-6DC3-4CE5-91E6-01797D076086} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{D3F63405-C0FA-4F83-8B79-E30BFF5FF5BF} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{4AC1088B-9965-4497-AC8E-570F1AD5631F} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
+ {904C8476-FCEF-41F0-8948-9EFA7C08712E} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{DA1D7ADE-452C-4369-83CC-56289176EACD} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{3E55CBE8-AFE8-426D-9470-49D63CD1051C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{56D0DCA5-567F-4B3B-8B79-CB108F8EB8A6} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
diff --git a/src/Testcontainers.Consul/Usings.cs b/src/Testcontainers.Consul/Usings.cs
index 49507dd3e..bf2829a65 100644
--- a/src/Testcontainers.Consul/Usings.cs
+++ b/src/Testcontainers.Consul/Usings.cs
@@ -1,5 +1,4 @@
global using System;
-global using System.Net;
global using Docker.DotNet.Models;
global using DotNet.Testcontainers.Builders;
global using DotNet.Testcontainers.Configurations;
diff --git a/src/Testcontainers.Papercut/.editorconfig b/src/Testcontainers.Papercut/.editorconfig
new file mode 100644
index 000000000..6f066619d
--- /dev/null
+++ b/src/Testcontainers.Papercut/.editorconfig
@@ -0,0 +1 @@
+root = true
\ No newline at end of file
diff --git a/src/Testcontainers.Papercut/PapercutBuilder.cs b/src/Testcontainers.Papercut/PapercutBuilder.cs
new file mode 100644
index 000000000..4a16972c2
--- /dev/null
+++ b/src/Testcontainers.Papercut/PapercutBuilder.cs
@@ -0,0 +1,69 @@
+namespace Testcontainers.Papercut;
+
+///
+[PublicAPI]
+public sealed class PapercutBuilder : ContainerBuilder
+{
+ public const string PapercutImage = "jijiechen/papercut:latest";
+
+ public const ushort HttpPort = 37408;
+
+ public const ushort SmtpPort = 25;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PapercutBuilder()
+ : this(new PapercutConfiguration())
+ {
+ DockerResourceConfiguration = Init().DockerResourceConfiguration;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ private PapercutBuilder(PapercutConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ DockerResourceConfiguration = resourceConfiguration;
+ }
+
+ ///
+ protected override PapercutConfiguration DockerResourceConfiguration { get; }
+
+ ///
+ public override PapercutContainer Build()
+ {
+ Validate();
+ return new PapercutContainer(DockerResourceConfiguration, TestcontainersSettings.Logger);
+ }
+
+ ///
+ protected override PapercutBuilder Init()
+ {
+ return base.Init()
+ .WithImage(PapercutImage)
+ .WithPortBinding(HttpPort, true)
+ .WithPortBinding(SmtpPort, true)
+ .WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(request => request.ForPort(HttpPort)));
+ }
+
+ ///
+ protected override PapercutBuilder Clone(IResourceConfiguration resourceConfiguration)
+ {
+ return Merge(DockerResourceConfiguration, new PapercutConfiguration(resourceConfiguration));
+ }
+
+ ///
+ protected override PapercutBuilder Clone(IContainerConfiguration resourceConfiguration)
+ {
+ return Merge(DockerResourceConfiguration, new PapercutConfiguration(resourceConfiguration));
+ }
+
+ ///
+ protected override PapercutBuilder Merge(PapercutConfiguration oldValue, PapercutConfiguration newValue)
+ {
+ return new PapercutBuilder(new PapercutConfiguration(oldValue, newValue));
+ }
+}
\ No newline at end of file
diff --git a/src/Testcontainers.Papercut/PapercutConfiguration.cs b/src/Testcontainers.Papercut/PapercutConfiguration.cs
new file mode 100644
index 000000000..6582a0276
--- /dev/null
+++ b/src/Testcontainers.Papercut/PapercutConfiguration.cs
@@ -0,0 +1,53 @@
+namespace Testcontainers.Papercut;
+
+///
+[PublicAPI]
+public sealed class PapercutConfiguration : ContainerConfiguration
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PapercutConfiguration()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public PapercutConfiguration(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 PapercutConfiguration(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 PapercutConfiguration(PapercutConfiguration resourceConfiguration)
+ : this(new PapercutConfiguration(), 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 PapercutConfiguration(PapercutConfiguration oldValue, PapercutConfiguration newValue)
+ : base(oldValue, newValue)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Testcontainers.Papercut/PapercutContainer.cs b/src/Testcontainers.Papercut/PapercutContainer.cs
new file mode 100644
index 000000000..e73e762e6
--- /dev/null
+++ b/src/Testcontainers.Papercut/PapercutContainer.cs
@@ -0,0 +1,30 @@
+namespace Testcontainers.Papercut;
+
+///
+[PublicAPI]
+public sealed class PapercutContainer : DockerContainer
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The container configuration.
+ /// The logger.
+ public PapercutContainer(PapercutConfiguration configuration, ILogger logger)
+ : base(configuration, logger)
+ {
+ }
+
+ ///
+ /// Gets the SMTP port.
+ ///
+ public ushort SmtpPort => GetMappedPublicPort(PapercutBuilder.SmtpPort);
+
+ ///
+ /// Gets the Papercut base address.
+ ///
+ /// The Papercut base address.
+ public string GetBaseAddress()
+ {
+ return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(PapercutBuilder.HttpPort)).ToString();
+ }
+}
\ No newline at end of file
diff --git a/src/Testcontainers.Papercut/Testcontainers.Papercut.csproj b/src/Testcontainers.Papercut/Testcontainers.Papercut.csproj
new file mode 100644
index 000000000..054b86339
--- /dev/null
+++ b/src/Testcontainers.Papercut/Testcontainers.Papercut.csproj
@@ -0,0 +1,16 @@
+
+
+ netstandard2.0;netstandard2.1
+ latest
+ Copyright (c) 2019 - 2023 Liam Wilson, Andre Hofmeister and other authors
+ Liam Wilson, Andre Hofmeister and contributors
+ A Testcontainers Papercut module for testing SMTP clients and sending emails.
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Testcontainers.Papercut/Usings.cs b/src/Testcontainers.Papercut/Usings.cs
new file mode 100644
index 000000000..bf2829a65
--- /dev/null
+++ b/src/Testcontainers.Papercut/Usings.cs
@@ -0,0 +1,7 @@
+global using System;
+global using Docker.DotNet.Models;
+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.Papercut.Tests/.editorconfig b/tests/Testcontainers.Papercut.Tests/.editorconfig
new file mode 100644
index 000000000..6f066619d
--- /dev/null
+++ b/tests/Testcontainers.Papercut.Tests/.editorconfig
@@ -0,0 +1 @@
+root = true
\ No newline at end of file
diff --git a/tests/Testcontainers.Papercut.Tests/PapercutContainerTest.cs b/tests/Testcontainers.Papercut.Tests/PapercutContainerTest.cs
new file mode 100644
index 000000000..6c51f803c
--- /dev/null
+++ b/tests/Testcontainers.Papercut.Tests/PapercutContainerTest.cs
@@ -0,0 +1,72 @@
+namespace Testcontainers.Papercut;
+
+public sealed class PapercutContainerTest : IAsyncLifetime
+{
+ private readonly PapercutContainer _papercutContainer = new PapercutBuilder().Build();
+
+ public Task InitializeAsync()
+ {
+ return _papercutContainer.StartAsync();
+ }
+
+ public Task DisposeAsync()
+ {
+ return _papercutContainer.DisposeAsync().AsTask();
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task ReceivesSentMessage()
+ {
+ // Given
+ const string subject = "Test";
+
+ Message[] messages;
+
+ using var httpClient = new HttpClient();
+ httpClient.BaseAddress = new Uri(_papercutContainer.GetBaseAddress());
+
+ using var smtpClient = new SmtpClient(_papercutContainer.Hostname, _papercutContainer.SmtpPort);
+
+ // When
+ smtpClient.Send("from@example.com", "to@example.com", subject, "A test message");
+
+ do
+ {
+ var messagesJson = await httpClient.GetStringAsync("/api/messages")
+ .ConfigureAwait(false);
+
+ var jsonDocument = JsonDocument.Parse(messagesJson);
+ messages = jsonDocument.RootElement.GetProperty("messages").Deserialize();
+ }
+ while (messages.Length == 0);
+
+ // Then
+ Assert.NotEmpty(messages);
+ Assert.Equal(subject, messages[0].Subject);
+ }
+
+ private readonly struct Message
+ {
+ [JsonConstructor]
+ public Message(string id, string subject, string size, DateTime createdAt)
+ {
+ Id = id;
+ Subject = subject;
+ Size = size;
+ CreatedAt = createdAt;
+ }
+
+ [JsonPropertyName("id")]
+ public string Id { get; }
+
+ [JsonPropertyName("subject")]
+ public string Subject { get; }
+
+ [JsonPropertyName("size")]
+ public string Size { get; }
+
+ [JsonPropertyName("createdAt")]
+ public DateTime CreatedAt { get; }
+ }
+}
\ No newline at end of file
diff --git a/tests/Testcontainers.Papercut.Tests/Testcontainers.Papercut.Tests.csproj b/tests/Testcontainers.Papercut.Tests/Testcontainers.Papercut.Tests.csproj
new file mode 100644
index 000000000..d42adb2ec
--- /dev/null
+++ b/tests/Testcontainers.Papercut.Tests/Testcontainers.Papercut.Tests.csproj
@@ -0,0 +1,17 @@
+
+
+ net6.0
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/Testcontainers.Papercut.Tests/Usings.cs b/tests/Testcontainers.Papercut.Tests/Usings.cs
new file mode 100644
index 000000000..763582fff
--- /dev/null
+++ b/tests/Testcontainers.Papercut.Tests/Usings.cs
@@ -0,0 +1,8 @@
+global using System;
+global using System.Net.Http;
+global using System.Net.Mail;
+global using System.Text.Json;
+global using System.Text.Json.Serialization;
+global using System.Threading.Tasks;
+global using DotNet.Testcontainers.Commons;
+global using Xunit;
\ No newline at end of file