MassTransit + Hangfire scheduled redelivery


I was recently attempting to add scheduled redelivery to my ASP.NET Core project using MassTransit. I found the docs for this a little lacklustre, so I wanted to take the opportunity to write this up for my own future benefit as much as anything else. I also spent three days down a particular rabbit hole so hopefully this helps somebody else who has the same issue in the future 😅

Why Scheduled Redelivery?

Within our system we have an account wide aggregate root, so when multiple messages from the same account are processed concurrently it can cause contention on that entity for longer than the configured retry window. When this occurs we needed to reschedule the unprocessed message for a minute or two down the line, once some of that contention had cleared. The easiest way to do this would have been to use the RabbitMQ Delayed Message Plugin at the transport level however we use AmazonMQ and this isn’t supported 🤦 So, time to explore scheduled redelivery instead.

Using Hangfire

MassTransit supports various methods for scheduling, but as we were already using Hangfire for other scheduled tasks in the project it was the obvious choice. Setting up Hangfire is super easy - simply include the following in your Program.cs.

builder.Services.AddHangfire(cfg => cfg.UseInMemoryStorage());
builder.Services.AddHangfireServer();

app.UseHangfireDashboard();

Add Scheduled Redelivery to MassTransit

This part is again easy (three day rabbit hole notwithstanding - I’ll come onto that!) It should have been as simple as adding the following to our MassTransit config.

builder.Services.AddMassTransit(mt =>
{
    mt.AddPublishMessageScheduler();
    mt.UsingRabbitMq((context, cfg) =>
    {
        cfg.UseHangfireScheduler();
        cfg.UseScheduledRedelivery(r =>
        {
            r.Intervals(TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(2), TimeSpan.FromMinutes(3));
        });
    });
});

Checking the Hangfire dashboard, and we’ve got three scheduled redeliveries - one after 1 minute, one two minutes later and a final one three minutes after that ✅

The Rabbit(MQ) Hole 🐰🕳️

So, it should have been as simple as that. However, when we tried to set this up we found that the crucial message header to make the above work, MT-Redelivery-Count was missing. Honestly, at this point we assumed there was a bug in MassTransit and spent way too long trying to work around it. As it happens, the only reason that was happening was because of a particular MassTransit setting which had been copied over from another project.

cfg.UseRawJsonSerializer();

We had this in place because in another project we were consuming messages which hadn’t been produced by MassTransit and were coming from a node.js project. As per the MassTransit docs when you use the raw json serializer the message is no longer wrapped in an envelope and therefore the redelivery context was lost.

We simply removed that setting and everything worked as expected.

Hope this helps anybody else that finds themselves down the same rabbit hole! Here is a full example configuration for anybody looking to set this up from scratch.

Get in touch