Channel Schema
A connector without a schema is a black box: you have no way to know what parameters it needs, what capabilities it offers, what message shapes it accepts, or what authentication it requires without reading its source code. The schema solves this by making every connector self-describing.
IChannelSchema is the contract that defines a connector's capabilities, requirements, and constraints. Every connector has exactly one schema that describes:
What it can do — capabilities like send, receive, status query, bulk messaging
What it needs — connection parameters like API keys, endpoints, timeouts
What it accepts — endpoint types, content types, message property constraints
How it authenticates — supported authentication methods
This contract serves multiple purposes: it drives pre-flight validation so errors are caught before provider API calls, it enables the schema registry for runtime discovery, it provides the blueprint for schema derivation (environment-specific restrictions), and it serves as living documentation that evolves with the connector.
ChannelSchema is the built-in fluent implementation. You construct it inline, via a schema factory, or derive it from a base schema (see Schema derivation).
IChannelSchema interface
public interface IChannelSchema
{
string ChannelProvider { get; } // e.g., "Twilio"
string ChannelType { get; } // e.g., "SMS"
string Version { get; } // e.g., "1.0.0"
string? DisplayName { get; }
bool IsStrict { get; }
ChannelCapability Capabilities { get; }
IReadOnlyList<ChannelEndpointConfiguration> Endpoints { get; }
IReadOnlyList<ChannelParameter> Parameters { get; }
IReadOnlyList<MessagePropertyConfiguration> MessageProperties { get; }
IReadOnlyList<MessageContentType> ContentTypes { get; }
IReadOnlyList<AuthenticationConfiguration> AuthenticationConfigurations { get; }
}The logical identity ChannelProvider/ChannelType/Version (e.g., "Twilio/SMS/1.0.0") is used for schema discovery and compatibility checks.
Minimal schema
Capabilities
Not every connector supports every operation. An SMS connector may not support receiving messages; a push notification connector may not support delivery status queries. Capabilities are flags that advertise exactly what operations the connector supports. The base class uses these flags to guard operations at runtime: calling a method whose capability is not set throws NotSupportedException, failing fast instead of producing confusing errors at the provider level.
Capabilities are flags that advertise what operations the connector supports. When you call a method on IChannelConnector whose capability is not set, the base class throws NotSupportedException.
SendMessages
SendMessageAsync
ReceiveMessages
ReceiveMessagesAsync
MessageStatusQuery
GetMessageStatusAsync
HandleMessageState
ReceiveMessageStatusAsync
MediaAttachments
Content type checks for MediaContent
Templates
Content type checks for TemplateContent
BulkMessaging
SendBatchAsync
HealthCheck
GetHealthAsync
Add individual capabilities:
Parameters
Connectors need configuration to operate: API keys, endpoint URLs, timeouts, feature flags. Parameters declare what configuration each connector expects, including whether the value is required, what data type it should be, and any constraints on acceptable values. This enables the framework to validate connection settings at startup (before any provider call) and to generate documentation automatically.
Parameters define what connection settings the connector needs at runtime. Each parameter has a name, data type, and optional constraints.
DataType enum
String
string
Integer
int
Number
double
Boolean
bool
Sensitive parameters
Mark secrets like tokens and passwords as sensitive — the framework redacts their values in logs:
Updating parameters after creation
For derived schemas that need to change parameter properties:
Endpoint configurations
Different connectors handle different addressing schemes: SMS uses phone numbers, email uses email addresses, push notifications use device tokens, and chat apps use user or chat identifiers. Endpoint configurations declare which addressing schemes the connector supports and whether each can be used for sending, receiving, or both. The schema validator uses this to reject messages that use an endpoint type the connector cannot handle.
Standard endpoints
Declare which EndpointType values the connector handles and in which direction:
Use the simpler overload when you accept both directions:
For connectors that accept any endpoint type (e.g., a generic webhook relay):
Sender identity endpoints
The framework defines specialised sender types (see Message model) that each carry a specific EndpointType:
Sender type
EndpointType
When to expect on messages
SenderRef
Label
Before resolution — a logical name reference awaiting registry lookup
EmailSender
EmailAddress
After resolution — a resolved email sender
PhoneSender
PhoneNumber
After resolution — a resolved phone sender
AlphaNumericSender
Label
After resolution — a resolved alphanumeric sender ID
BotSender
Id
After resolution — a resolved bot identifier
Resolution-aware validation
When a message arrives at the connector (SendMessageAsync), sender resolution happens before validation:
This means:
SenderRef(which hasEndpointType.Label) is replaced by the concrete sender before validation runs. The validator sees the resolved endpoint, not the reference. You do not need to declareEndpointType.Labelin the schema unless you also sendAlphaNumericSenderendpoints after resolution.Resolved senders must still match the schema's endpoint declarations. For example, if the schema only declares
EndpointType.PhoneNumberand the registry returns anEmailSender, validation fails even though resolution succeeded.
A connector that accepts only phone senders and supports SenderRef resolution:
A connector that also accepts alphanumeric senders (which stay as Label after resolution) must declare both:
ChannelEndpointConfiguration properties
Type
(required)
The endpoint type
CanSend
true
This endpoint type can be used as sender
CanReceive
true
This endpoint type can be used as receiver
IsWildcard
false
Accepts any endpoint type
Content type restrictions
A connector that handles SMS cannot deliver HTML email; a connector that handles push notifications cannot process multipart content with file attachments. Content type restrictions declare which message payload formats the connector accepts. When you build a message with a content type the connector does not support, validation catches the mismatch before any provider API call.
Declare which MessageContentType values the connector supports:
Available content types:
PlainText
TextContent
Html
HtmlContent
Multipart
MultipartContent
Template
TemplateContent
Media
MediaContent
Json
JsonContent
Binary
BinaryContent
Location
LocationContent
If a message's content type is not in the schema's list, ValidateMessage returns a validation error.
Message property configurations
Connectors accept per-message configuration beyond sender, receiver, and content. Email needs a subject line, SMS supports validity periods, push notifications expect titles and badges. Message property configurations declare which properties the connector recognizes and what validation rules apply — turning what would be undocumented magic strings into a formal contract.
Define what message properties the connector understands, along with validation rules:
MessagePropertyConfiguration properties
Name
Property key name
DataType
Expected data type (String, Integer, Number, Boolean)
IsRequired
Property must be present
IsSensitive
Value should be redacted in logs
MinLength / MaxLength
String length constraints
MinValue / MaxValue
Numeric range constraints
Pattern
Regex pattern for string values
AllowedValues
Explicit set of allowed values
CustomValidator
Func<object?, IEnumerable<ValidationResult>> for arbitrary validation
Custom validator example
Authentication configurations
Every provider has its own authentication model: API keys, bearer tokens, basic auth, OAuth 2.0 client credentials, or custom schemes. Authentication configurations declare which methods the connector supports and which ConnectionSettings parameters each method requires. The authentication manager uses these declarations to automatically select the right provider and credential flow at initialization time.
Adding a scheme with default field aliases
This uses sensible defaults for the scheme (for Basic: Username, Password, AccountSid, AuthToken, User, Pass, ClientId, ClientSecret). The parameters are not automatically added as schema parameters — use AddAuthenticationConfiguration() with explicit fields to get auto-registration.
Adding an explicit configuration (recommended)
Fields with AuthenticationRole = "principal" are automatically registered as optional ChannelParameter entries in the schema, making them recognized by strict-mode validation.
Multiple fallback options
The authentication manager iterates through the schema's configurations and selects the first one where IsSatisfiedBy(ConnectionSettings) returns true:
Role-based field matching
The IsSatisfiedBy method uses the field's AuthenticationRole to determine whether a configuration is fulfilled:
Configs with principal + credential roles: at least one of each must be present
Configs with only principal roles: at least one must be present
Auxiliary roles: all must be present
This allows flexible configurations (e.g., Username+Password OR AccountSid+AuthToken for Basic) without requiring all possible field names to be populated.
Authentication configuration properties
Scheme
The AuthenticationScheme identifier
DisplayName
Human-readable name for the method
Fields
List of AuthenticationField definitions with roles
See Authentication for the full authentication model.
Strict vs flexible mode
By default, schemas enforce strict validation: any parameter or message property that is not explicitly declared is rejected as an error. This catches typos, deprecated keys, and misconfigurations early.
Some scenarios call for leniency — staged rollouts where new properties are added gradually, generic connectors that forward arbitrary metadata, or integration with systems that send extra fields. Flexible mode allows unknown keys to pass through without error.
By default, schemas are strict: any unknown parameter in ConnectionSettings or unknown property on a message produces a validation error.
Flexible mode allows extra keys — useful during staged rollouts or when the connector forwards arbitrary metadata:
Schema factory pattern
Connectors declare their schema via the [ChannelSchema] attribute:
The attribute points to a class that implements IChannelSchema or IChannelSchemaFactory:
Or a static instance:
Schema discovery
When you call AddConnector<T>(), the messaging builder:
Reads the
[ChannelSchema]attribute onTConnectorInstantiates the schema type (or calls
CreateSchema()on the factory)Registers the schema in
IChannelSchemaRegistryPasses the schema to the connector constructor
Schema override at registration time
This is useful for testing, environment-specific customization, or feature-tier restrictions.
Full schema example
Last updated