# Aws Scheduler

**AWS EventBridge Scheduler** is a fully managed, serverless service on AWS that allows you to create, run, and manage scheduled tasks at scale. Brighter integrates with AWS EventBridge Scheduler to provide cloud-native scheduling for AWS workloads.

## When to Use AWS Scheduler

AWS Scheduler is recommended when:

* **Running on AWS**: Your application is deployed on AWS (EC2, ECS, Lambda, etc.)
* **Serverless Architecture**: You prefer managed services over self-hosted solutions
* **AWS Native**: You want to leverage AWS services directly
* **Scalability**: You need to handle millions of scheduled messages
* **Cost Optimization**: You want to pay per use without managing infrastructure
* **Multi-Region**: You need to schedule across AWS regions

**When NOT to use:**

* **Multi-Cloud**: Need scheduler to work across cloud providers
* **On-Premises**: Application runs outside AWS
* **Complex Scheduling**: Need advanced scheduling features (job chains, dependencies)
* **Dashboard**: Need visual management interface

## How Brighter Integrates with AWS Scheduler

Brighter provides two approaches for scheduling with AWS EventBridge Scheduler:

### 1. Direct to Target (Recommended)

When `UseMessageTopicAsTarget = true` (default), Brighter schedules messages directly to the target SNS topic or SQS queue:

```
Your Code → CommandProcessor.SendAsync(delay, command)
    ↓
Brighter creates AWS EventBridge Schedule
    ↓
Schedule stored in AWS EventBridge Scheduler
    ↓
[Schedule fires at specified time]
    ↓
AWS EventBridge → Directly publishes to target SNS/SQS
    ↓
Your Dispatcher → Handler executes
```

**Benefits**:

* Fewer moving parts
* Lower latency (no intermediate handler)
* Simpler architecture

### 2. Via FireAwsScheduler Message

When `UseMessageTopicAsTarget = false` or using request scheduler, Brighter schedules through an intermediate `FireAwsScheduler` message:

```
Your Code → CommandProcessor.SendAsync(delay, command)
    ↓
Brighter creates AWS EventBridge Schedule
    ↓
Schedule stored in AWS EventBridge Scheduler
    ↓
[Schedule fires at specified time]
    ↓
AWS EventBridge → Publishes FireAwsScheduler to scheduler topic/queue
    ↓
AwsSchedulerFiredHandler executes
    ↓
CommandProcessor dispatches original command
    ↓
Your Handler executes
```

**Use when**:

* Request scheduling (Send/Publish commands, not messages)
* Target topic doesn't exist at scheduling time
* Need centralized scheduler message handling

## IAM Role Requirements

AWS EventBridge Scheduler requires an IAM role that allows it to publish to SNS topics and send messages to SQS queues.

### Trust Policy

The role must trust the EventBridge Scheduler service:

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "scheduler.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
```

### Permissions Policy

The role must have permissions to publish messages:

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sqs:SendMessage",
                "sns:Publish"
            ],
            "Resource": ["*"]
        }
    ]
}
```

**Best Practice**: Limit `Resource` to specific topic/queue ARNs instead of `"*"`.

### Automatic Role Creation

Brighter can create the role automatically if it doesn't exist:

```csharp
var schedulerFactory = new AwsSchedulerFactory(awsConnection, "my-scheduler-role")
{
    MakeRole = OnMissingRole.Create  // Brighter will create role if missing
};
```

**Options**:

* `OnMissingRole.Assume` (default): Assume role exists, throw error if not found
* `OnMissingRole.Create`: Create role if it doesn't exist

## NuGet Packages

AWS Scheduler integration is available in two versions:

### AWS SDK v4 (Recommended)

```bash
dotnet add package Paramore.Brighter.MessageScheduler.AWS.V4
```

Uses the latest AWS SDK for .NET v4.

### AWS SDK v3 (Legacy)

```bash
dotnet add package Paramore.Brighter.MessageScheduler.AWS
```

Uses AWS SDK for .NET v3 (deprecated in AWS).

**Recommendation**: Use V4 for new projects. Both packages provide identical Brighter functionality.

## Configuration

### Basic Configuration

Configure AWS Scheduler with minimal settings:

