> 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

> **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
// ...
public class OrderService
{
    private readonly IAmACommandProcessor _commandProcessor;

    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
// ...
public class RegistrationService
{
    private readonly IAmACommandProcessor _commandProcessor;

    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
// ...
public class NotificationService
{
    private readonly IAmACommandProcessor _commandProcessor;

    public async Task 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
// ...
public class OrderService
{
    private readonly IMessageScheduler _scheduler;

    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. 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
// ...
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
        await _commandProcessor.SendAsync(
            delay,
            command with { AttemptNumber = attemptNumber + 1 }
        );
    }
}
```

### Using Requeue with Delay in a Handler

```csharp
// ...
public class ProcessPaymentHandlerAsync : RequestHandlerAsync<ProcessPaymentCommand>
{
    private const int MaxRetries = 3;

    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 requeueDelayInMilliseconds 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

### Configuring with Hangfire

```csharp
// ...
services.AddBrighter(options =>
{
    options.HandlerLifetime = ServiceLifetime.Scoped;
})
.UseScheduler(
    scheduler: new HangfireMessageSchedulerFactory(
        connectionString: Configuration.GetConnectionString("Hangfire")
    )
)
.AutoFromAssemblies();
```

### Configuring with Quartz.NET

```csharp
// ...
services.AddBrighter(options =>
{
    options.HandlerLifetime = ServiceLifetime.Scoped;
})
.UseScheduler(
    scheduler: new QuartzMessageSchedulerFactory(
        configuration: Configuration.GetSection("Quartz")
    )
)
.AutoFromAssemblies();
```

### Configuring with InMemory (Development Only)

```csharp
// ...
services.AddBrighter(options =>
{
    options.HandlerLifetime = ServiceLifetime.Scoped;
})
.UseScheduler(
    scheduler: 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 dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

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

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
