Version v1.0 · dotnet

First Job Example

Detailed first-job walkthrough including attribute options and enqueue versus schedule behavior.

First Job Example

This walkthrough shows a realistic first job, all options in both job attributes, and why DurableStack separates enqueue metadata from recurring schedule metadata.

Example job

using DurableStack.Core;
using DurableStack.Core.Abstractions;
using DurableStack.Core.Models;
using DurableStack.Hosting.DependencyInjection;

[DurableJob(
    Name = "invoice-sync",
    MaxAttempts = 5,
    RetryBehavior = RetryBehavior.Backoff,
    RetryInitialDelaySeconds = 10)]
[RecurringJob(
    "*/5 * * * *",
    TimeZone = "UTC",
    Enabled = true,
    AllowConcurrentRuns = false)]
public sealed class InvoiceSyncJob : IDurableJob
{
    private readonly ILogger<InvoiceSyncJob> _logger;

    public InvoiceSyncJob(ILogger<InvoiceSyncJob> logger)
    {
        _logger = logger;
    }

    public async Task ExecuteAsync(JobContext context, CancellationToken cancellationToken)
    {
        _logger.LogInformation(
            "Invoice sync run {RunId} attempt {Attempt} started at {StartedUtc}",
            context.RunId,
            context.Attempt,
            context.StartedAtUtc);

        await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);

        _logger.LogInformation("Invoice sync run {RunId} completed", context.RunId);
    }
}

Why there are two attributes

DurableJob defines execution behavior

  • Name: stable job identity used for registration and operations.
  • MaxAttempts: total attempts including initial execution.
  • RetryBehavior: FixedDelay or Backoff.
  • RetryInitialDelaySeconds: per-job retry baseline.

This attribute defines what happens when a run executes and fails.

RecurringJob defines schedule behavior

  • Cron: schedule cadence.
  • TimeZone: IANA timezone used for cron evaluation.
  • Enabled: whether schedule emits new runs.
  • AllowConcurrentRuns: whether overlapping scheduled runs are allowed.

This attribute defines whether and how new runs are created over time.

Enqueue versus schedule

  • Enqueue creates one run immediately.
  • Recurring schedule materializes runs automatically when cron slots are due.
  • A job can be enqueue-only (no RecurringJob) or recurring (both attributes).

Enqueue the same job manually

using DurableStack.Core.Abstractions;

app.MapPost("/jobs/invoice-sync/run-now", async (
    IDurableStackClient client,
    CancellationToken cancellationToken) =>
{
    var runId = await client.EnqueueAsync<InvoiceSyncJob>(cancellationToken: cancellationToken);
    return Results.Accepted($"/runs/{runId}", new { runId });
});

With this route plus [RecurringJob], the same handler supports both:

  • automatic recurring execution
  • manual run-now execution

Practical guidance

  • Keep DurableJob.Name stable after production adoption.
  • Use AllowConcurrentRuns = false for jobs that mutate shared resources.
  • Prefer RetryBehavior.Backoff for third-party API dependencies.
  • Keep handlers idempotent so retries are safe.

Next step

After this page, review Getting Started for the recommended runtime configuration baseline.