Skip to content

Effects Composition

The EmailModule is an [EffectsModule(typeof(IEmailService))] that generates Eff<RT, T> effect methods for every IEmailService method. This enables functional composition with built-in OpenTelemetry tracing.

Capability Interface

The generator produces:

public interface IHasEmailService
{
    IEmailService EmailService { get; }
}

Any runtime implementing IHasEmailService can resolve the email service at execution time.

Effect Methods

Generated as nested static members on EmailModule:

public static partial class EmailModule
{
    public static partial class Email
    {
        public static Eff<RT, EmailResult> SendAsync<RT>(EmailMessage message)
            where RT : IHasEmailService => ...

        public static Eff<RT, IReadOnlyList<EmailResult>> SendBatchAsync<RT>(
            IReadOnlyList<EmailMessage> messages)
            where RT : IHasEmailService => ...
    }
}

Composing with [Runtime] and [Uses]

Wire into your runtime:

[Runtime]
public sealed partial class AppRuntime;
// EmailModule is auto-discovered if in the same assembly
// Use [Uses(typeof(EmailModule))] for external assemblies

Effect Pipeline Examples

Send a single email

var sendWelcome =
    from result in EmailModule.Email.SendAsync<AppRuntime>(new EmailMessage
    {
        From = new EmailAddress("noreply@example.com"),
        To = [new EmailAddress(user.Email)],
        Subject = "Welcome!",
        HtmlBody = $"<h1>Welcome, {user.Name}</h1>"
    })
    select result;

Send with error handling

var sendWithRetry =
    from result in EmailModule.Email.SendAsync<AppRuntime>(message)
    from _ in result.Status == EmailStatus.Rejected
        ? FailEff<AppRuntime, Unit>(Error.New(502, $"Email rejected: {result.ErrorMessage}"))
        : unitEff
    select result;

Compose with other effects

// Send order confirmation + update audit log
var confirmOrder =
    from order in AppStore.Orders.GetById<AppRuntime>(orderId)
        .Require(Error.New(404, "Order not found"))
    from emailResult in EmailModule.Email.SendAsync<AppRuntime>(new EmailMessage
    {
        From = new EmailAddress("orders@example.com"),
        To = [new EmailAddress(order.CustomerEmail)],
        Subject = $"Order #{order.Id} Confirmed",
        HtmlBody = RenderConfirmation(order),
        Tags = new Dictionary<string, string> { ["order_id"] = order.Id.ToString() }
    })
    from _ in AuditModule.Audit.LogAsync<AppRuntime>(
        "Order", order.Id.ToString(), "confirmation_sent")
    select emailResult;

Batch send with tracking

var sendCampaign =
    from recipients in AppStore.Subscribers.GetActive<AppRuntime>()
    from results in EmailModule.Email.SendBatchAsync<AppRuntime>(
        recipients.Select(r => new EmailMessage
        {
            From = new EmailAddress("campaign@example.com"),
            To = [new EmailAddress(r.Email)],
            Subject = campaign.Subject,
            HtmlBody = campaign.Body,
            Tags = new Dictionary<string, string> { ["campaign_id"] = campaign.Id.ToString() }
        }).ToList())
    select results;