Skip to main content

Overview

Gallery.CredentialExpiration is a scheduled background job (console executable) that monitors NuGet Gallery user API keys approaching or past their expiration date and dispatches targeted email notifications. It serves two notification categories per user:
  • Expiring — keys that will expire within a configurable warning window (e.g., 10 days).
  • Expired — keys that crossed their expiration timestamp since the last successful job run.
The job is designed to run on a recurring schedule (managed externally by NSSM or a similar service wrapper). It persists a JSON cursor to Azure Blob Storage after every run so that expiry windows are tracked incrementally and duplicate notifications are avoided even when the job is delayed or restarts.
The job uses a WhatIf configuration flag. When set to true, all database queries and cursor updates execute normally, but no emails are dispatched. This is intended for development and integration environments to prevent accidental user spam.

Role in the System

Gallery Database Consumer

Queries the Credentials table in the NuGet Gallery SQL database for apikey.v3 and apikey.v4 credential types whose expiry falls within the job’s computed time window.

Email Pipeline Producer

Publishes outbound email messages to an Azure Service Bus topic using the shared NuGet.Services.Messaging infrastructure. Actual delivery is handled downstream by a separate email worker.

Cursor-Driven Incremental Processing

Reads and writes a cursorv2.json file in Azure Blob Storage to track the last processed timestamp, enabling safe re-runs and gap recovery.

Standalone NuGet Job

Extends JsonConfigurationJob from NuGet.Jobs.Common, following the standard job pattern used across the NuGetGallery background job fleet.

Key Files and Classes

Dependencies

Internal Project References

Key NuGet Dependencies

SQL Query

The credential retrieval query is stored in Strings.resx:
Only apikey.v3 and apikey.v4 credential types are targeted. Revoked credentials (RevocationSourceKey IS NOT NULL) are explicitly excluded. Users must have EmailAllowed = 1 and a non-empty email address.

Notable Patterns and Quirks

The cursor file is named cursorv2.json. The v2 suffix implies a breaking schema change from a prior cursor format. Deserialisation uses MissingMemberHandling.Error, meaning any cursor file written by an older schema version will cause the job to throw and halt rather than silently misprocess notifications.
Non-scoped API keys (those with a null or empty Description) are backfilled with the string "Full access API key" before grouping. This is defined as Constants.NonScopedApiKeyDescription in ExpiredCredentialData.cs.
The expiring-credentials boundary uses MaxProcessedCredentialsTime from the cursor rather than JobRunTime when the cursor’s value is ahead of the current run time. This guards against sending duplicate expiry warnings if the job runs more frequently than the warning window.

Email Aggregation Pattern

Credentials are grouped per user before emailing. Each user receives at most two emails per run — one for expiring keys and one for expired keys — with all affected key names listed as bullet points inside a single message body. This avoids per-key email storms for users with many API keys.

Configuration Reference