Skip to content

Installation

Packages

Emit is split into focused packages so you only take what you need.

PackageNuGetPurpose
EmitNuGetCore library (required)
Emit.KafkaNuGetKafka producers and consumers
Emit.MongoDBNuGetMongoDB outbox and distributed lock persistence
Emit.EntityFrameworkCoreNuGetEF Core outbox and distributed lock persistence (PostgreSQL)
Emit.MediatorNuGetIn-process request/response mediator
Emit.OpenTelemetryNuGetMetrics and distributed tracing integration
Emit.Kafka.JsonSerializerNuGetJSON Schema serializer for Kafka topics
Emit.Kafka.AvroSerializerNuGetAvro serializer for Kafka topics
Emit.Kafka.ProtobufSerializerNuGetProtobuf serializer for Kafka topics
Emit.Kafka.HealthChecksNuGetASP.NET Core health check for Kafka broker connectivity
Emit.MongoDB.HealthChecksNuGetASP.NET Core health check for MongoDB connectivity
Emit.EntityFrameworkCore.HealthChecksNuGetASP.NET Core health check for PostgreSQL connectivity
Emit.FluentValidationNuGetFluentValidation integration for consumer validation
Emit.TestingNuGetTest utilities: MessageSink<T>, SinkConsumer<T>

For a typical Kafka + MongoDB setup:

Terminal window
dotnet add package Emit
dotnet add package Emit.Kafka
dotnet add package Emit.MongoDB

Registering Emit

Everything flows through AddEmit. Call it once in your service registration and chain the providers you need:

Program.cs
builder.Services.AddEmit(emit =>
{
emit.AddMongoDb(mongo =>
{
mongo.Configure((sp, ctx) =>
{
ctx.Client = sp.GetRequiredService<IMongoClient>();
ctx.Database = ctx.Client.GetDatabase("my-db");
})
.UseOutbox()
.UseDistributedLock();
});
emit.AddKafka(kafka =>
{
kafka.ConfigureClient(cfg =>
{
cfg.BootstrapServers = "localhost:9092";
});
kafka.Topic<string, OrderPlaced>("orders", topic =>
{
topic.Producer();
topic.ConsumerGroup("order-processor", group =>
{
group.AddConsumer<OrderPlacedConsumer>();
});
});
});
});

AddEmit returns IServiceCollection, so it chains normally with the rest of your registration.

UseOutbox() and UseDistributedLock() are both opt-in. Call UseOutbox() to enable the transactional outbox infrastructure: when active, all producers route through the outbox by default, giving you atomic writes and guaranteed delivery. To bypass the outbox for a specific producer, call UseDirect() on that producer. You can mix outbox and direct producers freely.

UseDistributedLock() is independent of the outbox. You can use Emit purely as a broker integration layer without any persistence package at all.

What gets registered

Emit registers its own background services automatically: the outbox worker, leader election, and offset committer are all BackgroundService implementations managed by the host lifecycle. You don’t start them manually.

All configuration is validated at startup via IValidateOptions<T> with ValidateOnStart(). Misconfiguration surfaces when the application starts, not during a production incident.

DI lifetimes

IEventProducer<TKey, TValue> is registered as scoped. Inject it into controllers, endpoint handlers, or any scoped service. If you need it from a singleton, inject IServiceScopeFactory and create a scope per operation.

See the Quickstart for a full working example.