Skip to content

Quartz.NET Configuration

alt MassTransit on NuGet

MassTransit supports Quartz.NET as a message scheduler, providing enterprise-grade scheduled message delivery with persistent storage. Quartz.NET is particularly useful when you need:

  • Enterprise-grade reliability with database-backed storage
  • Clustering support for high availability and scalability
  • Complex job scheduling with cron expressions
  • Fine-grained control over thread pools and job execution
  • Integration with existing Quartz.NET installations

Install the required packages:

Terminal window
dotnet add package MassTransit.Quartz

For development or simple deployments:

services.AddQuartz();
services.AddMassTransit(x =>
{
x.AddPublishMessageScheduler();
x.AddQuartzConsumers();
x.UsingRabbitMq((context, cfg) =>
{
cfg.UsePublishMessageScheduler();
cfg.ConfigureEndpoints(context);
});
});

For production deployments, use persistent storage with SQL Server:

services.AddQuartz(q =>
{
q.SchedulerName = "MassTransit-Scheduler";
q.SchedulerId = "AUTO";
q.UseDefaultThreadPool(tp =>
{
tp.MaxConcurrency = 10;
});
q.UsePersistentStore(s =>
{
s.UseProperties = true;
s.RetryInterval = TimeSpan.FromSeconds(15);
s.UseSqlServer(connectionString);
});
});
services.AddMassTransit(x =>
{
x.AddPublishMessageScheduler();
x.AddQuartzConsumers();
x.UsingRabbitMq((context, cfg) =>
{
cfg.UsePublishMessageScheduler();
cfg.ConfigureEndpoints(context);
});
});

The key components are:

  1. AddPublishMessageScheduler() - Registers the message scheduler in the container
  2. AddQuartzConsumers() - Adds the Quartz consumers that handle scheduling operations
  3. UsePublishMessageScheduler() - Configures the transport to use the scheduler

Inject ConsumeContext and use the scheduling methods:

public class OrderProcessingConsumer :
IConsumer<ProcessOrder>
{
public async Task Consume(ConsumeContext<ProcessOrder> context)
{
// Schedule a message to be sent after a delay
await context.ScheduleSend<SendNotification>(
DateTime.UtcNow + TimeSpan.FromHours(1),
new NotificationMessage
{
OrderId = context.Message.OrderId,
Message = "Your order has been processed"
});
}
}

Inject IMessageScheduler from the container:

public class NotificationService
{
private readonly IMessageScheduler _scheduler;
public NotificationService(IMessageScheduler scheduler)
{
_scheduler = scheduler;
}
public async Task SendDelayedNotification(string email, string message)
{
await _scheduler.SchedulePublish(
DateTime.UtcNow.AddDays(1),
new NotificationMessage { Email = email, Message = message });
}
}
  • ScheduleSend - Sends directly to a specific endpoint (useful for targeting a specific queue)
  • SchedulePublish - Publishes to the message bus (useful for pub/sub scenarios)
// Send to a specific endpoint
await context.ScheduleSend(
new Uri("queue:notification-service"),
delay,
message);
// Publish for subscribers
await context.SchedulePublish(delay, message);

Quartz.NET supports recurring jobs using cron expressions:

public class DailyReportSchedule :
DefaultRecurringSchedule
{
public DailyReportSchedule()
{
ScheduleId = "daily-report";
CronExpression = "0 0 9 * * ?"; // Every day at 9 AM
}
}
public record GenerateDailyReport;

To schedule a recurring message:

public class ReportSchedulerService
{
private readonly IRecurringMessageScheduler _scheduler;
public ReportSchedulerService(IRecurringMessageScheduler scheduler)
{
_scheduler = scheduler;
}
public async Task ScheduleDailyReports()
{
var inputAddress = new Uri("queue:report-generator");
await _scheduler.ScheduleRecurringSend(
inputAddress,
new DailyReportSchedule(),
new GenerateDailyReport());
}
}

To cancel a recurring schedule:

await _scheduler.CancelScheduledRecurringMessage("daily-report", null);

Quartz.NET supports clustering for high availability and scalability. When clustered:

  • Multiple scheduler instances share the same database
  • Jobs are load-balanced across nodes
  • Automatic fail-over when a node goes down
services.AddQuartz(q =>
{
q.SchedulerName = "MassTransit-Scheduler";
q.SchedulerId = "AUTO";
q.UseDefaultThreadPool(tp =>
{
tp.MaxConcurrency = 10;
});
q.UsePersistentStore(s =>
{
s.UseProperties = true;
s.PerformSchemaValidation = true;
s.RetryInterval = TimeSpan.FromSeconds(15);
s.UseSqlServer(connectionString);
s.UseClustering(c =>
{
c.CheckinMisfireThreshold = TimeSpan.FromSeconds(20);
c.CheckinInterval = TimeSpan.FromSeconds(10);
});
});
});

Quartz.NET can be used for scheduled message redelivery (second-level retries):

services.AddMassTransit(x =>
{
x.AddConsumer<OrderProcessingConsumer>();
x.AddConfigureEndpointsCallback((context, name, cfg) =>
{
cfg.UseScheduledRedelivery(r => r
.Intervals(
TimeSpan.FromMinutes(5),
TimeSpan.FromMinutes(15),
TimeSpan.FromMinutes(30)));
cfg.UseMessageRetry(r => r.Immediate(3));
});
x.UsingRabbitMq((context, cfg) =>
{
cfg.UsePublishMessageScheduler();
cfg.ConfigureEndpoints(context);
});
});
Program.cs
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("quartz")
?? throw new InvalidOperationException("Connection string 'quartz' is not configured.");
// Configure Quartz with SQL Server and clustering
builder.Services.AddQuartz(q =>
{
q.SchedulerName = "MassTransit-Scheduler";
q.SchedulerId = "AUTO";
q.UseDefaultThreadPool(tp =>
{
tp.MaxConcurrency = 10;
});
q.UsePersistentStore(s =>
{
s.UseProperties = true;
s.RetryInterval = TimeSpan.FromSeconds(15);
s.UseSqlServer(connectionString);
s.UseClustering(c =>
{
c.CheckinMisfireThreshold = TimeSpan.FromSeconds(20);
c.CheckinInterval = TimeSpan.FromSeconds(10);
});
});
});
// Add MassTransit with Quartz
builder.Services.AddMassTransit(x =>
{
x.AddPublishMessageScheduler();
x.AddConsumers(typeof(Program).Assembly);
x.AddQuartzConsumers();
x.UsingRabbitMq((context, cfg) =>
{
cfg.Host(builder.Configuration.GetConnectionString("RabbitMq"));
cfg.UsePublishMessageScheduler();
cfg.ConfigureEndpoints(context);
});
});
// Start Quartz scheduler
builder.Services.AddQuartzHostedService(options =>
{
options.StartDelay = TimeSpan.FromSeconds(5);
options.WaitForJobsToComplete = true;
});
var app = builder.Build();
app.UseRouting();
app.UseAuthorization();
app.MapControllers();
app.MapHealthChecks("/health/ready");
app.Run();

appsettings.json:

{
"ConnectionStrings": {
"quartz": "Server=.;Database=Quartz;Trusted_Connection=True;TrustServerCertificate=true",
"RabbitMq": "rabbitmq://localhost:5672"
}
}