```csharp
using Microsoft.Extensions.Hosting;
using Paramore.Brighter.Extensions.DependencyInjection;
using Paramore.Brighter.MessageScheduler.AWS.V4;
using Paramore.Brighter.MessagingGateway.AWSSQS;

var builder = WebApplication.CreateBuilder(args);

// Create AWS connection
var awsConnection = new AWSMessagingGatewayConnection(
    credentials,  // From AWS credentials provider
    RegionEndpoint.USEast1
);

// Configure Brighter with AWS Scheduler
builder.Services.AddBrighter(options =>
{
    options.HandlerLifetime = ServiceLifetime.Scoped;
})
.UseScheduler(new AwsSchedulerFactory(awsConnection, "my-scheduler-role")
{
    // Schedule topic for FireAwsScheduler messages (when not direct to target)
    SchedulerTopicOrQueue = new RoutingKey("message-scheduler-topic"),
    OnConflict = OnSchedulerConflict.Overwrite
})
.AutoFromAssemblies();

var app = builder.Build();
```

### Configuration with Dispatcher (FireAwsScheduler Handler)

If using `FireAwsScheduler` approach, configure the Dispatcher to handle scheduler messages:

```csharp
using Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection;
using Paramore.Brighter.MessagingGateway.AWSSQS.V4;

var builder = WebApplication.CreateBuilder(args);

var awsConnection = new AWSMessagingGatewayConnection(credentials, RegionEndpoint.USEast1);

// Configure producer registry (includes scheduler topic)
var producerRegistry = new SnsProducerRegistryFactory(
    awsConnection,
    [
        new SnsPublication
        {
            Topic = new RoutingKey("message-scheduler-topic"),
            RequestType = typeof(FireAwsScheduler)
        }
    ]
).Create();

// Configure subscriptions (handle FireAwsScheduler messages)
var subscriptions = new[]
{
    new SqsSubscription<FireAwsScheduler>(
        new SubscriptionName("scheduler-message-subscription"),
        new ChannelName("scheduler-message-channel"),
        new RoutingKey("message-scheduler-topic"),
        bufferSize: 10,
        timeOut: TimeSpan.FromSeconds(30),
        messagePumpType: MessagePumpType.Proactor
    )
};

builder.Services.AddServiceActivator(options =>
{
    options.Subscriptions = subscriptions;
})
.UseExternalBus(configure =>
{
    configure.ProducerRegistry = producerRegistry;
})
.UseScheduler(new AwsSchedulerFactory(awsConnection, "my-scheduler-role")
{
    SchedulerTopicOrQueue = new RoutingKey("message-scheduler-topic"),
    UseMessageTopicAsTarget = false,  // Use FireAwsScheduler approach
    OnConflict = OnSchedulerConflict.Overwrite
})
.AutoFromAssemblies();

var app = builder.Build();
```

### Configuration with Scheduler Groups

AWS EventBridge Scheduler supports organizing schedules into groups:

```csharp
var schedulerFactory = new AwsSchedulerFactory(awsConnection, "my-scheduler-role")
{
    Group = new SchedulerGroup
    {
        Name = "orders-scheduler-group",
        Tags = new List<Tag>
        {
            new Tag { Key = "Environment", Value = "Production" },
            new Tag { Key = "Application", Value = "OrderProcessing" }
        },
        MakeSchedulerGroup = OnMissingSchedulerGroup.Create  // Create group if missing
    }
};
```

**Options**:

* `OnMissingSchedulerGroup.Assume` (default): Assume group exists
* `OnMissingSchedulerGroup.Create`: Create group if it doesn't exist

### Configuration with Flexible Time Window

EventBridge Scheduler supports flexible time windows for cost optimization:

```csharp
var schedulerFactory = new AwsSchedulerFactory(awsConnection, "my-scheduler-role")
{
    FlexibleTimeWindowMinutes = 5  // Execute within 5-minute window
};
```

A flexible time window allows AWS to batch executions for better efficiency. The schedule will execute within the specified window after the scheduled time.

### Configuration with Custom Scheduler ID

Customize how Brighter generates scheduler IDs for messages and requests:

```csharp
var schedulerFactory = new AwsSchedulerFactory(awsConnection, "my-scheduler-role")
{
    // Custom scheduler ID for messages
    GetOrCreateMessageSchedulerId = message => $"msg-{message.Id}",

    // Custom scheduler ID for requests (idempotency)
    GetOrCreateRequestSchedulerId = request =>
    {
        if (request is ProcessOrderCommand cmd)
        {
            return $"order-{cmd.OrderId}";  // Idempotent per order
        }
        return request.Id.ToString();
    }
};
```

**Use Cases**:

* **Idempotency**: Same ID prevents duplicate schedules
* **Tracking**: Correlate scheduled tasks with business entities
* **Debugging**: Meaningful IDs in AWS Console

## Code Examples

### Basic Scheduling with Delay

Schedule a command to execute after a delay:

