TailoredApps.Shared.Email¶
Description¶
This library provides a complete abstraction for sending email messages in .NET applications. It is built around the IEmailProvider interface, which can be swapped depending on the environment — in production use SmtpEmailProvider (sends via SMTP), in local development use EmailServiceToConsoleWriter (prints to console without actual delivery).
The library also includes a template-based email body building system (IMailMessageBuilder), supporting simple token substitution (DefaultMessageBuilder) or file-system templates with {{token}} placeholders (TokenReplacingMailMessageBuilder).
Built-in safeguard against accidental spam in non-production environments: when IsProd = false, all emails are redirected to the CatchAll address instead of real recipients.
Installation¶
DI registration¶
// Program.cs
using TailoredApps.Shared.Email;
// Register the SMTP provider
builder.Services.RegisterSmtpProvider();
// Optional: register the template builder
builder.Services.AddTransient<IMailMessageBuilder, TokenReplacingMailMessageBuilder>();
builder.Services.Configure<TokenReplacingMailMessageBuilderOptions>(options =>
{
options.Location = Path.Combine(builder.Environment.ContentRootPath, "EmailTemplates");
options.FileExtension = "html";
});
appsettings.json configuration¶
{
"Mail": {
"Providers": {
"Smtp": {
"Host": "smtp.example.com",
"Port": 587,
"UserName": "noreply@example.com",
"Password": "secret",
"From": "noreply@example.com",
"EnableSsl": true,
"IsProd": true,
"CatchAll": "dev@example.com"
}
}
}
}
Usage example¶
public class NotificationService
{
private readonly IEmailProvider _emailProvider;
private readonly IMailMessageBuilder _messageBuilder;
public NotificationService(
IEmailProvider emailProvider,
IMailMessageBuilder messageBuilder)
{
_emailProvider = emailProvider;
_messageBuilder = messageBuilder;
}
public async Task SendWelcomeEmailAsync(string recipientEmail, string userName)
{
var body = _messageBuilder.Build(
templateKey: "welcome.html",
variables: new Dictionary<string, string>
{
{ "UserName", userName },
{ "AppUrl", "https://myapp.example.com" }
},
templates: null // loaded from disk when Location is configured
);
var messageId = await _emailProvider.SendMail(
recipnet: recipientEmail,
topic: "Welcome to MyApp!",
messageBody: body,
attachments: null
);
Console.WriteLine($"Email sent, MessageId: {messageId}");
}
public async Task SendInvoiceAsync(
string recipientEmail,
string subject,
string htmlBody,
byte[] pdfBytes)
{
await _emailProvider.SendMail(
recipnet: recipientEmail,
topic: subject,
messageBody: htmlBody,
attachments: new Dictionary<string, byte[]>
{
{ "invoice.pdf", pdfBytes }
}
);
}
}
Email template (welcome.html)¶
<!DOCTYPE html>
<html>
<body>
<h1>Hello, {{UserName}}!</h1>
<p>Your account has been created. <a href="{{AppUrl}}">Click here</a> to sign in.</p>
</body>
</html>
API Reference¶
| Type | Kind | Description |
|---|---|---|
IEmailProvider |
Interface | Main contract: SendMail, GetMail |
SmtpEmailProvider |
Class | Sends via SMTP; options from SmtpEmailServiceOptions |
EmailServiceToConsoleWriter |
Class | Writes the email data to the console (dev/test) |
SmtpEmailServiceOptions |
Class | SMTP configuration: Host, Port, UserName, Password, From, IsProd, CatchAll |
IMailMessageBuilder |
Interface | Contract: Build(templateKey, variables, templates) |
DefaultMessageBuilder |
Class | Substitutes tokens in the template dictionary |
TokenReplacingMailMessageBuilder |
Class | Loads templates from the file system; {{token}} placeholders |
TokenReplacingMailMessageBuilderOptions |
Class | Location (template directory path), FileExtension |
SmtpEmailProviderExtensions.RegisterSmtpProvider |
Ext. method | Registers SmtpEmailProvider in DI |
SmtpEmailProviderExtensions.RegisterConsoleProvider |
Ext. method | Registers EmailServiceToConsoleWriter in DI |
🤖 AI Agent Prompt¶
## TailoredApps.Shared.Email — AI agent instructions
You are using the TailoredApps.Shared.Email library in a .NET project.
### Registration
```csharp
// Production (SMTP):
builder.Services.RegisterSmtpProvider();
// Development (console):
builder.Services.RegisterConsoleProvider();
// Template builder (optional):
builder.Services.AddTransient<IMailMessageBuilder, TokenReplacingMailMessageBuilder>();
builder.Services.Configure<TokenReplacingMailMessageBuilderOptions>(o => {
o.Location = "EmailTemplates/";
o.FileExtension = "html";
});
appsettings.json¶
"Mail": { "Providers": { "Smtp": {
"Host": "smtp.host.com", "Port": 587, "UserName": "user",
"Password": "pass", "From": "no-reply@app.com",
"EnableSsl": true, "IsProd": true, "CatchAll": "dev@app.com"
}}}
Usage¶
// Inject IEmailProvider + IMailMessageBuilder
var body = _builder.Build("template.html", variables, null);
await _emailProvider.SendMail(email, subject, body, attachments);
Rules¶
- When IsProd=false, every email goes to CatchAll — never to real recipients
- For tests inject IEmailProvider as a mock or use RegisterConsoleProvider
- Placeholders in TokenReplacing templates: {{TokenName}}
- The template key (
templateKey) must equal the template file name including its extension, e.g.template.html; an unknown key →KeyNotFoundException - The "Mail:Providers:Smtp" configuration section is required — a missing section throws
InvalidOperationException - Attachments: dictionary fileName → byte[] ```