Skip to content

Background jobs

Work that does not belong in a request runs on scheduled jobs. Job state is persisted in the database, so a restart does not lose scheduled work.

The dashboard is at Hangfire:DashboardPath (/hangfire by default) and is restricted to administrators.

All configured under the BackgroundJobs section. Each block has at least Enabled, Interval, and MaxItems.

Job Does Interval Batch
PermissionRebuild Materialises effective permissions from the rebuild queue. 30 s 200
ReminderDispatch Fires due document reminders. 1 min 200
EmailDelivery Drains the outbound email outbox. 1 min 100
SignatureExpiry Expires stale signature requests. 5 min 200
FolderEmailIntake Polls a mailbox and files attachments. Disabled by default. 5 min -
UploadSessionSweep Cleans up abandoned chunked uploads. 15 min 200
WorkflowEscalation Escalates overdue workflow tasks. 15 min 200
PreviewGeneration Renders missing previews and thumbnails. 15 min 200
OcrBackfill Extracts text from documents that have none. 15 min 100
RetentionCleanup Purges recycle-bin items past retention. 1 h 200
OrphanByteReclamation Deletes blobs nothing references any more. 6 h 200
StorageIntegrity Re-hashes stored blobs and verifies them. 12 h 500

A WorkflowAssignmentReconciliation job also runs, reassigning tasks belonging to departed users.

Job Extra settings
UploadSessionSweep SessionLifetime (1 day) - how long an abandoned session survives.
OrphanByteReclamation MinimumAge (1 hour) - how long a blob must be unreferenced before deletion.
WorkflowEscalation EscalationWindow (2 days) - how long a task may sit before escalation.
PreviewGeneration MaxAttempts (3) - retries before giving up on a version.
EmailDelivery MaxAttempts (3), BaseBackoff (1 min), LeaseTimeout (5 min).

The intervals are not arbitrary, and the pattern is worth understanding before changing any of them.

Fast (30 s - 1 min) for things a user is waiting on. Permission rebuild, reminders, email.

Medium (5 - 15 min) for things that should happen soon but that nobody is watching. Previews, text extraction, escalation, session cleanup. A new document without a thumbnail for a few minutes is acceptable; the alternative is doing that work inside the upload request.

Slow (1 - 12 h) for housekeeping. Retention, byte reclamation, integrity verification. These scan and are deliberately infrequent.

The configuration file itself records why PermissionRebuild runs every 30 seconds rather than every 5: access-control writes drain the rebuild queue inline, in the same transaction, so this job is a safety net for rows that outlive their request, not the primary path. At a 5-second cadence the job runner’s own bookkeeping became the dominant cost of an otherwise idle system.

That is the general lesson. A job that ticks frequently and usually finds nothing to do is not free - it costs a scheduler wake-up, a connection, and a query, on every tick, forever.

Deleting a document does not free disk space, and the reason is two jobs with different jobs to do:

  1. RetentionCleanup purges the document record once it is past retention.
  2. OrphanByteReclamation later deletes the underlying blob, if nothing else references it and it is older than MinimumAge.

Because storage is content-addressed, a blob may still be referenced by other documents. Space is therefore freed some hours after a purge, not at it. See Governance and retention.

Every job can be turned off with its Enabled flag. Two are worth knowing about:

  • FolderEmailIntake is disabled by default and needs both its own flag and EmailIntake:Enabled to be turned on.
  • Disabling PermissionRebuild is usually safe in the short term, because access-control writes drain the queue inline. It is a safety net, not the primary path - but leaving it off indefinitely means nothing catches the rows that outlive their request.

Disabling StorageIntegrity removes your only automatic detection of blob corruption or missing files. Leave it on.

The dashboard shows scheduled, running, succeeded, and failed jobs, with retry history. Hangfire:RetryAttempts (3 by default) controls automatic retries.

A job failing consistently is worth investigating rather than waiting out - a preview job failing every pass usually means the converter path is wrong, not that the documents are unusual.