Message Model
At the core of the framework is the IMessage interface and its concrete implementation Message. Every piece of data that flows through the system — whether it is an SMS text, an email with attachments, a push notification, or a chat message — is represented as an IMessage. This unified representation is what makes multi-channel messaging possible: the same message object can be sent through different connectors without transformation.
Messages can be constructed in two ways:
MessageBuilder— a fluent builder class withFrom()/To()methods,WithText()/WithHtml()for content, and a final.Build()callExplicit constructors — use
new Message { ... }object initializers or the copy constructornew Message(original)
The message carries five pieces of information:
Identity — a unique
Idstring you assignRouting —
SenderandReceiverendpoints, each tagged with their typeContent — the payload, wrapped in a typed content class
Metadata — a dictionary of properties for per-message configuration
IMessage interface
public interface IMessage
{
string Id { get; }
IEndpoint? Sender { get; }
IEndpoint? Receiver { get; }
IMessageContent? Content { get; }
IDictionary<string, IMessageProperty>? Properties { get; }
}The concrete Message class implements this interface with mutable settable properties, a parameterless constructor, and a copy constructor (new Message(IMessage)).
Basic construction
Copy constructor
Create a new Message from an existing IMessage, producing an independent copy:
This deep-copies the endpoint, content, and properties.
Endpoints
An endpoint identifies who sent the message and who should receive it. Unlike passing raw strings, every endpoint carries a type tag that tells the schema validator what kind of address it is. This prevents mistakes like using an email address where a phone number is expected, and it enables connectors to enforce endpoint-specific rules.
Endpoint implements IEndpoint and provides typed static factories. Every endpoint carries a type tag and an address string.
Factory methods
Sender identities
Beyond Endpoint, the framework provides specialised sender types that implement both IEndpoint and ISender. These carry additional semantic meaning and can be resolved at send time from the sender registry.
Type
EndpointType
Description
SenderRef
Label
Logical name reference, resolved at send time via ISenderResolver
EmailSender
EmailAddress
Email sender with an optional display name
PhoneSender
PhoneNumber
Phone number sender with an optional display name
AlphaNumericSender
Label
Alphanumeric sender ID (e.g. brand name for SMS)
BotSender
Id
Bot identifier (e.g. Telegram bot ID)
SenderRef — identity reference
SenderRef carries a logical name that the connector resolves at send time. The resolution flows through ISenderResolver → ISenderRepository<Sender> → repository, letting you decouple message composition from sender configuration.
The FromSender() extension method on MessageBuilder (from Ratatosk.Senders) is a shorthand:
The connector (ChannelConnectorBase.SendMessageAsync) calls ResolveSenderAsync which uses ISenderResolver to look up the name in the registry and replace the SenderRef with the concrete sender before validation and dispatch.
EmailSender, PhoneSender, AlphaNumericSender, BotSender
These are direct sender types that carry both an address and optional display name. They are used when you know the sender details at message-construction time but want typed semantics:
Setting sender and receiver
Content types
Different channels support different kinds of content. SMS carries plain text, email supports HTML and attachments, chat apps handle media and locations, and push notifications can carry structured JSON payloads. The framework models this with separate content classes, each implementing IMessageContent. When you build a message, you choose the content type that matches your channel — and the schema validator confirms the connector supports it.
Twelve content classes implement IMessageContent. The base class MessageContent provides a static factory MessageContent.Create(IMessageContent?) that auto-selects the correct subclass.
TextContent
For plain text messages — the most common content type (SMS, chat, plain-text email):
Properties: Text, Encoding (optional, defaults to UTF-8).
HtmlContent
For rich HTML content, typically used in email:
Properties: Html, Attachments (list of MessageAttachment).
MediaContent
For images, audio, video, and documents:
MediaType enum: Image, Audio, Video, Document, File.
Properties: MediaType, FileName, FileUrl, Data.
BinaryContent
For raw binary payloads with a MIME type:
Properties: RawData, MimeType.
JsonContent
For structured JSON payloads:
Properties: Json (string).
LocationContent
For geographical coordinates, used in chat channels:
Properties: Latitude, Longitude, HorizontalAccuracy, LivePeriod, Heading, ProximityAlertRadius.
TemplateContent
For provider-side template rendering. The template ID and parameters are sent to the provider, which merges them server-side:
Typical use: SendGrid dynamic templates, Twilio WhatsApp templates, Facebook message templates.
Properties: TemplateId, Parameters.
MultipartContent
Combine multiple content parts into a single message (e.g., text + image):
Each part can be any IMessageContentPart implementation: TextContentPart, HtmlContentPart.
ButtonContent
For sending interactive buttons in chat channels. Each button has a Text, ButtonType (Url, Postback, or PhoneNumber), and an optional Value (URL, callback data, or phone number):
Properties: Text, ButtonType, Value.
QuickReplyContent
For offering a single quick, one-tap reply option that appears above the keyboard in chat apps:
Properties: Title, Payload, ImageUrl.
CarouselContent
For sending a horizontal scrollable set of cards, each with an image, title, subtitle, and optional buttons (Facebook Messenger). Use the sub-builder API for a fluent construction:
CarouselCard properties: ImageUrl, Title, Subtitle, Buttons. CarouselContent properties: Cards (read-only), AddCard()/RemoveCard()/ClearCards() for mutation.
ListPickerContent
For sending a vertically-scrollable list of items the user can pick from (Facebook Messenger). Use the sub-builder API for a fluent construction:
ListPickerStyle values: Inlined (default), Compact, Large. ListPickerItem properties: Title, Description, ImageUrl, Payload. ListPickerContent properties: Title, Subtitle, Style, Items.
Channel support
ButtonContent
Button template
InlineKeyboardMarkup
QuickReplyContent
Quick Replies
ReplyKeyboardMarkup (one-time)
CarouselContent
Generic template
—
ListPickerContent
List template
—
Carousel and List picker content throw NotSupportedException on Telegram.
Message properties
Not everything about a message fits neatly into sender, receiver, and content. Email needs a subject line, SMS has validity periods and max prices, push notifications carry badges and sounds, and every provider has its own set of per-message knobs. Properties are the escape hatch — a key-value dictionary on the message that connectors interpret for channel-specific configuration.
Properties are arbitrary key-value metadata attached to a message. They carry per-message configuration that the connector interprets.
Single property
Bulk properties
With MessageProperty objects
Use MessageProperty.Sensitive(name, value) to mark a property that should be redacted in logs.
Known constants
Predefined property keys for common scenarios:
KnownMessageProperties.Subject
"subject"
Email subject line
KnownMessageProperties.RemoteMessageId
"remoteMessageId"
Provider-assigned message ID
KnownMessageProperties.ReplyTo
"replyTo"
Message being replied to
KnownMessageProperties.CorrelationId
"correlationId"
Cross-channel correlation
These are convenience wrappers around With(key, value) — they produce the same result.
Message batches
When you need to send many messages at once, individual SendMessageAsync calls create overhead. Many providers offer a bulk API that accepts multiple messages in a single HTTP request. The MessageBatch type wraps this pattern: collect your messages into a batch, send once, and get per-message results back.
For sending multiple messages in a single batch:
IMessageBatch also supports a Properties dictionary for batch-level metadata.
Message status lifecycle
A message passes through several states between creation and delivery. The framework models these with MessageStatus, from initial receipt through queuing, sending, delivery, and optional read receipts. Status updates arrive either by polling (GetMessageStatusAsync) or via webhook callbacks (ReceiveMessageStatusAsync).
The status lifecycle:
StatusUpdatesResult captures the status history of a message, used by GetMessageStatusAsync queries and ReceiveMessageStatusAsync webhook receivers.
IMessageProperty
Represents a single named property value:
The MessageProperty class implements this and provides .IsSensitive to control log redaction.
IMessageContentPart
Content parts for multipart messages. The TextContentPart and HtmlContentPart classes implement both the content interface and the content-type-specific interface:
MessageAttachment
Used by HtmlContent for embedding files:
Properties: Id, FileName, MimeType, Content (base64-encoded string).
Polymorphic JSON serialization
Message.Sender and Message.Receiver are typed as IEndpoint?. The IEndpoint interface carries [JsonDerivedType] for every concrete implementation, enabling round-trip JSON serialization without custom converters:
The $type discriminator tells System.Text.Json which concrete type to deserialize. The available discriminator values and their corresponding types are:
$type value
CLR type
Sender-friendly
endpoint
Endpoint
Generic endpoint — specify address and type
senderref
SenderRef
Identity reference — specify senderName, resolved at send time
email
EmailSender
Email sender — specify emailAddress and optional displayName
phone
PhoneSender
Phone sender — specify phoneNumber and optional displayName
alphanumeric
AlphaNumericSender
Alphanumeric ID — specify text
bot
BotSender
Bot sender — specify botId and optional displayName
When a message with a SenderRef is deserialized at a connector endpoint, the SenderRef is resolved through the sender identity pipeline before processing.
Last updated