Quickstart

Every interaction with the framework follows the same lifecycle regardless of which channel provider you use:

  1. Register — add the Ratatosk package and configure connectors in the DI container

  2. Build — construct an IMessage using MessageBuilder

  3. Send/Receive — call IMessagingClient.SendAsync() or IMessagingClient.ReceiveAsync()

  4. Handle — check .IsSuccess(), read the result value, or inspect .Error

This guide walks through the IMessagingClient approach — the recommended pattern for all applications. Connectors are infrastructure: you register them in DI and never instantiate them directly.

1. Create a project

dotnet new console -o MessagingDemo
cd MessagingDemo
dotnet add package Ratatosk
dotnet add package Ratatosk.Twilio

2. Send an SMS

Register the connector in DI, inject IMessagingClient, build a message, and send it:

// Program.cs
using Ratatosk;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddMessaging()
    .AddConnector<TwilioSmsConnector>("sms", cfg => cfg
        .WithSettings("Twilio"))
    .AddClient();

builder.Services.AddSingleton<NotificationService>();

var app = builder.Build();
app.Run();

// NotificationService.cs
public class NotificationService(IMessagingClient messagingClient)
{
    public async Task<string?> SendSmsAsync(string to, string text)
    {
        var message = new MessageBuilder()
            .WithId(Guid.NewGuid().ToString("n"))
            .FromPhone("+15550001111")
            .ToPhone(to)
            .WithText(text)
            .Build();

        var result = await messagingClient.SendAsync("sms", message);

        if (result.IsSuccess)
            return result.Value?.RemoteMessageId;

        throw new InvalidOperationException(
            $"SMS failed: {result.Error?.Message}");
    }
}

The client handles connector resolution, lazy initialization, caching, and disposal. You never interact with IChannelConnector directly.

3. Same code, different channel

Add another connector and send through it using the same client — only the channel name and endpoint types change:

The MessageBuilder, the IMessagingClient methods, and the OperationResult<T> pattern are identical across channels.

4. Receive inbound messages (webhook)

Channels like Twilio, Telegram, and Facebook push inbound messages via webhooks. The client's ReceiveAsync normalises the payload into IMessage objects:

Status callbacks (delivery receipts, read receipts) are handled through ReceiveMessageStatusAsync on the client, with the same MessageSource pattern.

5. Advanced resolution strategies

The IMessagingClient supports three resolution strategies that can be mixed in the same application.

Resolution by name (named connectors)

Register multiple channels with distinct names and send through each by name:

Resolution by type (anonymous connectors)

When you register a single unnamed connector, resolve it through the generic overload:

Runtime resolution

For applications where connection settings are loaded at runtime (from a database, API, or external configuration), register the connector type at startup without providing settings, and supply the settings at the call site:

The same pattern works with type parameters for anonymous runtime resolution:

Auto-initialization

The client automatically initializes the connector on first use. To disable this (for scenarios where initialization is handled externally), configure MessagingClientOptions:

Next steps

Last updated