Carrigan.Emailer
0.1.3
dotnet add package Carrigan.Emailer --version 0.1.3
NuGet\Install-Package Carrigan.Emailer -Version 0.1.3
<PackageReference Include="Carrigan.Emailer" Version="0.1.3" />
<PackageVersion Include="Carrigan.Emailer" Version="0.1.3" />
<PackageReference Include="Carrigan.Emailer" />
paket add Carrigan.Emailer --version 0.1.3
#r "nuget: Carrigan.Emailer, 0.1.3"
#:package Carrigan.Emailer@0.1.3
#addin nuget:?package=Carrigan.Emailer&version=0.1.3
#tool nuget:?package=Carrigan.Emailer&version=0.1.3
Carrigan.Emailer
Carrigan.Emailer is a configuration-driven email sender for .NET that supports SMTP delivery through MailKit and an optional pickup-directory mode that writes .eml files to disk.
- Current package targets:
net9.0andnet10.0 - License: Apache-2.0
Important notice
Carrigan.Emailer is a delivery library.
It helps applications send email, but it does not by itself make an application compliant with anti-spam laws, marketing-email rules, privacy laws, retention rules, consent requirements, unsubscribe requirements, or provider policies.
Application developers remain responsible for ensuring that their use of this library complies with all applicable legal, regulatory, contractual, provider, and organizational requirements.
Examples include, without limitation:
- obtaining any required recipient consent
- honoring unsubscribe or opt-out requests where required
- providing any required sender identification, notices, or disclosures
- complying with retention, deletion, privacy, and security obligations
- following the terms and technical requirements of the selected email provider
What it does
- SMTP network delivery using
MailKit.Net.Smtp.SmtpClient - Pickup-directory delivery that writes
.emlfiles to disk - Domain-based SMTP endpoint mapping through
EmailDomainSmtpEndpoint - Default sender support through
IEmailConfiguration.DefaultAccount - Optional
Cc,Bcc, andReply-To - Optional
List-Unsubscribeheader support Microsoft.Extensions.Loggingintegration throughILogger<Emailer>- Structured send results through
EmailerResults
Install
dotnet add package Carrigan.Emailer
Configuration
Carrigan.Emailer uses a static configuration store internally.
Call:
Emailer.ValidateConfiguration(configuration);
once during startup before sending email.
Implement IEmailConfiguration
using Carrigan.Emailer;
public sealed class MyEmailConfiguration : IEmailConfiguration
{
public EmailAddress? DefaultAccount { get; set; } = new("no-reply@example.com", "My App");
public IEnumerable<EmailDomainSmtpEndpoint> EmailDomainSmtpEndpoints { get; set; } =
[
new EmailDomainSmtpEndpoint
{
EmailDomain = "example.com",
SmtpServer = "smtp.example.com",
SmtpPort = 587
}
];
public SecurityEnum? SecurityOption { get; set; } = SecurityEnum.Tls;
public string? PickupDirectory { get; set; }
public bool UsePickupDirectory { get; set; }
public bool? UseNetworkDelivery { get; set; } = true;
public string? GetPasswordForAccount(string accountAddress)
{
return accountAddress switch
{
"no-reply@example.com" => Environment.GetEnvironmentVariable("EMAIL_NO_REPLY_PASSWORD"),
_ => null
};
}
}
Example startup configuration
using Carrigan.Emailer;
MyEmailConfiguration config = new()
{
DefaultAccount = new EmailAddress("no-reply@example.com", "My App"),
EmailDomainSmtpEndpoints =
[
new EmailDomainSmtpEndpoint
{
EmailDomain = "example.com",
SmtpServer = "smtp.example.com",
SmtpPort = 587
}
],
SecurityOption = SecurityEnum.Tls,
UseNetworkDelivery = true,
UsePickupDirectory = false,
PickupDirectory = "C:\\maildrop"
};
Emailer.ValidateConfiguration(config);
What validation enforces
Emailer.ValidateConfiguration(...) will throw if, among other checks:
DefaultAccountis missing or invalidUseNetworkDeliveryis null- neither network nor pickup delivery is enabled
SecurityOptionis null or invalid when network delivery is enabledGetPasswordForAccount(DefaultAccount.Address)returns null or empty when network delivery is enabled- no matching
EmailDomainSmtpEndpointexists forDefaultAccount.Hostwhen network delivery is enabled - pickup-directory mode is enabled but
PickupDirectoryis invalid or unusable
Sending email
using Carrigan.Emailer;
using Microsoft.Extensions.Logging;
ILoggerFactory loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
ILogger<Emailer> logger = loggerFactory.CreateLogger<Emailer>();
Emailer emailer = new(logger);
EmailMessage message = new(
id: Guid.NewGuid(),
toAddress: "recipient@example.com",
subject: "Hello from Carrigan.Emailer",
htmlBody: "<p>Hello!</p>",
textBody: "Hello!"
);
EmailerResults results = await emailer.SendEmailsAsync(message);
if (results.Errors.Any())
{
throw new AggregateException("One or more emails failed to send.", results.Errors);
}
About From
- If
EmailMessage.Fromis null, the library usesconfiguration.DefaultAccount.
About message bodies
EmailMessagerequires at least one body.- You may provide HTML, plain text, or both.
Pickup directory mode
Enable pickup-directory output in configuration:
config.UsePickupDirectory = true;
config.PickupDirectory = "C:\\maildrop";
Then call the same send method:
EmailerResults results = await emailer.SendEmailsAsync(message);
Pickup output structure:
{PickupDirectory}/{recipientFolderName}/inbox/{timestamp}_{guid}.eml{PickupDirectory}/{senderFolderName}/sent/{timestamp}_{guid}.eml
Folder names are derived from email addresses and sanitized for use as paths.
Note: pickup-directory mode is primarily intended for development, testing, and local inspection scenarios. The library writes message files, but it does not manage retention, cleanup, archival, or directory security for you.
List-Unsubscribe header
If EmailMessage.UnsubscribeOptions contains one or more values, the library adds a List-Unsubscribe header.
Example:
EmailMessage newsletter = new(
Guid.NewGuid(),
new EmailAddress("recipient@example.com"),
"Newsletter",
"<p>...</p>",
null,
unsubscribeOptions: new[]
{
"mailto:unsubscribe@example.com?subject=unsubscribe",
"https://example.com/unsubscribe?id=123"
}
);
await emailer.SendEmailsAsync(newsletter);
Convenience helper
For applications that use a simple HTML-email flow, Emailer also provides:
await emailer.SendEmailAsync(
toEmailAddress: "recipient@example.com",
subject: "Confirm your account",
htmlMessage: "<p>Click the link...</p>"
);
If sending fails, it throws an AggregateException containing the underlying errors.
Results and error handling
SendEmailsAsync(...) returns EmailerResults, which exposes:
SuccessfulEmailIdsPermanentlyFailedEmailIdsPermanentlyFailedRecipientsOtherErrorsHostErrorsAccountErrorsEmailErrorsErrorsEmailIdsToDelete
Current repository status
The repository includes a test project, but it is currently a scaffold for future unit tests rather than a mature coverage suite.
Support Development
If this package helps you, consider supporting development:
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. 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. |
-
net10.0
- Carrigan.Core (>= 0.1.5)
- MailKit (>= 4.17.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.10)
-
net9.0
- Carrigan.Core (>= 0.1.5)
- MailKit (>= 4.17.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.10)
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 |
|---|---|---|
| 0.1.3 | 41 | 7/25/2026 |
| 0.1.2 | 190 | 6/11/2026 |
| 0.1.2-alpha.202606110104 | 61 | 6/11/2026 |
| 0.1.2-alpha.202606030851 | 52 | 6/3/2026 |
| 0.1.1 | 108 | 6/3/2026 |
| 0.1.0 | 62 | 6/3/2026 |
| 0.1.0-alpha.202605270630 | 55 | 5/27/2026 |
| 0.1.0-alpha.202605270622 | 63 | 5/27/2026 |
| 0.1.0-alpha.202605030715 | 65 | 5/3/2026 |
| 0.1.0-alpha.202604232245 | 81 | 4/23/2026 |
| 0.1.0-alpha.202604232037 | 74 | 4/23/2026 |
| 0.1.0-alpha.202604231630 | 65 | 4/23/2026 |
| 0.1.0-alpha.202604231535 | 76 | 4/23/2026 |
| 0.1.0-alpha.202604191211 | 91 | 4/19/2026 |
| 0.1.0-alpha.202604140326 | 73 | 4/14/2026 |
| 0.1.0-alpha.202604042201 | 88 | 4/4/2026 |
| 0.1.0-alpha.202604042200 | 64 | 4/4/2026 |
| 0.1.0-alpha.202603221003 | 78 | 3/22/2026 |
| 0.1.0-alpha.202603180610 | 71 | 3/18/2026 |
| 0.1.0-alpha.202603130123 | 69 | 3/13/2026 |