Katalyst.Library.Email 1.0.0

dotnet add package Katalyst.Library.Email --version 1.0.0
                    
NuGet\Install-Package Katalyst.Library.Email -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Katalyst.Library.Email" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Katalyst.Library.Email" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Katalyst.Library.Email" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Katalyst.Library.Email --version 1.0.0
                    
#r "nuget: Katalyst.Library.Email, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Katalyst.Library.Email@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Katalyst.Library.Email&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Katalyst.Library.Email&version=1.0.0
                    
Install as a Cake Tool

Katalyst.Library.Email

Overview

Library.Email is a small abstraction for transactional email that sends through Resend using the official Resend .NET SDK. The package is structured so additional providers can be added later; today only Resend is implemented.

Capabilities

  • Unified API — Work against IEmailSender instead of wiring the Resend client and Razor rendering directly.
  • Configuration-driven provider — Choose Resend via Email:Provider (comparison is case-insensitive). Unsupported values throw NotSupportedException at registration time.
  • Shared defaults — Optional default From address and optional Razor base path for relative .cshtml paths.
  • Plain, template, and Razor mailSendAsync (RegularMail), SendTemplatedAsync (TemplateMail with a published Resend template id), SendRazorTemplatedAsync (RazorMail via RazorLight).
  • Send outcomes — Each send returns a named value tuple (bool Succeeded, string? Reason, string? BodyContent, bool? BodyIsHtml) (no separate result type). Succeeded is true when the provider accepted the message; Reason is non-null on failure. BodyContent / BodyIsHtml echo the payload when useful (for example rendered Razor HTML). EmailConstants.EmailStatus remains a shared vocabulary for sent, failed, pipeline values such as queued, and webhook-style states when you persist or map provider events yourself.

Configuration shape

{
  "Email": {
    "Provider": "Resend",
    "DefaultFrom": {
      "Address": "noreply@mail.yourdomain.com",
      "Name": "Your App"
    },
    "RazorTemplateBasePath": "Templates",
    "Resend": {
      "ApiKey": "re_xxxxxxxxxxxxxxxxxxxxxxxx",
      "IsApiKeyEncrypted": false
    }
  }
}

Everything under the Email section is bound to EmailSettings (Provider, DefaultFrom, optional RazorTemplateBasePath). Resend-specific options live under Email:Resend and map to ResendEmailSettings (ApiKey, IsApiKeyEncrypted).

  • Provider — Must be Resend for the current release. AddAppEmail calls AddResendEmailProvider when it matches; otherwise registration fails with NotSupportedException.
  • DefaultFrom — Required default sender when a message omits From; the address should use a domain you verified in Resend.
  • RazorTemplateBasePath — Optional folder prefix for relative template paths in SendRazorTemplatedAsync. Relative paths combine with this value, then resolve under AppContext.BaseDirectory when the result is not already absolute (see BaseEmailSender).
  • ResendApiKey is the dashboard token (re_...) or ciphertext from Email.Utility encrypt (see Resend section). IsApiKeyEncrypted — set false for a plaintext key from the dashboard; when omitted or not false, the runtime treats ApiKey as encrypted and decrypts via IEmailSecretResolver (default EmailSecretResolver).

Calling AddAppEmail registers IEmailSender for the selected provider, binds options with Configure<TOptions>(configuration.GetSection(...)), ValidateDataAnnotations(), and ValidateOnStart(). For Resend it also registers IEmailSecretResolver, IRazorLightEngine (file-system project at AppContext.BaseDirectory, memory cache), HttpClient for ResendClient, and IResend / ResendEmailSender.

IEmailSender methods

Method Description
SendAsync Sends RegularMail (subject, body, HTML vs plain, recipients, optional attachments path through the model as supported by the provider).
SendTemplatedAsync Sends TemplateMail using a published Resend template id (GUID string) and TemplateVariables.
SendRazorTemplatedAsync Renders a .cshtml file with RazorLight, then sends like a regular HTML message. Template path resolution uses RazorTemplateBasePath when the path is not absolute.

Each method returns Task<(bool Succeeded, string? Reason, string? BodyContent, bool? BodyIsHtml)>: check Succeeded, read Reason on failure, and use BodyContent / BodyIsHtml when you need the body that was sent (especially Razor HTML).


Resend provider

When it runs

Selected when Email:Provider is Resend. Registers ResendEmailSender as IEmailSender, ResendClient behind IResend, and the Razor engine described above.

How it works

  • Uses the Resend .NET package with an API token resolved from Email:Resend:ApiKey (plain or decrypted).
  • ThrowExceptions is enabled on the client; the sender still catches failures and returns Succeeded: false with Reason set from the provider error.
  • Razor templates are rendered with CompileRenderAsync(absoluteTemplatePath, model) after path resolution.

Domain and dashboard setup

  1. Create an API key in the Resend dashboard (API Keys).
  2. Add and verify a domain (DNS: SPF, DKIM, etc.) until the domain shows verified.
  3. Use a from address on that verified domain (for example noreply@mail.example.com).
  4. Optional — Resend templates for SendTemplatedAsync: create and publish a template and copy its template UUID.

Official help: Resend documentation.

Configuration example (plaintext API key)

{
  "Email": {
    "Provider": "Resend",
    "DefaultFrom": {
      "Address": "noreply@mail.yourdomain.com",
      "Name": "Your App"
    },
    "Resend": {
      "ApiKey": "re_xxxxxxxxxxxxxxxxxxxxxxxx",
      "IsApiKeyEncrypted": false
    }
  }
}

Configuration example (encrypted API key in config)

