Quartz.Jobs 4.0.0-alpha.1
title: Jobs
Quartz.Jobs provides some useful ready-made jobs for your convenience.
Quartz provides a number of utility jobs that you can use in your application for doing things like sending
e-mails and invoking native processes. These out-of-the-box jobs live in the Quartz.Jobs namespace, which is
also the assembly and NuGet package name. In 3.x the namespace was the singular Quartz.Job; a configuration
string or a stored JOB_CLASS_NAME naming the old spelling still resolves, with a warning.
Installation
You need to add NuGet package reference to your project which uses Quartz.
dotnet add package Quartz.Jobs
How these jobs are configured
Each of these jobs reads its settings from its JobDataMap, under the keys listed with it below. Those keys
are the persisted form: they are what a job store writes, what a cluster shares, and what an XML or JSON
scheduling file names.
Each job also has an options type that maps onto exactly those keys, and an extension that writes it. It is the same stored job either way — but the key cannot be misspelled, the value cannot be of the wrong type, and every setting the job honours is a named property you can find by typing a dot.
| Job | Options | Extension |
|---|---|---|
DirectoryScanJob |
DirectoryScanOptions |
UsingDirectoryScanOptions(…) |
FileScanJob |
FileScanOptions |
UsingFileScanOptions(…) |
NativeJob |
NativeJobOptions |
UsingNativeJobOptions(…) |
SendMailJob |
SendMailOptions |
UsingSendMailOptions(…) |
The extensions work on both configuration surfaces — JobBuilder.Create<TJob>() and the configurator
AddJob<TJob>(…) hands you — and each leaves you with what you started with, so the chain continues as usual.
Options.FromJobData(map) reads the same settings back out of a job's data.
Features
DirectoryScanJob
Inspects a directory and compares whether any files' "last modified dates" have changed since the last time it
was inspected. If one or more files have been updated, created or deleted, the job invokes a call-back method
on an IDirectoryScanListener.
IJobDetail job = JobBuilder.Create<DirectoryScanJob>()
.WithIdentity("inboxScan")
.UsingDirectoryScanOptions(new DirectoryScanOptions
{
Directories = ["/var/spool/inbox"],
ScanListenerName = nameof(InboxListener),
SearchPattern = "*.csv",
IncludeSubDirectories = true,
MinimumUpdateAge = TimeSpan.FromSeconds(30),
})
.Build();
| Setting | Job data key | Default |
|---|---|---|
Directories |
DIRECTORY_NAMES (semicolon-separated), or DIRECTORY_NAME for one |
— |
DirectoryProviderName |
DIRECTORY_PROVIDER_NAME |
none; the paths above are used |
ScanListenerName |
DIRECTORY_SCAN_LISTENER_NAME |
required |
SearchPattern |
SEARCH_PATTERN |
* |
IncludeSubDirectories |
INCLUDE_SUB_DIRECTORIES |
false |
MinimumUpdateAge |
MINIMUM_UPDATE_AGE, in milliseconds |
5 seconds |
MinimumUpdateAge is how long a file must have been left alone before the job reports it. Without it a file
another process is still writing would be handed to the listener half-finished.
The listener is found in one of two ways, in this order:
- Dependency injection (recommended): register your
IDirectoryScanListenerimplementation in the container, and name its type —ScanListenerName = nameof(InboxListener). SchedulerContext: store the instance under a key, and name that key.
scheduler.Context["inboxListener"] = new InboxListener();
Where the directories come from can be decided at run time instead of being listed: implement
IDirectoryProvider, put the instance in the SchedulerContext, and name that key as
DirectoryProviderName. It is handed the merged job data and returns the paths to scan.
The job keeps its own bookkeeping — the last modification time it saw and the file list it saw it in — in the
job detail's data map, which is why it is [PersistJobDataAfterExecution].
FileScanJob
Inspects a single file and compares whether its "last modified date" has changed since the last time it was
inspected. If it has, the job invokes a call-back method on an IFileScanListener found in the
SchedulerContext.
IJobDetail job = JobBuilder.Create<FileScanJob>()
.WithIdentity("configWatch")
.UsingFileScanOptions(new FileScanOptions
{
FileName = "/etc/app/settings.json",
ScanListenerName = "settingsListener",
MinimumUpdateAge = TimeSpan.FromSeconds(5),
})
.Build();
| Setting | Job data key | Default |
|---|---|---|
FileName |
FILE_NAME |
required |
ScanListenerName |
FILE_SCAN_LISTENER_NAME |
required |
MinimumUpdateAge |
MINIMUM_UPDATE_AGE, in milliseconds |
5 seconds |
NativeJob
Runs a native executable in a separate process.
IJobDetail job = JobBuilder.Create<NativeJob>()
.WithIdentity("dumbJob")
.UsingNativeJobOptions(new NativeJobOptions
{
Command = "echo",
Parameters = "\"hi\" >> foobar.txt",
})
.Build();
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("dumbTrigger")
.WithSimpleSchedule(x => x.WithInterval(TimeSpan.FromSeconds(5)).RepeatForever())
.Build();
await scheduler.ScheduleJob(job, trigger);
| Setting | Job data key | Default |
|---|---|---|
Command |
command |
required |
Parameters |
parameters |
none |
WaitForProcess |
waitForProcess |
true |
ConsumeStreams |
consumeStreams |
false |
WorkingDirectory |
workingDirectory |
the scheduler's |
When WaitForProcess is on, the integer exit code of the process is saved as the job execution result in the
IJobExecutionContext. Turn ConsumeStreams on for a chatty process: one that writes more output than its
pipe holds blocks until someone reads it.
SendMailJob
Sends an e-mail with the configured content to the configured recipient.
IJobDetail job = JobBuilder.Create<SendMailJob>()
.WithIdentity("nightlyDigest")
.UsingSendMailOptions(new SendMailOptions
{
SmtpHost = "smtp.example.com",
SmtpPort = 587,
Sender = "scheduler@example.com",
Recipient = "ops@example.com",
Subject = "Nightly digest",
Message = "Everything ran.",
})
.Build();
| Setting | Job data key | Default |
|---|---|---|
SmtpHost |
smtp_host |
required |
SmtpPort |
smtp_port |
the client's default |
Sender |
sender |
required |
Recipient |
recipient |
required |
CcRecipient |
cc_recipient |
none |
ReplyTo |
reply_to |
the sender |
Subject |
subject |
required |
Message |
message |
required |
Encoding |
encoding |
the default |
Override Send(MailInfo, CancellationToken) to route the mail through something other than SmtpClient, or
BuildMessage(SendMailOptions) to add to the message — an attachment, a header — before it goes.
Keep the SMTP credential out of job data
SendMailOptions has no user name or password on purpose. Job data is durable: a persistent job store writes
it to QRTZ_JOB_DETAILS, every node in the cluster reads it, the dashboard shows it, and any export of that
table carries it. A password put there is a password in all of those places.
Register the credential with the container instead, and the job authenticates with it:
services.AddSingleton<ICredentialsByHost>(new NetworkCredential("mailer", smtpPassword));
ICredentialsByHost is what SmtpClient.Credentials takes, so a CredentialCache covers several servers.
The password itself belongs wherever the rest of your secrets live — user secrets in development, a key vault
or an environment variable in production — and reaches this registration through IConfiguration.
The smtp_username and smtp_password job data keys are still read when nothing is registered, so a job
scheduled by an earlier version keeps sending. The job logs a warning when it uses them, and a credential from
the container wins.
NoOpJob
A job that does nothing. Useful as a placeholder, and for triggering listeners on a schedule without any work attached.
Registering these jobs with the container
The jobs take their dependencies — a TimeProvider, an IServiceProvider, an ICredentialsByHost — from the
container, so register them the same way you register your own:
builder.Services.AddQuartz(q =>
{
q.AddJob<NativeJob>(j => j
.WithIdentity("nightlyReport")
.StoreDurably()
.UsingNativeJobOptions(new NativeJobOptions
{
Command = "report.exe",
Parameters = "--nightly",
ConsumeStreams = true,
}));
q.AddTrigger<NativeJob>(t => t
.ForJob("nightlyReport")
.WithCronSchedule("0 0 2 * * ?"));
});
Showing the top 20 packages that depend on Quartz.Jobs.
| Packages | Downloads |
|---|---|
|
Quartz.Plugins
Quartz.NET Plugins; Quartz Scheduling Framework for .NET
|
29 |
|
Quartz.Plugins
Quartz.NET Plugins; Quartz Scheduling Framework for .NET
|
30 |
|
Quartz.Plugins
Quartz.NET Plugins; Quartz Scheduling Framework for .NET
|
31 |
|
Quartz.Plugins
Quartz.NET Plugins; Quartz Scheduling Framework for .NET
|
32 |
|
Quartz.Plugins
Quartz Scheduling Framework for .NET
|
29 |
|
Quartz.Plugins
Quartz Scheduling Framework for .NET
|
30 |
|
Quartz.Plugins
Quartz Scheduling Framework for .NET
|
31 |
.NET 10.0
- Quartz (>= 4.0.0-alpha.1)
- Microsoft.Extensions.DependencyInjection (>= 10.0.11)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.11)
- Microsoft.Extensions.Logging (>= 10.0.11)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.11)
| Version | Downloads | Last updated |
|---|---|---|
| 4.0.0-alpha.3 | 1 | 08/27/2026 |
| 4.0.0-alpha.2 | 2 | 08/25/2026 |
| 4.0.0-alpha.1 | 5 | 08/24/2026 |
| 3.20.0 | 1 | 08/27/2026 |
| 3.19.1 | 15 | 07/28/2026 |
| 3.19.0 | 13 | 07/26/2026 |
| 3.18.2 | 19 | 06/28/2026 |
| 3.18.1 | 31 | 04/27/2026 |
| 3.18.0 | 28 | 04/17/2026 |
| 3.17.1 | 28 | 04/17/2026 |
| 3.17.0 | 31 | 04/17/2026 |
| 3.16.1 | 19 | 04/17/2026 |
| 3.16.0 | 28 | 04/17/2026 |
| 3.15.1 | 29 | 04/17/2026 |
| 3.15.0 | 30 | 04/17/2026 |
| 3.14.0 | 31 | 04/17/2026 |
| 3.13.1 | 28 | 04/17/2026 |
| 3.13.0 | 28 | 04/17/2026 |
| 3.12.0 | 21 | 04/17/2026 |
| 3.11.0 | 31 | 04/17/2026 |
| 3.10.0 | 28 | 04/17/2026 |
| 3.9.0 | 26 | 04/17/2026 |
| 3.8.1 | 28 | 04/17/2026 |
| 3.8.0 | 28 | 04/17/2026 |
| 3.7.0 | 29 | 04/17/2026 |
| 3.6.3 | 32 | 04/17/2026 |
| 3.6.2 | 31 | 04/17/2026 |
| 3.6.1 | 29 | 04/17/2026 |
| 3.6.0 | 28 | 04/17/2026 |
| 3.5.0 | 27 | 04/17/2026 |
| 3.4.0 | 23 | 04/17/2026 |
| 3.3.3 | 30 | 04/17/2026 |
| 3.3.2 | 31 | 04/17/2026 |
| 3.3.1 | 31 | 04/17/2026 |
| 3.3.0 | 31 | 04/17/2026 |
| 3.2.4 | 29 | 04/17/2026 |
| 3.2.3 | 30 | 04/17/2026 |
| 3.2.2 | 27 | 04/17/2026 |
| 3.2.1 | 32 | 04/17/2026 |
| 3.2.0 | 21 | 04/17/2026 |
| 3.1.0 | 30 | 04/17/2026 |
| 3.0.7 | 28 | 04/17/2026 |
| 3.0.6 | 32 | 04/17/2026 |
| 3.0.5 | 22 | 04/17/2026 |
| 3.0.4 | 24 | 04/17/2026 |
| 3.0.3 | 28 | 04/17/2026 |
| 3.0.2 | 31 | 04/17/2026 |
| 3.0.1 | 32 | 04/17/2026 |
| 3.0.0 | 28 | 04/17/2026 |
| 3.0.0-beta1 | 29 | 04/17/2026 |