Skip to main content

Overview

NuGet.Services.Messaging is a small, focused library that forms the bridge between the NuGet Gallery frontend (the email sender) and the back-end email processing job (the email consumer). It defines the canonical EmailMessageData model that both sides agree on, the IEmailMessageEnqueuer interface that the frontend uses to dispatch email without knowing the delivery mechanism, and the ServiceBusMessageSerializer that translates between EmailMessageData and Azure Service Bus brokered messages. The library encodes one deliberate GDPR constraint directly in the serializer: every Service Bus message is given a time-to-live of exactly two days. Because email message payloads can contain personally identifiable information (recipient addresses, user names, etc.), this hard-coded TTL ensures that un-processed messages are automatically purged by the broker before they can linger indefinitely. The project targets both net472 and netstandard2.0, which allows it to be referenced by the full-framework NuGetGallery web application and by any future .NET Standard-compatible services without modification. It has no direct Azure SDK dependency of its own; all Azure Service Bus types are abstracted behind interfaces defined in NuGet.Services.Contracts and implemented in NuGet.Services.ServiceBus.

Role in System

NuGet.Services.Messaging sits in the middle of the asynchronous email pipeline. The gallery frontend constructs an EmailMessageData value, hands it to IEmailMessageEnqueuer, which serializes it and publishes it to an Azure Service Bus topic. A separate back-end job subscribes to that topic, deserializes the message using the same IServiceBusMessageSerializer, and performs the actual SMTP delivery.

Shared Data Contract

EmailMessageData is the single agreed-upon model for an email message. It carries subject, plain-text body, HTML body, sender, To/CC/Bcc/ReplyTo recipient lists, a tracking GUID, and a delivery count populated on the consumer side.

Enqueuing Abstraction

IEmailMessageEnqueuer hides the Service Bus publishing details from callers. The concrete EmailMessageEnqueuer serializes the message, calls ITopicClient.SendAsync, and emits structured log entries at Trace and Information level for each step.

Versioned Schema Serialization

ServiceBusMessageSerializer uses the generic BrokeredMessageSerializer<T> from NuGet.Services.ServiceBus together with a private [Schema(Name = "EmailMessageData", Version = 1)] inner class. Schema name and version are stored as Service Bus message properties and validated on deserialization.

GDPR-Enforced TTL

Messages are given a two-day TTL at serialization time. This is a hard-coded policy (not configuration) that ensures PII-containing email payloads are automatically expired by the broker if they are not consumed promptly.

Key Files and Classes

Dependencies

NuGet Package References

Internal Project References

Notable Patterns and Implementation Details

The EmailMessageData1 inner class in ServiceBusMessageSerializer is private and versioned with [Schema(Name = "EmailMessageData", Version = 1)]. This versioning scheme means that a schema change (adding, removing, or modifying a property) requires incrementing the version number and adding a new inner class (e.g., EmailMessageData2). The deserializer enforces the version at runtime and throws FormatException if the message was produced by a different schema version.
The two-day TTL on Service Bus messages is enforced unconditionally in ServiceBusMessageSerializer.SerializeEmailMessageData. It is not configurable. Any email message payload that remains unconsumed in the topic for longer than 48 hours will be dead-lettered or discarded by the broker. This is intentional GDPR policy, but it means that extended outages of the back-end email consumer will result in lost email notifications.
EmailMessageEnqueuer logs the MessageTrackingId GUID at every step using structured logging. This allows operators to correlate a specific email message across the frontend log (where it was enqueued) and the backend log (where it was processed or failed) by searching for the same tracking ID value.
The DeliveryCount field on EmailMessageData is not set by the sender. It is populated only during deserialization in ServiceBusMessageSerializer.DeserializeEmailMessageData from the broker’s own IReceivedBrokeredMessage.DeliveryCount property. Back-end consumers can use this value to detect and handle repeated delivery attempts (e.g., to avoid sending duplicate emails after transient failures).