Use ciphertext from Email.Utility (see Email/Email.Utility README). Omit IsApiKeyEncrypted or set it so the resolver decrypts (see EmailSecretResolver).

{
  "Email": {
    "Provider": "Resend",
    "DefaultFrom": {
      "Address": "noreply@mail.yourdomain.com",
      "Name": "Your App"
    },
    "Resend": {
      "ApiKey": "<paste ciphertext>"
    }
  }
}

Environment variables (double underscore) override JSON when both are loaded, for example:

  • Email__Provider
  • Email__DefaultFrom__Address
  • Email__DefaultFrom__Name
  • Email__RazorTemplateBasePath
  • Email__Resend__ApiKey
  • Email__Resend__IsApiKeyEncrypted

Requirements

  • .NET 10 SDK (library targets net10.0).
  • A reachable Resend account, verified sending domain, and valid ApiKey (or decryptable ciphertext and matching IsApiKeyEncrypted behavior).

Examples

ASP.NET Core (minimal hosting)

using Library.Email;
using Library.Email.Contracts;
using Library.Email.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddAppEmail(builder.Configuration);

var app = builder.Build();

app.MapPost("/notify/welcome", async (string to, IEmailSender emailSender, CancellationToken cancellationToken) =>
{
    var message = new RegularMail
    {
        Subject = "Welcome",
        To = [new EmailContact(to)],
        Body = "<p>Thanks for signing up.</p>",
        IsHtml = true,
    };

    var result = await emailSender.SendAsync(message, cancellationToken);
    if (result.Succeeded)
        return Results.Ok();
    return Results.BadRequest(new { result.Reason });
});

app.Run();

appsettings.json

{
  "Email": {
    "Provider": "Resend",
    "DefaultFrom": {
      "Address": "noreply@mail.yourdomain.com",
      "Name": "Your App"
    },
    "RazorTemplateBasePath": "Templates",
    "Resend": {
      "ApiKey": "re_xxxxxxxxxxxxxxxxxxxxxxxx",
      "IsApiKeyEncrypted": false
    }
  }
}

Use User Secrets or environment variables for ApiKey in development.

Resend template (TemplateMail)

Resend supplies visible content from the template. TemplateMail still has Subject on BaseMail; the Resend implementation aligns with the provider (use string.Empty if the template owns the subject line).

var message = new TemplateMail
{
    Subject = string.Empty,
    TemplateId = "00000000-0000-0000-0000-000000000000",
    To = [new EmailContact("user@example.com")],
    TemplateVariables = new Dictionary<string, object>
    {
        ["name"] = "Alex",
        ["resetLink"] = "https://app.example.com/reset?token=...",
    },
};

var result = await emailSender.SendTemplatedAsync(message);

Local Razor .cshtml (RazorMail)

dynamic model = new { name = "Alex", resetLink = "https://app.example.com/reset?token=..." };

var message = new RazorMail
{
    Subject = "Welcome",
    To = [new EmailContact("user@example.com")],
    TemplatePath = "Welcome.cshtml",
    Data = model,
};

var result = await emailSender.SendRazorTemplatedAsync(message);

Set Email:RazorTemplateBasePath when using relative template paths; absolute TemplatePath values are supported.

Class library or tests (manual Configuration + ServiceCollection)

using Library.Email;
using Library.Email.Contracts;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

var configuration = new ConfigurationBuilder()
    .SetBasePath(AppContext.BaseDirectory)
    .AddJsonFile("appsettings.json", optional: false)
    .AddInMemoryCollection(new Dictionary<string, string?>
    {
        ["Email:Provider"] = "Resend",
        ["Email:DefaultFrom:Address"] = "noreply@mail.yourdomain.com",
        ["Email:Resend:ApiKey"] = "re_...",
        ["Email:Resend:IsApiKeyEncrypted"] = "false",
    })
    .Build();

var services = new ServiceCollection();
services.AddSingleton<IConfiguration>(configuration);
services.AddLogging();
services.AddAppEmail(configuration);

await using var provider = services.BuildServiceProvider();
var emailSender = provider.GetRequiredService<IEmailSender>();

Custom implementation

Register your own IEmailSender instead of calling AddAppEmail:

services.AddSingleton<IEmailSender, MyCustomEmailSender>();

EmailConstants

EmailConstants.cs defines EmailConstants.EmailStatus: lowercase strings for the send pipeline (pending, draft, scheduled, queued, sending, sent, failed, cancelled) and delivery / webhook-style states (delivered, deferred, bounced, complained). Send methods surface success as Succeeded: true and failure as Succeeded: false with a Reason string; map those to sent / failed (or your own model) when you persist. The full EmailStatus set is available for dashboards or provider webhooks.


Referenced packages

The project references Microsoft.Extensions.Caching.Memory, Microsoft.Extensions.Configuration.Abstractions, Microsoft.Extensions.DependencyInjection.Abstractions, Microsoft.Extensions.Http, Microsoft.Extensions.Options, Microsoft.Extensions.Options.ConfigurationExtensions, Microsoft.Extensions.Options.DataAnnotations, Microsoft.Extensions.Logging, RazorLight, and Resend. Hosting apps typically already reference Microsoft.AspNetCore.App, which supplies WebApplicationBuilder and related hosting APIs.

NuGet: package id Katalyst.Library.Email.

dotnet add package Katalyst.Library.Email

Repository layout

  • Email/Library.Email — this library (packable; PackageReadmeFile is this README).
  • Email/Email.Utility — development CLI (encrypt, decrypt, send smoke tests; not published as a package). See Email/Email.Utility/README.md.
  • Email/Email.slnx — solution file.
dotnet build Email/Email.slnx
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 113 5/14/2026