Skip to content

Report Scheduling ​

GrydReports scheduling is optional and powered by GrydReports.Scheduling.GrydJobs. If your application does not use recurring reports, you can use GrydReports without GrydJobs.

Prerequisites for Scheduling ​

Install and register the optional scheduling integration:

bash
dotnet add package GrydReports.Scheduling.GrydJobs
dotnet add package GrydJobs
csharp
using GrydJobs;
using GrydReports.Scheduling.GrydJobs;

builder.Services.AddGrydJobs(options =>
{
    options.ConnectionString = builder.Configuration.GetConnectionString("Jobs")!;
});

builder.Services.AddGrydReportsScheduling();

if (app.Environment.IsDevelopment())
{
    await app.ApplyGrydReportsMigrationsAsync();
    await app.ApplyGrydJobsMigrationsAsync();
}

WARNING

If AddGrydReportsScheduling() is not registered, scheduling commands/endpoints are not executable.

Overview ​

┌──────────────────┐       ┌──────────────────┐       ┌──────────────────┐
│  ReportSchedule  │──────>│ ScheduledReportJob│──────>│  IReportGenerator│
│  (Entity)        │ cron  │ (GrydJobs)       │       │  (Orchestrator)  │
│                  │       │                  │       │                  │
│  CronExpression  │       │  On Tick:        │       │  Generate +      │
│  TemplateId      │       │  Fetch schedule  │       │  Store +         │
│  Format          │       │  Call generator  │       │  Deliver         │
│  DeliveryOptions │       │  Update status   │       │                  │
└──────────────────┘       └──────────────────┘       └──────────────────┘

Create a Schedule ​

Via API ​

bash
POST /api/v1/reports/schedules
Content-Type: application/json

{
  "templateId": "daily-sales",
  "name": "Daily Sales Summary",
  "cronExpression": "0 8 * * *",
  "format": 1,
  "parameters": {
    "region": "south"
  },
  "delivery": {
    "method": 2,
    "recipients": ["sales-team@company.com"],
    "emailSubject": "Daily Sales Summary"
  },
  "description": "Generates daily sales report at 8 AM"
}

Response 201 Created:

json
{
  "id": "c1d2e3f4-...",
  "templateId": "daily-sales",
  "name": "Daily Sales Summary",
  "cronExpression": "0 8 * * *",
  "format": 1,
  "isActive": true,
  "nextExecutionAt": "2026-02-20T08:00:00Z",
  "executionCount": 0,
  "consecutiveFailures": 0,
  "createdAt": "2026-02-19T14:00:00Z"
}

Via MediatR ​

csharp
var result = await mediator.Send(new CreateReportScheduleCommand(
    TemplateId: "daily-sales",
    Name: "Daily Sales Summary",
    CronExpression: "0 8 * * *",
    Format: ReportFormat.Pdf,
    Parameters: new Dictionary<string, object> { ["region"] = "south" },
    Delivery: new DeliveryOptions
    {
        Method = DeliveryMethod.Email,
        Recipients = ["sales-team@company.com"]
    },
    Description: "Daily sales report at 8 AM"
));

Cron Expressions ​

Standard 5-field cron expressions are supported (powered by Cronos):

ExpressionDescription
0 8 * * *Every day at 8:00 AM
0 9 * * MONEvery Monday at 9:00 AM
0 0 1 * *First day of every month at midnight
0 18 * * FRIEvery Friday at 6:00 PM
*/30 * * * *Every 30 minutes
0 8 1,15 * *1st and 15th of each month at 8:00 AM
0 0 * * MON-FRIWeekdays at midnight

Manage Schedules ​

List Schedules ​

bash
GET /api/v1/reports/schedules?templateId=daily-sales&isActive=true&pageNumber=1&pageSize=20

Get Schedule Details ​

bash
GET /api/v1/reports/schedules/{id}

Update Schedule ​

bash
PUT /api/v1/reports/schedules/{id}
Content-Type: application/json

{
  "name": "Updated Name",
  "cronExpression": "0 9 * * *",
  "format": 2,
  "description": "Changed to 9 AM, Excel format"
}

Delete Schedule ​

bash
DELETE /api/v1/reports/schedules/{id}

Schedule Controls ​

Pause ​

Stops automatic execution without deleting the schedule:

bash
POST /api/v1/reports/schedules/{id}/pause

Resume ​

Reactivates a paused schedule:

bash
POST /api/v1/reports/schedules/{id}/resume

Trigger Now ​

Immediately executes the schedule out-of-cycle:

bash
POST /api/v1/reports/schedules/{id}/trigger

Returns 202 Accepted — the execution runs in background.

IReportScheduler ​

The scheduling interface:

csharp
public interface IReportScheduler
{
    Task<ReportSchedule> CreateScheduleAsync(
        string templateId,
        string cronExpression,
        ReportFormat format,
        Dictionary<string, object>? parameters = null,
        DeliveryOptions? delivery = null,
        CancellationToken ct = default);

    Task UpdateScheduleAsync(Guid scheduleId, UpdateReportScheduleDto update, CancellationToken ct = default);
    Task DeleteScheduleAsync(Guid scheduleId, CancellationToken ct = default);
    Task PauseScheduleAsync(Guid scheduleId, CancellationToken ct = default);
    Task ResumeScheduleAsync(Guid scheduleId, CancellationToken ct = default);
    Task TriggerNowAsync(Guid scheduleId, CancellationToken ct = default);
}

Schedule Configuration ​

csharp
builder.Services.ConfigureGrydReportsScheduling(opts =>
{
    // 0 = unlimited
    opts.MaxSchedulesPerTenant = 0;

    // Auto-pause after N consecutive failures
    opts.MaxConsecutiveFailures = 3;

    // Minimum allowed cron interval
    opts.MinimumInterval = TimeSpan.FromHours(1);

    // Notify when schedule is auto-paused
    opts.NotifyOnAutoPause = true;
});

Schedule Lifecycle ​

Created (Active) ──> Executing ──> Complete ──> Next Tick...
       │                  │
       │              Failed (retry)
       │                  │
       ├── Paused ────────┘ (consecutive failures)
       │
       └── Deleted

The ReportScheduleDto exposes tracking fields:

FieldDescription
isActiveWhether the schedule is currently active
lastExecutedAtTimestamp of last execution
nextExecutionAtCalculated next execution time
executionCountTotal number of successful executions
consecutiveFailuresNumber of failures since last success

Delivery with Schedules

Combine scheduling with delivery to create fully automated reporting workflows. The schedule's DeliveryOptions are applied after each successful generation.

Released under the MIT License.