Skip to main content

IJobClient Interface

The primary API for enqueuing and managing background jobs from your application code. Inject it anywhere — controllers, services, middleware, Razor Pages, or Minimal API endpoints.

Namespace: Zeridion.Flare · Assembly: Zeridion.Flare.dll

public interface IJobClient
{
Task<string> EnqueueAsync<TJob>(object payload, JobOptions? options = null)
where TJob : class;

Task<string> ScheduleAsync<TJob>(object payload, TimeSpan delay, JobOptions? options = null)
where TJob : class;

Task<string> ScheduleAsync<TJob>(object payload, DateTimeOffset runAt, JobOptions? options = null)
where TJob : class;

Task<string> ContinueWithAsync<TJob>(string parentJobId, object payload, JobOptions? options = null)
where TJob : class;

Task<bool> CancelAsync(string jobId);

Task<bool> RetryAsync(string jobId);

Task<JobStatus?> GetStatusAsync(string jobId);
}

Methods overview

MethodReturnsDescription
EnqueueAsyncTask<string>Enqueue a job for immediate execution.
ScheduleAsync (×2)Task<string>Enqueue a job to run after a TimeSpan delay or at a DateTimeOffset. Two overloads.
ContinueWithAsyncTask<string>Enqueue a job that runs after a parent succeeds.
CancelAsyncTask<bool>Cancel a pending or scheduled job.
RetryAsyncTask<bool>Retry a failed or dead-lettered job.
GetStatusAsyncTask<JobStatus?>Get the current status of a job.

EnqueueAsync

Task<string> EnqueueAsync<TJob>(object payload, JobOptions? options = null)
where TJob : class;

Enqueue a job for immediate execution. The job enters the Pending state and becomes eligible for the next available worker.

ParameterTypeDescription
TJobtype paramThe job class. Must implement IJob<TPayload>.
payloadobjectThe payload to pass to the job. Serialized to JSON.
optionsJobOptions?Optional per-call overrides for queue, retries, etc.
ReturnsstringThe job ID assigned by the server.
var jobId = await jobs.EnqueueAsync<SendWelcomeEmail>(
new NewUserEvent { Email = "alice@example.com", Name = "Alice" });

The job type is resolved via the JobTypeRegistry. If TJob was not discovered during assembly scanning, an exception is thrown.


ScheduleAsync (TimeSpan)

Task<string> ScheduleAsync<TJob>(object payload, TimeSpan delay, JobOptions? options = null)
where TJob : class;

Enqueue a job that becomes eligible for processing after the specified delay. The job enters the Scheduled state and transitions to Pending when UtcNow + delay is reached.

ParameterTypeDescription
delayTimeSpanHow long to wait before the job becomes eligible.
var jobId = await jobs.ScheduleAsync<SendFollowUpEmail>(
new FollowUpPayload { UserId = userId },
TimeSpan.FromHours(24));

ScheduleAsync (DateTimeOffset)

Task<string> ScheduleAsync<TJob>(object payload, DateTimeOffset runAt, JobOptions? options = null)
where TJob : class;

Enqueue a job that becomes eligible for processing at a specific UTC time. Use this when you know the exact time a job should run.

ParameterTypeDescription
runAtDateTimeOffsetThe UTC time when the job becomes eligible for processing.
var jobId = await jobs.ScheduleAsync<PublishArticle>(
new PublishPayload { ArticleId = article.Id },
article.PublishAt);

ContinueWithAsync

Task<string> ContinueWithAsync<TJob>(string parentJobId, object payload, JobOptions? options = null)
where TJob : class;

Enqueue a continuation job that runs after a parent job succeeds. The child job enters the Scheduled state and transitions to Pending only when the parent reaches Succeeded.

ParameterTypeDescription
parentJobIdstringThe ID of the parent job to wait for. Must be non-null.

Parent outcome behavior:

Parent stateChild result
SucceededChild transitions from Scheduled to Pending
FailedChild stays in Scheduled (parent may still retry)
DeadLetterChild is cancelled (cascading cancellation)
CancelledChild is cancelled (cascading cancellation)
var processId = await jobs.EnqueueAsync<ProcessOrder>(orderPayload);

var emailId = await jobs.ContinueWithAsync<SendConfirmationEmail>(
processId,
new EmailPayload { OrderId = order.Id, Email = order.Email });

You can chain multiple continuations from the same parent, or build multi-step pipelines by chaining continuations in sequence:

var step1 = await jobs.EnqueueAsync<ValidateOrder>(payload);
var step2 = await jobs.ContinueWithAsync<ChargePayment>(step1, chargePayload);
var step3 = await jobs.ContinueWithAsync<FulfillOrder>(step2, fulfillPayload);
var step4 = await jobs.ContinueWithAsync<SendReceipt>(step3, receiptPayload);
note

