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.