Documentation

Utilities for different messaging formats and protocols for .NET applications. Right now, the library includes a Mailgun email service, but more features and…

Utilities for different messaging formats and protocols for .NET applications. Right now, the library includes a Mailgun email service, but more features and protocols may be added in the future, as well as support for more email providers.

Contributions are welcome!

Installation

Install the package via the .NET CLI:

dotnet add package ArturRios.Messaging

Or via the NuGet Package Manager:

Install-Package ArturRios.Messaging

Requirements

  • .NET 10.0 or later
  • Environment variables for service configuration (see Configuration)

Dependencies

PackagePurpose
ArturRios.OutputStructured operation result type (ProcessOutput)

Features

  • Email — send transactional emails via Mailgun with a clean async interface
  • Designed for dependency injection — register services through the standard IServiceCollection pattern
  • Returns structured ProcessOutput results (from ArturRios.Output) rather than throwing exceptions, making error handling predictable

Configuration

Mailgun Email Service

Set the following environment variables before calling SendEmailAsync:

VariableRequiredDefaultDescription
MAILGUN_API_KEYYesYour Mailgun private API key
MAILGUN_DOMAINYesYour verified Mailgun sending domain
MAILGUN_API_VERSIONNov3Mailgun API version used to build the request URL. When unset or blank, v3 is used

Requests are sent to https://api.mailgun.net/{MAILGUN_API_VERSION}/{MAILGUN_DOMAIN}/messages. Environment variables are read on every SendEmailAsync call, so changes take effect without recreating the service.

If MAILGUN_API_KEY or MAILGUN_DOMAIN is unset or blank, SendEmailAsync returns a failed ProcessOutput naming the missing variable and sends nothing — rather than issuing an unauthenticated request against an empty domain and reporting whatever Mailgun makes of it.

The credential is attached to each request, never to the client’s DefaultRequestHeaders. That matters because the documented registration is AddHttpClient, which hands the service a client it does not own: writing a credential onto that client’s defaults would race with concurrent sends and leave the Mailgun key attached to every later request the client makes.

Usage

Dependency Injection

Register MailgunEmailService with your DI container:

using ArturRios.Messaging.Email;

builder.Services.AddHttpClient<IEmailService, MailgunEmailService>();

Sending an Email

using ArturRios.Messaging.Email;

public class NotificationService(IEmailService emailService)
{
    public async Task NotifyAsync(string recipient)
    {
        var output = await emailService.SendEmailAsync(
            to: recipient,
            subject: "Welcome!",
            body: "Thanks for signing up."
        );

        if (!output.Success)
        {
            foreach (var error in output.Errors)
                Console.WriteLine(error);
        }
    }
}

Without Dependency Injection

using ArturRios.Messaging.Email;
using Microsoft.Extensions.Logging.Abstractions;

var service = new MailgunEmailService(NullLogger<MailgunEmailService>.Instance);
var output = await service.SendEmailAsync("to@example.com", "Hello", "World");

Class Diagram

classDiagram
    class IEmailService {
        <<interface>>
        +SendEmailAsync(to: string, subject: string, body: string) Task~ProcessOutput~
    }

    class MailgunEmailService {
        -ILogger~MailgunEmailService~ _logger
        -HttpClient _httpClient
        -string MailgunApiBaseUrl$
        -string MailgunMessagesEndpoint$
        +string ApiKeyVariable$
        +string DomainVariable$
        +string ApiVersionVariable$
        +string DefaultMailgunApiVersion$
        +MailgunEmailService(logger: ILogger~MailgunEmailService~, httpClient: HttpClient?)
        +SendEmailAsync(to: string, subject: string, body: string) Task~ProcessOutput~
        -GetApiVersion() string$
    }

    class ProcessOutput {
        <<ArturRios.Output>>
        +bool Success
        +IEnumerable~string~ Errors
        +AddError(message: string) void
    }

    IEmailService <|.. MailgunEmailService : implements
    MailgunEmailService ..> ProcessOutput : returns

Testing

The test suite is xUnit, and every test is named with the Given / When / Then pattern. Every test class carries a Category trait, so the two kinds can be run — and reported — separately:

dotnet test src/ArturRios.Messaging.sln --filter "Category=Unit"
dotnet test src/ArturRios.Messaging.sln --filter "Category=Functional"

Unit tests exercise the code in isolation against test doubles. Functional tests send through a real HTTP server on the loopback interface and inspect the request that arrives. CI runs the two as separate jobs, and both must pass before a pull request can be merged.

Versioning

Semantic Versioning (SemVer). Breaking changes result in a new major version. New methods or non-breaking behavior changes increment the minor version; fixes or tweaks increment the patch.

Build, test and publish

Use the official .NET CLI to build, test and publish the project and Git for source control. If you want, optional helper toolsets I built to facilitate these tasks are available:

This project is licensed under the MIT License. A copy of the license is available at LICENSE in the repository.