Skip to content

Latest commit

 

History

456 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Wolfgang.Extensions.Mail

NuGet Downloads PR build release License: MIT .NET GitHub

Extension methods and helpers for System.Net.Mail: a fluent message builder, an EML parser and serializer, message validation and cloning, attachment factories with content-type inference, inline-HTML image embedding, and collection conveniences.

Full API documentation: https://chris-wolfgang.github.io/System.Mail-Extensions/

Installation

dotnet add package Wolfgang.Extensions.Mail

Features

Build messages fluently — MailMessageBuilder

using Wolfgang.Extensions.Mail;

using var message = new MailMessageBuilder()
    .From("sender@example.com", "Sender Name")
    .To("alice@example.com", "bob@example.com")
    .Cc("archive@example.com")
    .Subject("Monthly Report")
    .PlainTextBody("The report is attached.")
    .HtmlBody("<h1>Report</h1><p>The report is attached.</p>")
    .Attach("report.pdf")
    .Build();

Build() throws InvalidOperationException if the message has no From address or no recipient, reporting every missing part in one message. Also available: Bcc, ReplyTo, SenderAddress, Priority, DeliveryNotification, BodyEncoding, SubjectEncoding, Header, and Attach overloads for byte arrays and streams.

Parse EML files — EmlParser

Parse RFC 2822 / MIME content — including multipart bodies, base64 and quoted-printable transfer encodings, attachments, and RFC 2047 encoded-word headers — into a MailMessage:

using var fromString = EmlParser.Parse(emlContent);
using var fromFile   = EmlParser.ParseFile("message.eml");
using var fromFileAsync = await EmlParser.ParseFileAsync("message.eml", cancellationToken);

Parsing is deliberately lenient, because real-world EML files routinely contain malformed headers: an address that cannot be parsed is skipped, and a malformed From header leaves message.From null.

Strict mode and diagnostics — EmlParserOptions

To reject malformed input instead of skipping it, parse in strict mode — the parser throws an EmlParseException (a FormatException) on the first malformed construct:

using var message = EmlParser.Parse(emlContent, new EmlParserOptions { Strict = true });
// throws EmlParseException on a malformed address, undecodable body part, or malformed encoded word

To see what a lenient parse dropped without throwing, use ParseWithDiagnostics, which returns the message alongside a list of skipped constructs:

ParseResult result = EmlParser.ParseWithDiagnostics(emlContent);
if (result.HasIssues)
{
    foreach (ValidationIssue issue in result.Issues)
    {
        Console.WriteLine($"{issue.PropertyName}: {issue.Message}");
    }
}
// result.Message is still populated best-effort (well-formed parts are kept)

Both ParseFile and ParseFileAsync have matching EmlParserOptions overloads.

Serialize to EML — ToMimeString()

string eml = message.ToMimeString();
await File.WriteAllTextAsync("message.eml", eml);

Produces a complete RFC 2822 MIME document, round-trippable through EmlParser.Parse.

Trimming / Native AOT: ToMimeString is the one API that is not trim- or AOT-safe — it uses reflection to reach the framework's internal MIME writer, so it's annotated [RequiresUnreferencedCode] / [RequiresDynamicCode] and will warn at compile time in a trimmed or PublishAot app. Everything else in the library is trim/AOT-safe (verified by a Native AOT smoke in CI).

Validate messages — Validate()

var result = message.Validate(new ValidationOptions
{
    RequireSubject = true,
    RequireBody = true,
    MaxAttachmentSizeBytes = 10 * 1024 * 1024,
    MaxTotalAttachmentSizeBytes = 25 * 1024 * 1024
});

if (!result.IsValid)
{
    foreach (var issue in result.Errors)
    {
        Console.WriteLine($"{issue.PropertyName}: {issue.Message}");
    }
}

ValidationResult exposes IsValid, Errors, Warnings, and AllIssues. Calling Validate() without options reports a missing From address or missing recipients as errors, and an empty subject or body as warnings (options promote those to errors via RequireSubject/RequireBody).

Deep-copy messages — Clone()

using var clone = original.Clone();
clone.To.Add("extra@example.com");   // original is unchanged

Copies addresses, headers, bodies, alternate views, linked resources, and attachments (with independent streams).

Create attachments — AttachmentFactory

var fromBytes  = AttachmentFactory.FromBytes(pdfBytes, "report.pdf");
var fromBase64 = AttachmentFactory.FromBase64(base64Content, "photo.jpg");
var fromStream = AttachmentFactory.FromStream(stream, "data.csv");

Content types are inferred from the file extension. The registry is extensible:

AttachmentFactory.RegisterContentType(".heic", "image/heic");
string contentType = AttachmentFactory.InferContentType("photo.heic");   // "image/heic"
bool known = AttachmentFactory.TryGetRegisteredContentType(".heic", out var registered);

Inline HTML images — InlineHtmlBuilder

Build an HTML AlternateView with embedded images wired up via Content-IDs — {0}, {1}, … placeholders are filled in the order images are embedded:

var view = new InlineHtmlBuilder()
    .Html("<h1>Report</h1><img src='cid:{0}' />")
    .EmbedImage("chart.png")
    .Build();

message.AlternateViews.Add(view);

EmbedImage overloads accept a file path, a byte array, or a stream.

Parse addresses safely — MailAddress.TryParse

Extension members that backfill MailAddress.TryCreate on frameworks that lack it (on .NET 5+ they delegate to the built-in):

if (MailAddress.TryParse("user@example.com", out var address))
{
    Console.WriteLine(address.Address);
}

MailAddress.TryParse("user@example.com", "Display Name", out var withName);

Attachment collection helpers

message.Attachments.AddRange(attachment1, attachment2);       // params, enumerable,
message.Attachments.AddRange("report.pdf", "data.csv");       // file paths, or
message.Attachments.AddRange(filePathList);                   // enumerable of paths

long totalBytes = message.Attachments.TotalSize();
bool tooBig = message.Attachments.ExceedsLimit(25 * 1024 * 1024);

Address collection helpers

message.To.AddRange("alice@example.com", "bob@example.com");
message.CC.AddRange(mailAddressList);

string formatted = message.To.ToFormattedString();
// "Alice Smith" <alice@example.com>; bob@example.com

string one = new MailAddress("alice@example.com", "Alice Smith").FormatMailAddress();

🎯 Supported Frameworks

This library targets:

  • .NET Framework: 4.6.2
  • .NET Standard: 2.0, 2.1
  • .NET: 8.0, 9.0, 10.0

See the NuGet package page for the authoritative per-TFM compatibility matrix.

License

This project is licensed under the MIT License.

About

A collection of extension methods for AddressCollection and AttachmentCollection

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages