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
<PackageReference Include="Katalyst.Library.Email" Version="1.0.0" />
<PackageVersion Include="Katalyst.Library.Email" Version="1.0.0" />
<PackageReference Include="Katalyst.Library.Email" />
paket add Katalyst.Library.Email --version 1.0.0
#r "nuget: Katalyst.Library.Email, 1.0.0"
#:package Katalyst.Library.Email@1.0.0
#addin nuget:?package=Katalyst.Library.Email&version=1.0.0
#tool nuget:?package=Katalyst.Library.Email&version=1.0.0
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
IEmailSenderinstead of wiring the Resend client and Razor rendering directly. - Configuration-driven provider — Choose
ResendviaEmail:Provider(comparison is case-insensitive). Unsupported values throwNotSupportedExceptionat registration time. - Shared defaults — Optional default
Fromaddress and optional Razor base path for relative.cshtmlpaths. - Plain, template, and Razor mail —
SendAsync(RegularMail),SendTemplatedAsync(TemplateMailwith a published Resend template id),SendRazorTemplatedAsync(RazorMailvia RazorLight). - Send outcomes — Each send returns a named value tuple
(bool Succeeded, string? Reason, string? BodyContent, bool? BodyIsHtml)(no separate result type).Succeededistruewhen the provider accepted the message;Reasonis non-null on failure.BodyContent/BodyIsHtmlecho the payload when useful (for example rendered Razor HTML).EmailConstants.EmailStatusremains a shared vocabulary forsent,failed, pipeline values such asqueued, 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 beResendfor the current release.AddAppEmailcallsAddResendEmailProviderwhen it matches; otherwise registration fails withNotSupportedException.DefaultFrom— Required default sender when a message omitsFrom; the address should use a domain you verified in Resend.RazorTemplateBasePath— Optional folder prefix for relative template paths inSendRazorTemplatedAsync. Relative paths combine with this value, then resolve underAppContext.BaseDirectorywhen the result is not already absolute (seeBaseEmailSender).Resend—ApiKeyis the dashboard token (re_...) or ciphertext fromEmail.Utility encrypt(see Resend section).IsApiKeyEncrypted— setfalsefor a plaintext key from the dashboard; when omitted or notfalse, the runtime treatsApiKeyas encrypted and decrypts viaIEmailSecretResolver(defaultEmailSecretResolver).
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). ThrowExceptionsis enabled on the client; the sender still catches failures and returnsSucceeded: falsewithReasonset from the provider error.- Razor templates are rendered with
CompileRenderAsync(absoluteTemplatePath, model)after path resolution.
Domain and dashboard setup
- Create an API key in the Resend dashboard (API Keys).
- Add and verify a domain (DNS: SPF, DKIM, etc.) until the domain shows verified.
- Use a from address on that verified domain (for example
noreply@mail.example.com). - 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__ProviderEmail__DefaultFrom__AddressEmail__DefaultFrom__NameEmail__RazorTemplateBasePathEmail__Resend__ApiKeyEmail__Resend__IsApiKeyEncrypted
Requirements
- .NET 10 SDK (library targets
net10.0). - A reachable Resend account, verified sending domain, and valid
ApiKey(or decryptable ciphertext and matchingIsApiKeyEncryptedbehavior).
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;PackageReadmeFileis this README).Email/Email.Utility— development CLI (encrypt,decrypt, send smoke tests; not published as a package). SeeEmail/Email.Utility/README.md.Email/Email.slnx— solution file.
dotnet build Email/Email.slnx
| Product | Versions 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. |
-
net10.0
- Microsoft.Extensions.Caching.Memory (>= 10.0.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Http (>= 10.0.0)
- Microsoft.Extensions.Logging (>= 10.0.0)
- Microsoft.Extensions.Options (>= 10.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.0)
- Microsoft.Extensions.Options.DataAnnotations (>= 10.0.0)
- RazorLight (>= 2.3.1)
- Resend (>= 0.5.0)
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 |