```csharp
public class OrderService
{
    private readonly IAmACommandProcessor _commandProcessor;

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

        // Schedule payment processing for 30 minutes later
        var schedulerId = await _commandProcessor.SendAsync(
            TimeSpan.FromMinutes(30),
            new ProcessPaymentCommand { OrderId = order.Id, Amount = order.Total }
        );

        _logger.LogInformation(
            "Scheduled payment processing for order {OrderId}: {SchedulerId}",
            order.Id,
            schedulerId
        );
    }
}
```

### Scheduling with Absolute Time

Schedule for a specific time:

```csharp
public class ReportService
{
    private readonly IAmACommandProcessor _commandProcessor;

    public async Task ScheduleDailyReport()
    {
        // Schedule for tomorrow at 9 AM UTC
        var tomorrow9AM = DateTimeOffset.UtcNow.Date.AddDays(1).AddHours(9);

        var schedulerId = await _commandProcessor.SendAsync(
            tomorrow9AM,
            new GenerateDailyReportCommand { Date = DateTime.UtcNow.Date }
        );

        _logger.LogInformation("Scheduled daily report: {SchedulerId}", schedulerId);
    }
}
```

### Cancelling a Scheduled Job

Cancel a scheduled task before it executes:

```csharp
public class OrderService
{
    private readonly IAmACommandProcessor _commandProcessor;
    private readonly IMessageScheduler _scheduler;

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

        // Cancel scheduled payment
        if (!string.IsNullOrEmpty(order.PaymentSchedulerId))
        {
            await _scheduler.CancelAsync(order.PaymentSchedulerId);
            _logger.LogInformation(
                "Cancelled scheduled payment for order {OrderId}",
                orderId
            );
        }

        order.Status = OrderStatus.Cancelled;
        await _repository.UpdateAsync(order);
    }
}
```

### Publishing Events with Delay

Schedule an event to be published:

```csharp
public class TrialService
{
    private readonly IAmACommandProcessor _commandProcessor;

    public async Task StartTrial(User user)
    {
        user.TrialStartDate = DateTime.UtcNow;
        user.TrialEndDate = DateTime.UtcNow.AddDays(30);
        await _repository.SaveAsync(user);

        // Schedule trial expiry reminder for 7 days before end
        var reminderDate = user.TrialEndDate.AddDays(-7);
        var schedulerId = await _commandProcessor.PublishAsync(
            reminderDate,
            new TrialExpiringEvent { UserId = user.Id, DaysRemaining = 7 }
        );

        _logger.LogInformation(
            "Scheduled trial expiry reminder for user {UserId}: {SchedulerId}",
            user.Id,
            schedulerId
        );
    }
}
```

## Scheduling Modes Comparison

| Feature             | Direct to Target                 | Via FireAwsScheduler              |
| ------------------- | -------------------------------- | --------------------------------- |
| **Configuration**   | `UseMessageTopicAsTarget = true` | `UseMessageTopicAsTarget = false` |
| **Use Case**        | Message scheduling               | Request scheduling                |
| **Latency**         | Lower (direct)                   | Higher (2-hop)                    |
| **Components**      | Fewer                            | More (requires Dispatcher)        |
| **Flexibility**     | Target must exist                | More flexible                     |
| **Recommended For** | Production messages              | Requests and commands             |

## Comparison with Other Schedulers

| Feature              | AWS Scheduler  | Quartz.NET     | Hangfire       | InMemory |
| -------------------- | -------------- | -------------- | -------------- | -------- |
| **Cloud Native**     | AWS Only       | ❌              | ❌              | ❌        |
| **Serverless**       | ✅              | ❌              | ❌              | ❌        |
| **Persistence**      | AWS Managed    | Database       | Database       | None     |
| **Dashboard**        | AWS Console    | Limited        | Yes            | No       |
| **Cancellation**     | ✅              | ✅              | ✅              | ✅        |
| **Reschedule**       | ✅              | ✅              | ✅              | ✅        |
| **Cost Model**       | Pay-per-use    | Infrastructure | Infrastructure | Free     |
| **Setup Complexity** | Moderate (IAM) | Moderate       | Easy           | Minimal  |
| **Production Ready** | ✅              | ✅              | ✅              | ❌        |
| **Multi-Cloud**      | ❌              | ✅              | ✅              | ✅        |
| **Strong Naming**    | ✅              | ✅              | ❌              | ✅        |

**When to use AWS Scheduler**:

* Running on AWS infrastructure
* Prefer serverless/managed services
* Need high scalability
* Cost-effective pay-per-use model

## Best Practices

### 1. Use Direct to Target for Messages

