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 with From()/To() methods, WithText()/WithHtml() for content, and a final .Build() call

  • Explicit constructors — use new Message { ... } object initializers or the copy constructor new Message(original)

The message carries five pieces of information:

  • Identity — a unique Id string you assign

  • RoutingSender and Receiver endpoints, each tagged with their type

  • Content — 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 ISenderResolverISenderRepository<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

Content type
Facebook Messenger
Telegram Bot

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:

Constant
Key
Typical use

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