> For the complete documentation index, see [llms.txt](https://brightercommand.gitbook.io/paramore-brighter-documentation/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://brightercommand.gitbook.io/paramore-brighter-documentation/scheduler/schedulingamessage.md).

# Scheduling a Message

This page shows you how to schedule a message or request for deferred execution, how to cancel one you have already scheduled, and how to configure each scheduler for the job.

> **How-to** · Applies to **Brighter V10** · Prerequisites: [Scheduler](/paramore-brighter-documentation/scheduler/brighterschedulersupport.md)

This page shows you how to schedule a message or request for deferred execution, how to cancel one you have already scheduled, and how to configure each scheduler for the job. For what scheduling is and which scheduler to pick, see [Scheduler](/paramore-brighter-documentation/scheduler/brighterschedulersupport.md).

## Message Scheduling Code Examples

### Basic Scheduling with DateTimeOffset

Schedule a command for a specific absolute time:

```csharp
using System;
using System.Threading.Tasks;
using Paramore.Brighter;

public class OrderService
{
    private readonly IAmACommandProcessor _commandProcessor;
    private readonly IOrderRepository _repository;

    public async Task CreateOrder(Order order)
    {
        // Save order
        await _repository.SaveAsync(order);

        // Schedule order processing for tomorrow at 9 AM
        var processTime = DateTime.UtcNow.Date.AddDays(1).AddHours(9);
        var schedulerId = await _commandProcessor.SendAsync(
            new DateTimeOffset(processTime),
            new ProcessOrderCommand { OrderId = order.Id }
        );

        // Store scheduler ID for potential cancellation
        order.ProcessSchedulerId = schedulerId;
    }
}
```

### Basic Scheduling with TimeSpan

Schedule a command with a relative delay:

```csharp
using System;
using System.Threading.Tasks;
using Paramore.Brighter;

public class RegistrationService
{
    private readonly IAmACommandProcessor _commandProcessor;
    private readonly IUserRepository _repository;

    public async Task RegisterUser(User user)
    {
        // Create user account
        await _repository.SaveAsync(user);

        // Send welcome email immediately
        await _commandProcessor.SendAsync(new SendWelcomeEmailCommand { UserId = user.Id });

        // Schedule reminder email for 24 hours later
        await _commandProcessor.SendAsync(
            TimeSpan.FromHours(24),
            new SendReminderEmailCommand { UserId = user.Id }
        );
    }
}
```

### Scheduling with Post for External Bus

Schedule a message to an external broker:

```csharp
using System.Threading.Tasks;
using Paramore.Brighter;

public class NotificationService
{
    private readonly IAmACommandProcessor _commandProcessor;

    public async Task<string> ScheduleNotification(NotificationRequest request)
    {
        // Schedule notification to be sent via external bus
        var schedulerId = await _commandProcessor.PostAsync(
            request.Delay,
            new NotificationEvent
            {
                UserId = request.UserId,
                Message = request.Message
            }
        );

        // Return scheduler ID for tracking
        return schedulerId;
    }
}
```

### Cancelling a Scheduled Message

Cancel a previously scheduled message:

```csharp
using System;
using System.Threading.Tasks;
using Paramore.Brighter;

public class OrderService
{
    private readonly IAmAMessageSchedulerAsync _scheduler;
    private readonly IOrderRepository _repository;

    public async Task CancelOrder(Guid orderId)
    {
        var order = await _repository.GetAsync(orderId);

        // Cancel the scheduled order processing
        if (!string.IsNullOrEmpty(order.ProcessSchedulerId))
        {
            await _scheduler.CancelAsync(order.ProcessSchedulerId);
        }

        // Mark order as cancelled
        order.Status = OrderStatus.Cancelled;
        await _repository.UpdateAsync(order);
    }
}
```

**Note:** Every scheduler supports cancellation. Before Brighter 10.8.0 the InMemory scheduler could not cancel or reschedule a request scheduled through the command processor: `CancelAsync` returned, and the request still ran when it fell due ([#4437](https://github.com/BrighterCommand/Brighter/issues/4437)). Among the schedulers, rescheduling is the operation that varies: the Azure Service Bus scheduler does not reschedule, so cancel the message and schedule it again instead. See [Choosing a Scheduler](/paramore-brighter-documentation/scheduler/brighterschedulersupport.md#choosing-a-scheduler).

### Retry with Exponential Backoff

Implement retry logic with increasing delays:

```csharp
using System;
using System.Threading.Tasks;
using Paramore.Brighter;

public class RetryService
{
    private readonly IAmACommandProcessor _commandProcessor;

    public async Task RetryWithBackoff(OperationCommand command, int attemptNumber)
    {
        // Calculate exponential backoff delay
        var delaySeconds = Math.Pow(2, attemptNumber); // 2^attempt seconds
        var maxDelay = TimeSpan.FromMinutes(30);
        var delay = TimeSpan.FromSeconds(Math.Min(delaySeconds, maxDelay.TotalSeconds));

        // Schedule retry
        command.AttemptNumber = attemptNumber + 1;
        await _commandProcessor.SendAsync(delay, command);
    }
}
```

### Using Requeue with Delay in a Handler

```csharp
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Paramore.Brighter;
using Paramore.Brighter.Actions;

public class ProcessPaymentHandlerAsync : RequestHandlerAsync<ProcessPaymentCommand>
{
    private const int MaxRetries = 3;
    private readonly IPaymentGateway _paymentGateway;
    private readonly ILogger<ProcessPaymentHandlerAsync> _logger;

    public override async Task<ProcessPaymentCommand> HandleAsync(
        ProcessPaymentCommand command,
        CancellationToken cancellationToken = default)
    {
        try
        {
            await _paymentGateway.ProcessAsync(command.PaymentId, cancellationToken);
            return await base.HandleAsync(command, cancellationToken);
        }
        catch (PaymentGatewayUnavailableException)
        {
            // Throw DeferMessageAction to requeue with configured delay
            // Subscription must have requeueCount and requeueDelay configured
            throw new DeferMessageAction();
        }
        catch (PaymentDeclinedException ex)
        {
            // Don't requeue for business logic failures
            _logger.LogWarning(ex, "Payment declined for {PaymentId}", command.PaymentId);
            return await base.HandleAsync(command, cancellationToken);
        }
    }
}
```

## Message Scheduling Configuration Examples

### Registering Handlers When You Schedule Requests

At Brighter 10.7.0, `AutoFromAssemblies()` and `AsyncHandlersFromAssemblies` register the scheduler's own handlers twice. When a request you scheduled with `SendAsync`, `PublishAsync` or `PostAsync` falls due, the command processor then throws `ArgumentException` (*"More than one handler was found for the typeof command Paramore.Brighter.Scheduler.Events.FireSchedulerRequest"*). With the InMemory scheduler that exception is unhandled, and it ends the process. Brighter 10.8.0 fixes this ([#4414](https://github.com/BrighterCommand/Brighter/issues/4414)), and the examples below, which use `AutoFromAssemblies()`, need that release.

On 10.7.0, register your async handlers explicitly if you schedule requests:

```csharp
using Microsoft.Extensions.DependencyInjection;
using Paramore.Brighter;
using Paramore.Brighter.Extensions.DependencyInjection;

services.AddBrighter(options =>
{
    options.HandlerLifetime = ServiceLifetime.Scoped;
})
.UseScheduler(new InMemorySchedulerFactory())
.AsyncHandlers(registry =>
{
    registry.RegisterAsync<ProcessOrderCommand, ProcessOrderHandlerAsync>();
});
```

### Configuring with Hangfire

```csharp
using Hangfire;
using Microsoft.Extensions.DependencyInjection;
using Paramore.Brighter.Extensions.DependencyInjection;
using Paramore.Brighter.MessageScheduler.Hangfire;

services.AddBrighter(options =>
{
    options.HandlerLifetime = ServiceLifetime.Scoped;
})
// Hangfire's own storage is configured with AddHangfire, not on the factory
.UseScheduler(provider =>
{
    // Resolve Hangfire's client first: that initialises its storage (Brighter#4600)
    var client = provider.GetRequiredService<IBackgroundJobClientV2>();
    return new HangfireMessageSchedulerFactory { Client = client };
})
.AutoFromAssemblies();
```

### Configuring with Quartz.NET

```csharp
using Microsoft.Extensions.DependencyInjection;
using Paramore.Brighter.Extensions.DependencyInjection;
using Paramore.Brighter.MessageScheduler.Quartz;
using Quartz;

services.AddBrighter(options =>
{
    options.HandlerLifetime = ServiceLifetime.Scoped;
})
// Quartz itself is configured with AddQuartz; the factory wraps its IScheduler
.UseScheduler(provider =>
{
    var schedulerFactory = provider.GetRequiredService<ISchedulerFactory>();
    var scheduler = schedulerFactory.GetScheduler().GetAwaiter().GetResult();
    return new QuartzSchedulerFactory(scheduler);
})
.AutoFromAssemblies();
```

### Configuring with InMemory (Development Only)

```csharp
using Microsoft.Extensions.DependencyInjection;
using Paramore.Brighter;
using Paramore.Brighter.Extensions.DependencyInjection;

services.AddBrighter(options =>
{
    options.HandlerLifetime = ServiceLifetime.Scoped;
})
.UseScheduler(new InMemorySchedulerFactory())
.AutoFromAssemblies();
```

## Further Reading

* [Scheduler](/paramore-brighter-documentation/scheduler/brighterschedulersupport.md) - What scheduling is, and choosing a scheduler
* [Switching Schedulers](/paramore-brighter-documentation/scheduler/switchingschedulers.md) - Moving from one scheduler to another
* [Custom Scheduler](/paramore-brighter-documentation/scheduler/customscheduler.md) - Implementing your own scheduler
* [Handler Failure](/paramore-brighter-documentation/using-an-external-bus/handlerfailure.md) - Error handling and retry strategies


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://brightercommand.gitbook.io/paramore-brighter-documentation/scheduler/schedulingamessage.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