```csharp
// Good - Direct scheduling (default)
var schedulerFactory = new AwsSchedulerFactory(awsConnection, "my-role")
{
    UseMessageTopicAsTarget = true  // Default, can omit
};
```

```csharp
// Bad - Unnecessary indirection for messages
var schedulerFactory = new AwsSchedulerFactory(awsConnection, "my-role")
{
    UseMessageTopicAsTarget = false  // Forces FireAwsScheduler approach
};
```

### 2. Limit IAM Role Permissions

```csharp
// Good - Specific resource ARNs
{
    "Effect": "Allow",
    "Action": ["sqs:SendMessage", "sns:Publish"],
    "Resource": [
        "arn:aws:sqs:us-east-1:123456789012:my-queue",
        "arn:aws:sns:us-east-1:123456789012:my-topic"
    ]
}
```

```csharp
// Bad - Overly permissive
{
    "Effect": "Allow",
    "Action": ["sqs:*", "sns:*"],
    "Resource": ["*"]
}
```

### 3. Use Scheduler Groups for Organization

```csharp
// Good - Organized by feature/team
var schedulerFactory = new AwsSchedulerFactory(awsConnection, "role")
{
    Group = new SchedulerGroup
    {
        Name = "payment-processing-schedules",
        Tags = new List<Tag>
        {
            new Tag { Key = "Team", Value = "Payments" },
            new Tag { Key = "CostCenter", Value = "Engineering" }
        }
    }
};
```

### 4. Handle OnConflict Appropriately

```csharp
// Good - Explicit conflict handling
var schedulerFactory = new AwsSchedulerFactory(awsConnection, "role")
{
    OnConflict = OnSchedulerConflict.Overwrite,  // Or Throw, depending on use case
    GetOrCreateRequestSchedulerId = request => $"idempotent-{request.Id}"
};
```

### 5. Use Custom Scheduler IDs for Idempotency

```csharp
// Good - Idempotent based on business key
GetOrCreateRequestSchedulerId = request =>
{
    if (request is ProcessPaymentCommand cmd)
    {
        return $"payment-{cmd.TransactionId}";  // One schedule per transaction
    }
    return request.Id.ToString();
}
```

```csharp
// Bad - Always creates new schedule
GetOrCreateRequestSchedulerId = request => Guid.NewGuid().ToString();
```

### 6. Monitor AWS Scheduler Metrics

```csharp
// Use AWS CloudWatch to monitor:
// - InvocationAttempts
// - InvocationSuccessRate
// - InvocationThrottles
// - TargetErrorRate

// Set up CloudWatch alarms for failures
```

### 7. Use Flexible Time Windows for Cost Optimization

```csharp
// Good - Allow flexibility for non-critical schedules
var schedulerFactory = new AwsSchedulerFactory(awsConnection, "role")
{
    FlexibleTimeWindowMinutes = 5  // AWS can batch executions
};
```

### 8. Test with LocalStack

```csharp
// Good - Test AWS Scheduler locally with LocalStack
var awsConnection = new AWSMessagingGatewayConnection(
    credentials,
    RegionEndpoint.USEast1,
    cfg =>
    {
        if (environment.IsDevelopment())
        {
            cfg.ServiceURL = "http://localhost:4566";  // LocalStack
        }
    }
);
```

## Troubleshooting

### Schedules Not Executing

**Symptom**: Schedules created but never fire

**Possible Causes**:

1. IAM role missing or incorrect permissions
2. Target topic/queue doesn't exist
3. Schedule created in wrong region
4. FireAwsScheduler handler not configured

**Solutions**:

```csharp
// 1. Verify IAM role permissions in AWS Console
// 2. Ensure target exists before scheduling
var schedulerFactory = new AwsSchedulerFactory(awsConnection, "role")
{
    MakeRole = OnMissingRole.Create  // Auto-create role
};

// 3. Verify region matches your resources
var awsConnection = new AWSMessagingGatewayConnection(
    credentials,
    RegionEndpoint.USEast1  // Match your SNS/SQS region
);

// 4. Configure FireAwsScheduler subscription if needed
```

### Access Denied Errors

**Symptom**: `AccessDeniedException` when creating schedules

**Cause**: Application credentials lack EventBridge Scheduler permissions

**Solution**:

```json
{
    "Effect": "Allow",
    "Action": [
        "scheduler:CreateSchedule",
        "scheduler:DeleteSchedule",
        "scheduler:GetSchedule",
        "scheduler:UpdateSchedule",
        "iam:PassRole"
    ],
    "Resource": "*"
}
```

### Schedule Conflicts

**Symptom**: `ConflictException` when creating schedule

