Stop polling background jobs

Introduction

In many applications, we need to run background work at a specific time in the future, generating reports, sending reminders, cleaning up data, etc.

Let’s say we have a use case where we want to notify a downstream service after some time based on the actions the users performed earlier. For example, a user has signed up but hasn’t performed KYC yet and you want to remind them after a certain period. Or a user forgot to update the status of their work item and you want to send a gentle nudge.

The traditional approach is often a polling job (e.g., a BackgroundService or Hangfire recurring job) that wakes up regularly and checks for work. While simple, this creates unnecessary load and makes the system harder to debug.

Rebus offers a much more elegant solution: deferred messages. You publish an event or command now, and Rebus delivers it exactly when you want.

In this article, I’ll show you how I replaced polling with deferred events using Rebus, the benefits, the setup, and important gotchas.

Architecture Diagram

Screenshot_19-7-2026_20629_

Why Deferred Messages Beat Polling

Prerequisites

Setting Up Rebus (Quick Recap)

services.AddRebus(configure => configure
    .Transport(t => t.UseRabbitMq("amqp://...", "your-input-queue"))
    .Routing(r => r.TypeBased().MapAssemblyOf<YourMessage>())
    // Optional but recommended
    .Timeouts(t => t.UseSqlServer(connectionString, "RebusTimeouts")) // or StoreInMemory
    .Outbox(o => o.UseSqlServer(connectionString, "RebusOutbox"))
);

Publishing a Deferred Message

This is the core feature:

public class ReminderService
{
    private readonly IBus _bus;

    public ReminderService(IBus bus)
    {
        _bus = bus;
    }

    public async Task ScheduleReminderAsync(Guid userId, string reminderType, TimeSpan remindAfter)
    {
        var command = new SendReminderCommand
        {
            UserId = userId,
            ReminderType = reminderType, // e.g., "KYC", "StatusUpdate"
            OriginalActionDate = DateTimeOffset.UtcNow
        };

        // Defer the message until the desired time
        await _bus.Defer(remindAfter, command);
    }
}

The Command and Handler

public record SendReminderCommand
{
    public Guid UserId { get; init; }
    public string ReminderType { get; init; }
    public DateTimeOffset OriginalActionDate { get; init; }
}

public class SendReminderCommandHandler : IHandleMessages<SendReminderCommand>
{
    private readonly INotificationService _notificationService;
    private readonly IBus _bus;

    public SendReminderCommandHandler(INotificationService notificationService, IBus bus)
    {
        _notificationService = notificationService;
        _bus = bus;
    }

    public async Task Handle(SendReminderCommand message)
    {
        try
        {
            await _notificationService.SendReminderAsync(
                message.UserId, 
                message.ReminderType);

            // Optionally publish a completion event
            await _bus.Publish(new ReminderSentEvent
            {
                UserId = message.UserId,
                ReminderType = message.ReminderType,
                SentAt = DateTimeOffset.UtcNow
            });
            
            // OR schedule another reminder for the next timeout
        }
        catch (Exception ex)
        {
            // Rebus will retry based on your configuration
            throw;
        }
    }
}

Example Usage in Controller

[HttpPost("signup")]
public async Task<IActionResult> Signup(...)
{
    // After successful signup
    await _reminderService.ScheduleReminderAsync(
        userId: newUser.Id,
        reminderType: "KYC",
        remindAfter: TimeSpan.FromDays(7));

    return Ok();
}

Key Considerations

Conclusion

Switching from polling jobs to Rebus deferred messages for user reminders has made my background processing much cleaner and more maintainable. It fits naturally into event-driven architecture and reduces operational noise significantly.

Have you used deferred messaging for reminders or notifications? What patterns work best in your applications?

I’d love to hear your thoughts in the comments.