Lyo.Email
1.0.2
See the version list below for details.
dotnet add package Lyo.Email --version 1.0.2
NuGet\Install-Package Lyo.Email -Version 1.0.2
<PackageReference Include="Lyo.Email" Version="1.0.2" />
<PackageVersion Include="Lyo.Email" Version="1.0.2" />
<PackageReference Include="Lyo.Email" />
paket add Lyo.Email --version 1.0.2
#r "nuget: Lyo.Email, 1.0.2"
#:package Lyo.Email@1.0.2
#addin nuget:?package=Lyo.Email&version=1.0.2
#tool nuget:?package=Lyo.Email&version=1.0.2
Lyo.Email
A production-ready email service library for .NET with SMTP support, built on MailKit.
Features
- Clean API - Fluent builder pattern for constructing emails
- SMTP Support - Full SMTP support via MailKit
- Bulk Sending - Sequential bulk send over a single SMTP connection with per-message results
- Attachments - File attachments and a
ZipFileBuilderfor bundling multiple files into a single ZIP attachment - HTML & Text - Support for both HTML and plain text email bodies
- Error Handling - Comprehensive error handling with detailed results
- Logging - Built-in logging support via Microsoft.Extensions.Logging
- Metrics - Optional metrics collection for monitoring email operations
- Dependency Injection - Full support for .NET dependency injection
- Async/Await - Fully asynchronous API with cancellation token support
- Events - Events for email sent, bulk completed, and connection tested
- Validation - Automatic validation of required configuration options
Examples
1. Configure Email Options
{
"EmailServiceOptions": {
"Host": "smtp.example.com",
"Port": 587,
"UseSsl": true,
"DefaultFromAddress": "noreply@example.com",
"DefaultFromName": "My Application",
"Username": "smtp_username",
"Password": "smtp_password",
"EnableMetrics": false
}
}
1. Configure Email Options (2)
var options = new EmailServiceOptions
{
Host = "smtp.example.com",
Port = 587,
UseSsl = true,
DefaultFromAddress = "noreply@example.com",
DefaultFromName = "My Application",
Username = "smtp_username",
Password = "smtp_password",
EnableMetrics = false
};
2. Register Services
// In ConfigureServices(context, services):
services.AddEmailServiceFromConfiguration(context.Configuration);
// Override the configuration section name if needed (defaults to "EmailServiceOptions"):
// services.AddEmailServiceFromConfiguration(context.Configuration, "MySection");
2. Register Services (2)
services.AddEmailService(options =>
{
options.Host = "smtp.example.com";
options.Port = 587;
options.UseSsl = true;
options.DefaultFromAddress = "noreply@example.com";
options.DefaultFromName = "My Application";
options.Username = "smtp_username";
options.Password = "smtp_password";
});
2. Register Services (3)
services.AddEmailService(provider =>
{
var config = provider.GetRequiredService<IConfiguration>();
return new EmailServiceOptions
{
Host = config["Smtp:Host"],
Port = int.Parse(config["Smtp:Port"] ?? "587"),
UseSsl = bool.Parse(config["Smtp:UseSsl"] ?? "true"),
DefaultFromAddress = config["Smtp:DefaultFromAddress"]!,
DefaultFromName = config["Smtp:DefaultFromName"]!,
Username = config["Smtp:Username"],
Password = config["Smtp:Password"]
};
});
2. Register Services (4)
services.AddEmailService(options => {
options.Host = "smtp.example.com";
options.Port = 587;
options.DefaultFromAddress = "noreply@example.com";
options.DefaultFromName = "My Application";
});
3. Use the Service
public class MyService
{
private readonly IEmailService _emailService;
public MyService(IEmailService emailService)
{
_emailService = emailService;
}
public async Task SendWelcomeEmailAsync(string recipientEmail)
{
var builder = EmailRequestBuilder.New()
.SetSubject("Welcome!")
.SetHtmlBody("<h1>Welcome to our service!</h1><p>Thank you for joining.</p>")
.SetTextBody("Welcome to our service! Thank you for joining.")
.AddTo(recipientEmail, "New User");
var result = await _emailService.SendEmailAsync(builder);
if (result.IsSuccess)
{
Console.WriteLine($"Email sent! Message ID: {(result as EmailResult)?.MessageId}");
}
else
{
Console.WriteLine($"Failed: {result.Errors?.FirstOrDefault()?.Message}");
}
}
}
Basic Email
var builder = EmailRequestBuilder.New()
.SetSubject("Hello")
.SetTextBody("This is a test email")
.AddTo("recipient@example.com", "Recipient Name");
var result = await _emailService.SendEmailAsync(builder);
HTML Email
var builder = EmailRequestBuilder.New()
.SetSubject("HTML Email")
.SetHtmlBody("<h1>Hello</h1><p>This is an <strong>HTML</strong> email.</p>")
.SetTextBody("Hello. This is an HTML email.") // Plain text fallback
.AddTo("recipient@example.com");
var result = await _emailService.SendEmailAsync(builder);
Email with Attachments
var builder = EmailRequestBuilder.New()
.SetSubject("Email with Attachment")
.SetTextBody("Please find the attachment.")
.AddTo("recipient@example.com")
.AddAttachment("document.pdf", File.ReadAllBytes("path/to/document.pdf"));
var result = await _emailService.SendEmailAsync(builder);
Multiple Attachments as ZIP
var zipBytes = ZipFileBuilder.New()
.AddFile("file1.txt", Encoding.UTF8.GetBytes("Content 1"))
.AddFile("file2.txt", "Content 2") // text overload (UTF-8 by default)
.AddFileFromPath("/path/to/report.pdf") // from disk; entry name defaults to file name
.AddDirectory("/path/to/docs", "docs/") // recurse a directory under a prefix
.Build();
var builder = EmailRequestBuilder.New()
.SetSubject("Files Attached")
.SetTextBody("Please find the attached files.")
.AddTo("recipient@example.com")
.AddAttachment("files.zip", zipBytes);
var result = await _emailService.SendEmailAsync(builder);
Custom From Address
var builder = EmailRequestBuilder.New()
.SetSubject("From Custom Address")
.SetTextBody("This email is from a custom address.")
.SetFrom("custom@example.com", "Custom Sender")
.AddTo("recipient@example.com");
// Use the builder's From address
var result = await _emailService.SendEmailAsync(builder);
// Or override it
var result2 = await _emailService.SendEmailAsync(builder, "override@example.com", "Override Name");
Bulk Email Sending
var builders = new[]
{
EmailRequestBuilder.New()
.SetSubject("Bulk Email 1")
.SetTextBody("First email")
.AddTo("user1@example.com"),
EmailRequestBuilder.New()
.SetSubject("Bulk Email 2")
.SetTextBody("Second email")
.AddTo("user2@example.com")
};
var results = await _emailService.SendBulkEmailAsync(builders);
foreach (var result in results)
{
if (result.IsSuccess)
{
Console.WriteLine($"Sent to {result.Data?.ToAddresses?.FirstOrDefault()}: {(result as EmailResult)?.MessageId}");
}
else
{
Console.WriteLine($"Failed: {result.Errors?.FirstOrDefault()?.Message}");
}
}
Testing Connection
var isConnected = await _emailService.TestConnectionAsync();
if (isConnected)
{
Console.WriteLine("SMTP connection successful!");
}
Using Events
_emailService.EmailSent += (sender, args) =>
{
var result = args.EmailResult;
if (result.IsSuccess)
{
Console.WriteLine($"Email sent successfully: {result.Data?.Subject}");
}
else
{
Console.WriteLine($"Email failed: {result.Errors?.FirstOrDefault()?.Message}");
}
};
_emailService.BulkEmailSent += (sender, args) =>
{
var bulkResult = args.BulkEmailResult;
Console.WriteLine($"Bulk send completed: {bulkResult.SuccessCount}/{bulkResult.TotalCount} successful");
};
_emailService.ConnectionTested += (sender, args) =>
{
if (args.IsSuccess)
{
Console.WriteLine($"Connection test passed in {args.ElapsedTime}");
}
else
{
Console.WriteLine($"Connection test failed: {args.Exception?.Message}");
}
};
EmailServiceOptions
public class EmailServiceOptions
{
/// <summary>SMTP server hostname. Required.</summary>
public string Host { get; set; } = null!;
/// <summary>SMTP server port. Default: 587.</summary>
public int Port { get; set; } = 587;
/// <summary>Whether to use SSL/TLS. Default: false.</summary>
public bool UseSsl { get; set; } = false;
/// <summary>Default from email address. Required.</summary>
public string DefaultFromAddress { get; set; } = null!;
/// <summary>Default from display name. Required.</summary>
public string DefaultFromName { get; set; } = null!;
/// <summary>SMTP username for authentication. Optional.</summary>
public string? Username { get; set; }
/// <summary>SMTP password for authentication. Optional.</summary>
public string? Password { get; set; }
/// <summary>Enable metrics collection. Default: false.</summary>
public bool EnableMetrics { get; set; } = false;
/// <summary>Soft cap used by single-call bulk concurrency planning. Default: 10.</summary>
public int BulkEmailConcurrencyLimit { get; set; } = 10;
/// <summary>Maximum number of messages allowed per <c>SendBulkEmailAsync</c> call. Default: 1000.</summary>
public int MaxBulkEmailLimit { get; set; } = 1000;
/// <summary>Maximum number of attachments allowed per email. Default: 20.</summary>
public int MaxAttachmentCountPerEmail { get; set; } = 20;
}
EmailSending Event
_emailService.EmailSending += (sender, args) =>
{
var request = args.EmailRequest;
Console.WriteLine($"Sending email to {string.Join(", ", request.ToAddresses ?? [])}: {request.Subject}");
};
EmailSent Event
_emailService.EmailSent += (sender, args) =>
{
var result = args.EmailResult;
if (result.IsSuccess)
{
Console.WriteLine($"Email sent successfully: {(result as EmailResult)?.MessageId}");
}
else
{
Console.WriteLine($"Email failed: {result.Errors?.FirstOrDefault()?.Message}");
}
};
BulkSending Event
_emailService.BulkSending += (sender, args) =>
{
Console.WriteLine($"Starting bulk send for {args.BulkEmailMessage.Count} emails");
};
BulkEmailSent Event
_emailService.BulkEmailSent += (sender, args) =>
{
var bulkResult = args.BulkEmailResult;
Console.WriteLine($"Bulk send completed:");
Console.WriteLine($" Total: {bulkResult.TotalCount}");
Console.WriteLine($" Success: {bulkResult.SuccessCount}");
Console.WriteLine($" Failure: {bulkResult.FailureCount}");
};
ConnectionTested Event
_emailService.ConnectionTested += (sender, args) =>
{
if (args.IsSuccess)
{
Console.WriteLine($"Connection test successful in {args.ElapsedTime}");
}
else
{
Console.WriteLine($"Connection test failed: {args.Exception?.Message}");
}
};
Testing
dotnet test
1. Configure Email Options
Using Configuration File (appsettings.json)
Using Code
2. Register Services
Using Configuration Binding
Using Action
Using Service Provider
Using Action (minimal)
Multiple Attachments as ZIP
ZipFileBuilder packages multiple files into a single ZIP byte array that can be attached like any other file: ZipFileBuilder is a one-shot builder. After calling Build()/BuildToFile()/BuildToStream() the archive is closed and the instance cannot be reused.
Resilience
The library does not include built-in retry or timeout logic. Apply resilience at the application layer (e.g. using Lyo.Resilience
or Polly) by wrapping calls to IEmailService:
// Example: wrap email sends with IResilientExecutor
await _resilientExecutor.ExecuteAsync("email-pipeline", ct => _emailService.SendEmailAsync(builder, ct), cancellationToken);
EmailServiceOptions
The configuration section name defaults to EmailServiceOptions (exposed as EmailServiceOptions.SectionName).
Validation
Hostmust not be null or emptyPortmust be between 1 and 65535DefaultFromAddressmust not be null or emptyDefaultFromNamemust not be null or emptyMaxAttachmentCountPerEmailmust be greater than 0
Error Handling
All email operations return Result<EmailRequest> (runtime type EmailResult for single sends):
var result = await _emailService.SendEmailAsync(builder);
if (result.IsSuccess)
{
Console.WriteLine($"Success: {result.Data?.Subject}");
if (result is EmailResult er)
{
Console.WriteLine($"Message ID: {er.MessageId}");
Console.WriteLine($"Sent Date: {er.SentDate}");
Console.WriteLine($"SMTP Response: {er.SmtpResponse}");
}
}
else
{
var firstError = result.Errors?.FirstOrDefault();
Console.WriteLine($"Error: {firstError?.Message}");
if (firstError?.Exception != null)
{
Console.WriteLine($"Exception: {firstError.Exception.Message}");
}
}
Error Handling — Result<EmailRequest> / EmailResult Properties
IsSuccess- Whether the operation succeededData- The EmailRequest (recipients, subject, etc.)Errors- List of errors if failedMessageId- SMTP message ID (on EmailResult, when success)SentDate- When the email was sent (on EmailResult, when success)SmtpResponse- SMTP server response (on EmailResult, when success)
Events
The email service provides events for monitoring email operations:
EmailSending Event
Fired before each email is sent (including during bulk operations):
EmailSent Event
Fired after each email is sent (success or failure):
BulkSending Event
Fired before a bulk email operation starts:
BulkEmailSent Event
Fired after a bulk email operation completes:
ConnectionTested Event
Fired when a connection test completes:
Logging
The library uses Microsoft.Extensions.Logging for all logging:
services.AddLogging(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Information);
});
Log levels:
- Information: Successful operations, email details
- Debug: SMTP connection details, authentication
- Warning: Cancellations, disconnection errors
- Error: Failures, exceptions
Metrics
email.send.duration- Duration timer for send operationsemail.send.success- Counter for successful sendsemail.send.failure- Counter for failed sendsemail.send.cancelled- Counter for cancelled sendsemail.send.last_duration_ms- Gauge for last send durationemail.bulk.send.duration- Duration timer for bulk operationsemail.bulk.send.total- Counter for total bulk emailsemail.bulk.send.success- Counter for successful bulk emailsemail.bulk.send.failure- Counter for failed bulk emailsemail.bulk.send.last_duration_ms- Gauge for last bulk durationemail.smtp.connect.duration- SMTP connection durationemail.smtp.authenticate.duration- SMTP authentication durationemail.test_connection.duration- Connection test durationemail.test_connection.success- Counter for successful connection testsemail.test_connection.failure- Counter for failed connection tests
API Reference — IEmailService
Task<Result<EmailRequest>> SendEmailAsync(EmailRequestBuilder requestBuilder, string fromAddress, string? fromName = null, CancellationToken ct = default)- Send email with custom from addressTask<Result<EmailRequest>> SendEmailAsync(EmailRequestBuilder requestBuilder, CancellationToken ct = default)- Send email with default from addressTask<Result<EmailRequest>> SendEmailAsync(EmailRequest request, CancellationToken ct = default)- Send email using EmailRequest objectTask<IReadOnlyList<Result<EmailRequest>>> SendBulkEmailAsync(IEnumerable<EmailRequestBuilder> builders, CancellationToken ct = default)- Send multiple emails sequentiallyTask<BulkResult<EmailRequest>> SendBulkEmailAsync(BulkEmailRequestBuilder bulkRequestBuilder, CancellationToken ct = default)- Send bulk emails using BulkEmailRequestBuilderTask<bool> TestConnectionAsync(CancellationToken ct = default)- Test SMTP connection
API Reference — EmailRequestBuilder
AddTo(...)- Add To recipientsAddCc(...)- Add Cc recipientsAddBcc(...)- Add Bcc recipientsSetFrom(...)- Set From addressSetReplyTo(...)- Set Reply-To addressSetSubject(...)- Set email subjectSetPriority(...)- Set message prioritySetHtmlBody(...)- Set HTML bodySetTextBody(...)- Set plain text bodyAppendHtmlBody(...)- Append to HTML bodyAppendTextBody(...)- Append to text bodyAddAttachment(...)- Add file attachments (useZipFileBuilderfirst if you want a ZIP attachment)AddHeader(...)- Add custom headersClearTo()/ClearCc()/ClearBcc()/ClearAttachments()- Clear collectionsBuild()- Build the MimeMessage
API Reference — ZipFileBuilder
AddFile(name, byte[] | Stream | string)- Add a single entry; thestringoverload uses UTF-8 by defaultAddFiles(Dictionary<string, byte[]>)/AddFiles(params string[] filePaths)- Add multiple entriesAddFileFromPath(path, entryName?)- Add an entry from a file on diskAddDirectory(path, entryPrefix = "")- Recursively add an entire directory treeBuild()/BuildToFile(path)/BuildToStream()- Materialise the archive (one-shot — the instance cannot be reused after building)
API Reference — BulkEmailRequestBuilder
Use for bulk sends with a shared default sender:
SetDefaultFrom(fromAddress, fromName)- Set default sender for all messagesSetMaxLimit(maxLimit)- Set maximum number of messages allowedAdd(to, subject, textBody?, htmlBody?)- Add a messageAdd(to, subject, textBody, htmlBody, fromAddress?, fromName?)- Add with per-message sender overrideAddCc(cc)/AddBcc(bcc)- Add CC/BCC to the last messageClear()- Clear all messages and default senderBuild()- Build the collection of EmailRequestBuilders (used internally by SendBulkEmailAsync)
var bulk = BulkEmailRequestBuilder.New()
.SetDefaultFrom("noreply@example.com", "My App")
.Add("user1@example.com", "Subject 1", "Body 1")
.Add("user2@example.com", "Subject 2", "Body 2", "<p>Body 2</p>");
var bulkResult = await _emailService.SendBulkEmailAsync(bulk);
Thread Safety
The EmailService is thread-safe and can be registered as a singleton:
services.AddSingleton<IEmailService, EmailService>();
Multiple threads can safely use the same instance concurrently.
Important Notes — From Address Priority
- If
fromAddressparameter is provided toSendEmailAsync, it overrides any From address in the builder - If builder has a From address and no parameter is provided, the builder's From address is used
- If neither has a From address, the default From address from
EmailServiceOptions.DefaultFromAddressandEmailServiceOptions.DefaultFromNameis used
Important Notes — Bulk Email Processing
MaxBulkEmailLimit(default1000) —SendBulkEmailAsyncthrowsArgumentOutsideRangeExceptionif the input exceeds this count.MaxAttachmentCountPerEmail(default20) — enforced per request on both single and bulk sends.BulkEmailConcurrencyLimit(default10) — a soft cap used by callers planning concurrent bulk batches. The current implementation processes messages sequentially within a single bulk call, so this value does not change the in-call behaviour.
Important Notes — Cancellation
SendEmailAsyncoperations return a failure result if cancelledTestConnectionAsyncthrowsOperationCanceledExceptionif cancelled- Bulk operations check cancellation between emails and stop early if cancelled
Dependencies
Generated from ProjectReference / PackageReference (same model as docs/Lyo.ProjectGraph.html).
Lyo.Common— (direct, lyo)Lyo.Email.Models— (direct, lyo)Lyo.Exceptions— (direct, lyo)Lyo.Metrics— (direct, lyo)Lyo.Result— (direct, lyo)MailKit4.17.0— (direct, third-party)Microsoft.Extensions.Logging.Abstractions10.0.5— (direct, microsoft)Microsoft.Extensions.Options.ConfigurationExtensions10.0.5— (direct, microsoft)Microsoft.Extensions.DependencyInjection.Abstractions10.0.5— (transitive, microsoft)Microsoft.Extensions.Options10.0.5— (transitive, microsoft)System.Memory4.6.3— (transitive, microsoft, netstandard2.0)System.Text.Json10.0.5— (transitive, microsoft, netstandard2.0)
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Lyo.Common (>= 1.0.2)
- Lyo.Email.Models (>= 1.0.2)
- Lyo.Exceptions (>= 1.0.2)
- Lyo.Metrics (>= 1.0.2)
- Lyo.Result (>= 1.0.2)
- MailKit (>= 4.17.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.5)
-
net10.0
- Lyo.Common (>= 1.0.2)
- Lyo.Email.Models (>= 1.0.2)
- Lyo.Exceptions (>= 1.0.2)
- Lyo.Metrics (>= 1.0.2)
- Lyo.Result (>= 1.0.2)
- MailKit (>= 4.17.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.5)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Lyo.Email:
| Package | Downloads |
|---|---|
|
Lyo.Email.Web.Components
Reusable Blazor components for email composition, attachments, and delivery result inspection. |
GitHub repositories
This package is not used by any popular GitHub repositories.