**Cause**: Schedule with same ID already exists

**Solutions**:

```csharp
// Option 1: Overwrite existing schedule
OnConflict = OnSchedulerConflict.Overwrite

// Option 2: Throw exception and handle in code
OnConflict = OnSchedulerConflict.Throw
```

### High Costs

**Symptom**: Unexpected AWS Scheduler costs

**Causes**:

* Too many one-time schedules created and not cleaned up
* Short polling intervals

**Solutions**:

```csharp
// 1. Use flexible time windows
FlexibleTimeWindowMinutes = 5

// 2. Clean up old schedules
await _scheduler.CancelAsync(oldSchedulerId);

// 3. Consider Quartz/Hangfire for very frequent schedules
// AWS Scheduler is billed per schedule invocation
```

## Migration from Other Schedulers

### From InMemory Scheduler

```csharp
// Before (Development)
services.AddBrighter(options => { ... })
    .UseScheduler(new InMemorySchedulerFactory())
    .AutoFromAssemblies();

// After (Production on AWS)
services.AddBrighter(options => { ... })
    .UseScheduler(new AwsSchedulerFactory(awsConnection, "scheduler-role")
    {
        SchedulerTopicOrQueue = new RoutingKey("scheduler-topic"),
        OnConflict = OnSchedulerConflict.Overwrite
    })
    .AutoFromAssemblies();
```

**No code changes required** - just swap the scheduler factory!

### From Quartz or Hangfire to AWS Scheduler

```csharp
// Before (Quartz)
services.AddBrighter(options => { ... })
    .UseScheduler(provider =>
    {
        var schedulerFactory = provider.GetRequiredService<ISchedulerFactory>();
        return new QuartzSchedulerFactory(
            schedulerFactory.GetScheduler().GetAwaiter().GetResult()
        );
    })
    .AutoFromAssemblies();

// After (AWS Scheduler)
services.AddBrighter(options => { ... })
    .UseScheduler(new AwsSchedulerFactory(awsConnection, "scheduler-role")
    {
        SchedulerTopicOrQueue = new RoutingKey("scheduler-topic")
    })
    .AutoFromAssemblies();
```

**Benefits of moving to AWS Scheduler**:

* No database required
* No server maintenance
* Automatic scaling
* Pay-per-use pricing

## Related Documentation

* [Brighter Scheduler Support](/paramore-brighter-documentation/scheduler/brighterschedulersupport.md) - Overview of scheduling in Brighter
* [Quartz Scheduler](https://github.com/BrighterCommand/Docs/blob/master/contents/QuartzScheduler.md) - Alternative for non-AWS environments
* [Hangfire Scheduler](/paramore-brighter-documentation/scheduler/hangfirescheduler.md) - Alternative with dashboard
* [InMemory Scheduler](/paramore-brighter-documentation/scheduler/inmemoryscheduler.md) - For testing and development
* [Azure Scheduler](/paramore-brighter-documentation/scheduler/azurescheduler.md) - Azure equivalent
* [AWS SNS Configuration](/paramore-brighter-documentation/guaranteed-at-least-once/awssqsconfiguration.md) - Configuring AWS messaging

## External Links

* [AWS EventBridge Scheduler Documentation](https://docs.aws.amazon.com/scheduler/latest/UserGuide/what-is-scheduler.html)
* [AWS EventBridge Scheduler Pricing](https://aws.amazon.com/eventbridge/pricing/)
* [AWS EventBridge Scheduler Quotas](https://docs.aws.amazon.com/scheduler/latest/UserGuide/scheduler-quotas.html)
* [LocalStack EventBridge](https://docs.localstack.cloud/user-guide/aws/eventbridge/) - For local testing

## Summary

AWS EventBridge Scheduler is a cloud-native, serverless scheduling solution perfect for:

* **AWS Workloads** - Native integration with AWS services
* **Serverless Applications** - No infrastructure to manage
* **High Scalability** - Millions of schedules supported
* **Cost Optimization** - Pay only for what you use

**Recommended for production** when running on AWS infrastructure. Consider Quartz.NET or Hangfire for multi-cloud or on-premises deployments.

**Key Decision Factors**:

* ✅ Use AWS Scheduler if: Running on AWS, prefer managed services, need scalability
* ✅ Use Quartz if: Multi-cloud, complex scheduling, need job clustering
* ✅ Use Hangfire if: Need dashboard, simple setup, OK without strong naming
* ✅ Use InMemory if: Testing only, not for production


---

# Agent Instructions: 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:

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

The question should be specific, self-contained, and written in natural language.
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.
