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 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
| File Path | Class / Type | Purpose |
|---|---|---|
Program.cs | Program | Entry point; creates Job and hands off to JobRunner.Run(). |
Job.cs | Job | Main job class. Initialises services in Init(), orchestrates the full run loop in Run(). |
GalleryCredentialExpiration.cs | GalleryCredentialExpiration | Implements ICredentialExpirationExporter. Executes the SQL query and filters results into expiring vs. expired buckets. |
ICredentialExpirationExporter.cs | ICredentialExpirationExporter | Interface contract for credential retrieval and categorisation. |
CredentialExpirationEmailBuilder.cs | CredentialExpirationEmailBuilder | Extends MarkdownEmailBuilder. Constructs subject lines and Markdown bodies for both expiry states. |
JobRunTimeCursor.cs | JobRunTimeCursor | Serialisable cursor POCO holding JobCursorTime and MaxProcessedCredentialsTime, persisted as cursorv2.json. |
Configuration/InitializationConfiguration.cs | InitializationConfiguration | Strongly-typed configuration POCO bound from appsettings.json. |
Models/ExpiredCredentialData.cs | ExpiredCredentialData | DTO populated directly from the SQL result set. |
Models/CredentialExpirationJobMetadata.cs | CredentialExpirationJobMetadata | Immutable value object combining run time, cursor, and warn-days into a single context. |
LogEvents.cs | LogEvents | Structured log event IDs (600–651) for failed email, failed credential handling, and job lifecycle failures. |
Strings.resx | — | Embedded resource containing email subject/body templates and the raw SQL query for credential retrieval. |
Dependencies
Internal Project References
| Project | Role |
|---|---|
NuGet.Jobs.Common | Base JsonConfigurationJob, JobRunner, GalleryDbConfiguration, QueryWithRetryAsync, DI wiring. |
NuGet.Services.Storage | AzureStorageFactory, AzureStorage, BlobServiceClientFactory — blob cursor persistence. |
Key NuGet Dependencies
| Package | Usage |
|---|---|
NuGet.Services.Messaging | AsynchronousEmailMessageService, EmailMessageEnqueuer, MarkdownEmailBuilder. |
NuGet.Services.ServiceBus | TopicClientWrapper, ServiceBusMessageSerializer — Service Bus email dispatch. |
Azure.Identity | Managed-identity blob storage auth. |
Newtonsoft.Json | Cursor serialisation (strict MissingMemberHandling.Error). |
System.Data.SqlClient | Gallery SQL connection. |
SQL Query
The credential retrieval query is stored inStrings.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
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.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
| Property | Description |
|---|---|
ContainerName | Azure Blob container name for cursor storage. |
DataStorageAccountUrl | Blob service endpoint URL (used with managed identity). |
EmailPublisherConnectionString | Azure Service Bus namespace connection string. |
EmailPublisherTopicName | Service Bus topic name for outbound email messages. |
GalleryAccountUrl | URL inserted into email bodies pointing users to their API key management page. |
GalleryBrand | Brand name string (e.g., "NuGet") interpolated into email subjects and bodies. |
WarnDaysBeforeExpiration | Number of days ahead of expiry to begin sending warnings. |
WhatIf | When true, suppresses actual email dispatch (safe for non-production). |