diff --git a/CrowdParlay.Communication.sln b/CrowdParlay.Communication.sln
index 6a2fd00..0e33e1b 100644
--- a/CrowdParlay.Communication.sln
+++ b/CrowdParlay.Communication.sln
@@ -2,11 +2,20 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{6397F4A6-059A-4591-AB2F-9E0D28F8E619}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrowdParlay.Communication", "src\CrowdParlay.Communication\CrowdParlay.Communication.csproj", "{2A29A80A-0CB5-4914-B4D4-9595E667F874}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {2A29A80A-0CB5-4914-B4D4-9595E667F874}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2A29A80A-0CB5-4914-B4D4-9595E667F874}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2A29A80A-0CB5-4914-B4D4-9595E667F874}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2A29A80A-0CB5-4914-B4D4-9595E667F874}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {2A29A80A-0CB5-4914-B4D4-9595E667F874} = {6397F4A6-059A-4591-AB2F-9E0D28F8E619}
EndGlobalSection
EndGlobal
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..59876b4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,38 @@
+# Crowd Parlay's .NET *communication* tools
+
+### Technologies
+`C#` `.NET 7` `MassTransit` `RabbitMQ`
+
+### Responsibilities
+- Platform-wide messaging types
+- Common MassTransit configuration
+- RabbitMQ topology maintenance
+
+### Integration
+Given `MassTransit.RabbitMQ` NuGet package and `IServiceCollection services` instance, `CrowdParlay.Communication` gets integrated as follows:
+
+```csharp
+using CrowdParlay.Communication;
+using MassTransit;
+```
+
+```csharp
+services.AddMassTransit(bus => bus.UsingRabbitMq((context, configurator) =>
+{
+ var amqpServerUrl =
+ configuration["RABBITMQ_AMQP_SERVER_URL"]
+ ?? throw new InvalidOperationException("Missing required configuration 'RABBITMQ_AMQP_SERVER_URL'.");
+
+ configurator.Host(amqpServerUrl);
+ configurator.ConfigureEndpoints(context);
+ configurator.ConfigureTopology(); // This extension method is defined in CrowdParlay.Communication
+}));
+```
+
+Now, inject `IPublishEndpoint _broker` to publish messages like that:
+
+```csharp
+// UserCreatedEvent type is defined in CrowdParlay.Communication
+var @event = new UserCreatedEvent(user.Id.ToString(), user.Username, user.DisplayName, user.AvatarUrl);
+await _broker.Publish(@event, cancellationToken);
+```
diff --git a/docs/logo.png b/docs/logo.png
new file mode 100644
index 0000000..0db373a
Binary files /dev/null and b/docs/logo.png differ
diff --git a/src/CrowdParlay.Communication/CrowdParlay.Communication.csproj b/src/CrowdParlay.Communication/CrowdParlay.Communication.csproj
new file mode 100644
index 0000000..fcd2659
--- /dev/null
+++ b/src/CrowdParlay.Communication/CrowdParlay.Communication.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net9.0
+ enable
+ enable
+ CrowdParlay
+ 2.0.2
+ CrowdParlay's common .NET API for platform-wide communication.
+ true
+ https://gitlab.otter.su/crowdparlay/dotnet-communication
+ logo.png
+
+
+
+
+ True
+
+ logo.png
+
+
+
+
+
+
+
+
diff --git a/src/CrowdParlay.Communication/MassTransitExtensions.cs b/src/CrowdParlay.Communication/MassTransitExtensions.cs
new file mode 100644
index 0000000..e17484d
--- /dev/null
+++ b/src/CrowdParlay.Communication/MassTransitExtensions.cs
@@ -0,0 +1,18 @@
+using MassTransit;
+
+namespace CrowdParlay.Communication;
+
+public static class MassTransitExtensions
+{
+ public static void ConfigureTopology(this IRabbitMqBusFactoryConfigurator configurator)
+ {
+ configurator.Message(x => x.SetEntityName("user"));
+ configurator.Send(x => x.UseRoutingKeyFormatter(_ => "user.created"));
+
+ configurator.Message(x => x.SetEntityName("user"));
+ configurator.Send(x => x.UseRoutingKeyFormatter(_ => "user.updated"));
+
+ configurator.Message(x => x.SetEntityName("user"));
+ configurator.Send(x => x.UseRoutingKeyFormatter(_ => "user.deleted"));
+ }
+}
\ No newline at end of file
diff --git a/src/CrowdParlay.Communication/Messages.cs b/src/CrowdParlay.Communication/Messages.cs
new file mode 100644
index 0000000..1b3671b
--- /dev/null
+++ b/src/CrowdParlay.Communication/Messages.cs
@@ -0,0 +1,5 @@
+namespace CrowdParlay.Communication;
+
+public record UserCreatedEvent(string UserId, string Username, string DisplayName, string? AvatarUrl);
+public record UserUpdatedEvent(string UserId, string Username, string DisplayName, string? AvatarUrl);
+public record UserDeletedEvent(string UserId);
\ No newline at end of file