v0.5.0
.NET SDK

Schedule .NET workflows

Create cron, interval, and one-time schedules with inspectable run history.

A schedule stores a pipeline definition plus a trigger. Every firing creates a normal pipeline, executed by the same workers and handlers as an on-demand run. SDK and server 0.5.0 are the supported pair for this guide.

Register a schedule

This method can live in a producer application referencing PipelogiqSDK.Core. Supply the same connection options as the quickstart. DispatchDueEmailsHandler is your registered worker handler; the snippet registers a definition and does not send real email itself.

csharp
using PipelogiqSDK.Builders;
using PipelogiqSDK.Configuration;
using PipelogiqSDK.Contracts;

static async Task<ScheduleResponse> RegisterEmailScheduleAsync(
    PipelogiqRunnerOptions options, CancellationToken ct)
{
    using var schedule = ScheduleBuilder.Create("dispatch-due-emails", options)
        .Cron("*/5 * * * *", timeZone: "Europe/Tallinn")
        .WithAction("dispatch", "DispatchDueEmailsHandler", new
        {
            BatchSize = 200,
            ScheduledFor = "{{scheduledFor}}",
            RunId = "{{runId}}"
        })
        .WithOverlapPolicy(OverlapPolicy.Skip)
        .Catchup(CatchupPolicy.None)
        .Jitter(TimeSpan.FromSeconds(10))
        .AddLabel("workflow", "email-dispatch");
    return await schedule.ApplyAsync(ct);
}

ApplyAsync is an idempotent upsert by application-scoped, case-insensitive name. Reapplying an unchanged definition does not increment its version or move the next run. Updating business configuration creates a new definition version. Omitted overlap/catchup settings preserve existing settings; specify them when application code should own them.

Stored definitions exclude the application API key, a particular run's idempotency key, and runtime traces. Keep the name stable across deployments instead of creating a new schedule every time.

Select a trigger

Builder callBehavior
.Cron("0 3 * * *", "Europe/Tallinn")Five-field cron in an IANA time zone.
.Every(TimeSpan.FromMinutes(5))Fixed interval in wall-clock seconds.
.RunOnceAt(DateTimeOffset.UtcNow.AddHours(1))One future firing, then automatic archive.

Cron supports the documented descriptors such as @hourly and @daily; six-field expressions with seconds are not accepted. Every requires whole seconds, at least 10. The once instant must be in the future.

Cron is evaluated in its local time zone and stored as UTC. A time in the spring-forward gap is skipped; a repeated local time during the autumn transition can run twice. Choose UTC or a local time outside the transition hour if those semantics are unsuitable. Interval schedules do not use a time zone.

Decide overlap and catch-up behavior

Overlap policyWhen a previous run is unfinished
SkipRecord a skipped tick.
AllowCreate an independent overlapping run.
ReplaceCancel the previous pipeline, then create another.
QueueCreate a pipeline depending on the in-flight run.

Queue does not promise a FIFO chain: multiple queued runs can depend on the same predecessor and become eligible together. Use Skip when scheduled firings should not overlap. Replacement cannot reverse a side effect already performed by the cancelled run.

CatchupPolicy.None does not replay the missed backlog; the latest due tick may run normally. One creates the latest missed tick as a catch-up run. All replays up to the configured budget, oldest first; set it with .Catchup(CatchupPolicy.All, max: 24). The SDK accepts a catch-up maximum from 1 to 1000. Inspect run history to distinguish catch-up and skipped ticks.

Jitter adds a bounded random delay to firing. It uses whole seconds from zero to 3600. The planned time remains available separately from actual firing time; cron returns to the expression instead of accumulating drift.

Use firing-time values

The server substitutes these placeholders in stage inputs and context values:

PlaceholderValue
{{scheduledFor}}Planned tick as a UTC timestamp.
{{runId}}Schedule run ID.
{{previousPipelineId}}Previous pipeline ID, or an empty value on the first run.

Other strings are not a general template language. Use the scheduled time as a business window when processing delayed batches; use an external idempotency key for each record you change.

Inspect and control the schedule

This method lists schedules and run history using the public API:

csharp
using PipelogiqSDK.Api;
using PipelogiqSDK.Configuration;
using PipelogiqSDK.Contracts;

static async Task InspectSchedulesAsync(
    PipelogiqRunnerOptions options, CancellationToken ct)
{
    using var client = new PipelogiqApiClient(options);
    string? cursor = null;
    do
    {
        var page = await client.ListSchedulesAsync(
            ScheduleStatus.Active, cursor: cursor, limit: 100, ct: ct);
        foreach (var schedule in page.Schedules)
            Console.WriteLine($"{schedule.Name}: next={schedule.NextRunAt}");
        cursor = page.NextCursor;
    } while (!string.IsNullOrWhiteSpace(cursor));

    var history = await client.ListScheduleRunsAsync(
        "dispatch-due-emails", limit: 20, ct: ct);
    foreach (var run in history.Runs)
        Console.WriteLine($"{run.ScheduledFor}: {run.Outcome}, pipeline={run.PipelineId}");
}

With that same client, name, and cancellation token:

csharp
await client.PauseScheduleAsync(name, ct);
await client.ResumeScheduleAsync(name, ct);
var manual = await client.TriggerScheduleAsync(name, new { BatchSize = 1 }, ct);
await client.ArchiveScheduleAsync(name, ct);

These are separate operations, not a sequence to run blindly. Pause stops automatic firing. Resume replans from now. A manual trigger creates a run without advancing the automatic cursor and bypasses overlap/catch-up policies; its optional input replaces the first stage's input. Archive retires the schedule.

A history outcome of Created means a pipeline was created, not that its handlers succeeded. Inspect PipelineId and PipelineStatus for execution. Skipped includes a reason; Failed indicates creation failure. ScheduledFor, FiredAt, and LagSeconds show timing. Run history and schedule lists use cursors with page sizes from 1 to 200.

Public definitions redact sensitive values. Do not turn [REDACTED] into a replacement credential. Unchanged round trips can preserve the original secret; ambiguous edits are rejected. Prefer applying the original worker-owned definition from deployment configuration.

Operational expectations

Schedules target ordinary application workloads with second-scale observation, not a hard real-time clock. Validate the time zones and overlap/catch-up combinations your deployment depends on. SDK fixtures establish request correctness; the recorded Lab runs also exercised actual cron, once, and interval firing, but do not establish every timing boundary or a production throughput limit.

Download this page as MarkdownMatches the 0.5.0 source release

Start here