The parentJobId must reference an existing job. If the parent is already in a terminal state (Succeeded, DeadLetter, or Cancelled), the continuation is handled immediately — activated if the parent succeeded, or cancelled otherwise. If the parent is in Failed state, the child remains Scheduled because the parent may still be retried.


CancelAsync

Task<bool> CancelAsync(string jobId);

Cancel a pending or scheduled job.

ParameterTypeDescription
jobIdstringThe ID of the job to cancel.
Returnsbooltrue if cancelled successfully.

Returns true if the job was successfully cancelled. Returns false if the job is in a non-cancellable state (the API returns 409 Conflict, which the SDK maps to false). Throws FlareNotFoundException if the job does not exist (404).

var cancelled = await jobs.CancelAsync(jobId);

if (!cancelled)
{
// Job is already processing or completed — can't cancel (409)
}

Cancelling a parent job also cascades to any child continuation jobs in the Scheduled state.


RetryAsync

Task<bool> RetryAsync(string jobId);

Retry a failed or dead-lettered job, resetting it to Pending.

ParameterTypeDescription
jobIdstringThe ID of the job to retry.
Returnsbooltrue if retried successfully.

Returns true if the job was successfully retried. Returns false if the job is not in a retryable state (the API returns 409 Conflict, which the SDK maps to false). Throws FlareNotFoundException if the job does not exist (404).

When retrying a DeadLetter job where AttemptNumber >= MaxAttempts, the server automatically bumps MaxAttempts to allow at least one more attempt.

var retried = await jobs.RetryAsync(jobId);

if (retried)
{
// Job is back in Pending state, will be picked up by next available worker
}

GetStatusAsync

Task<JobStatus?> GetStatusAsync(string jobId);

Get the current status of a job.

ParameterTypeDescription
jobIdstringThe ID of the job to check.
ReturnsJobStatus?The job status, or null if not found.

Returns a JobStatus object with the job's current state, timing, progress, error information, and tags. Returns null if no job exists with the given ID.

var status = await jobs.GetStatusAsync(jobId);

if (status is not null)
{
Console.WriteLine($"Job {status.JobId} is {status.State}");

if (status.Progress.HasValue)
Console.WriteLine($"Progress: {status.Progress:P0}");

if (status.ErrorMessage is not null)
Console.WriteLine($"Error: {status.ErrorMessage}");
}

Usage example

A complete Minimal API controller using all IJobClient methods:

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

app.MapPost("/orders", async (CreateOrderRequest req, IJobClient jobs) =>
{
var order = await CreateOrder(req);

// Enqueue immediately
var processId = await jobs.EnqueueAsync<ProcessOrder>(
new OrderPayload { OrderId = order.Id },
new JobOptions { Queue = "orders", IdempotencyKey = $"order:{order.Id}" });

// Chain a confirmation email after processing succeeds
await jobs.ContinueWithAsync<SendOrderConfirmation>(
processId,
new EmailPayload { OrderId = order.Id, Email = order.Email });

return Results.Created($"/orders/{order.Id}", new { order_id = order.Id, job_id = processId });
});

app.MapPost("/reports/schedule", async (ReportRequest req, IJobClient jobs) =>
{
// Schedule for a specific time
var jobId = await jobs.ScheduleAsync<GenerateReport>(
new ReportPayload { ReportType = req.Type },
req.RunAt);

return Results.Accepted(value: new { job_id = jobId });
});

app.MapPost("/jobs/{id}/cancel", async (string id, IJobClient jobs) =>
{
var cancelled = await jobs.CancelAsync(id);
return cancelled ? Results.Ok() : Results.Conflict(new { error = "Job cannot be cancelled" });
});

app.MapPost("/jobs/{id}/retry", async (string id, IJobClient jobs) =>
{
var retried = await jobs.RetryAsync(id);
return retried ? Results.Ok() : Results.Conflict(new { error = "Job cannot be retried" });
});

app.MapGet("/jobs/{id}", async (string id, IJobClient jobs) =>
{
var status = await jobs.GetStatusAsync(id);
return status is not null ? Results.Ok(status) : Results.NotFound();
});

app.Run();

Exceptions

EnqueueAsync, ScheduleAsync, and ContinueWithAsync can throw:

CancelAsync and RetryAsync return false on 409 (state conflict) instead of throwing. They throw FlareNotFoundException on 404 (job not found).

GetStatusAsync returns null when the job is not found (404) instead of throwing FlareNotFoundException.

See the Exception hierarchy for details and catch patterns.

See also