Skip to content

MassTransit

Messaging & StreamingMessaging/QueueC#

What it is

MassTransit is a distributed application framework for .NET, abstracting over RabbitMQ, Azure Service Bus, Amazon SQS and Kafka with sagas and routing built in.

Define message contracts as records and consumers as classes. MassTransit handles serialisation, topology, retries and dead-lettering.

Installation

dotnet add package MassTransit

Getting started

The smallest useful thing you can do with it, and what each part means.

Publish and consume
public record OrderSubmitted(Guid OrderId, decimal Total);

public class OrderSubmittedConsumer : IConsumer<OrderSubmitted>
{
    public async Task Consume(ConsumeContext<OrderSubmitted> context)
    {
        await _inventory.ReserveAsync(context.Message.OrderId);
        // Publishing from the consumer participates in the same transaction
        // when the outbox is enabled.
        await context.Publish(new InventoryReserved(context.Message.OrderId));
    }
}

services.AddMassTransit(x =>
{
    x.AddConsumer<OrderSubmittedConsumer>();
    x.UsingRabbitMq((ctx, cfg) =>
    {
        cfg.Host("localhost", "/", h => { h.Username("guest"); h.Password("guest"); });
        cfg.ConfigureEndpoints(ctx);   // convention-based queue naming
    });
});
ConfigureEndpoints derives queue names and bindings from the consumers, so the broker topology is created for you rather than configured by hand.

Advanced usage

Where the library earns its place over a simpler alternative.

Sagas and the transactional outbox
public class OrderState : SagaStateMachineInstance
{
    public Guid CorrelationId { get; set; }
    public string CurrentState { get; set; } = default!;
}

public class OrderStateMachine : MassTransitStateMachine<OrderState>
{
    public OrderStateMachine()
    {
        Initially(When(Submitted)
            .TransitionTo(AwaitingPayment));

        During(AwaitingPayment,
            When(PaymentReceived).TransitionTo(Paid).Publish(ctx => new OrderPaid(ctx.Saga.CorrelationId)),
            When(PaymentFailed).TransitionTo(Cancelled));
    }
}

// Outbox: publish only if the database transaction commits.
x.AddEntityFrameworkOutbox<AppDb>(o => { o.UsePostgres(); o.UseBusOutbox(); });
The outbox solves the dual-write problem: without it, a crash between committing the database and publishing the message leaves the two permanently inconsistent.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Messages land in the _error queue
The consumer threw after exhausting retries. Inspect the fault headers, fix the cause, and requeue from the error queue.
Messages are published but never consumed
A topology mismatch — usually the consumer was not registered before ConfigureEndpoints, so no queue was bound.

Best practices

  • Enable the transactional outbox whenever a consumer both writes to a database and publishes.
  • Make consumers idempotent; at-least-once delivery means redelivery will happen.
  • Define messages as records in a shared contracts assembly.
  • Check licensing before upgrading — v9 is commercial, v8 is open source.

Background

Why it exists, and what it was reacting to.

MassTransit lets you write message consumers once and switch transports by configuration, and adds the patterns — sagas, outbox, retries — that raw broker clients leave to you.