Ozakboy.Mail
1.2.0
dotnet add package Ozakboy.Mail --version 1.2.0
NuGet\Install-Package Ozakboy.Mail -Version 1.2.0
<PackageReference Include="Ozakboy.Mail" Version="1.2.0" />
<PackageVersion Include="Ozakboy.Mail" Version="1.2.0" />
<PackageReference Include="Ozakboy.Mail" />
paket add Ozakboy.Mail --version 1.2.0
#r "nuget: Ozakboy.Mail, 1.2.0"
#:package Ozakboy.Mail@1.2.0
#addin nuget:?package=Ozakboy.Mail&version=1.2.0
#tool nuget:?package=Ozakboy.Mail&version=1.2.0
<img src="https://raw.githubusercontent.com/ozakboy/ozakboy.Mail/main/logo.png" width="112" align="right" alt="Ozakboy.Mail" />
Ozakboy.Mail
One configuration section, one call, and the mail is out the door.
English | 繁體中文 · Changelog · API Reference · NuGet
Notification mails. Nightly report mails. Verification mails. The kind of email an application sends, not a person. Ozakboy.Mail exists so that sending one takes an SmtpMailConfig section in appsettings.json and a single method call — no SMTP client lifetime to manage, no MIME tree to assemble, no TLS mode to guess.
✉ · · · ✈
Send an email
using Ozakboy.Mail;
IMail mail = new Mail(configuration);
var receivers = new List<MailInfo> { new MailInfo("someone@example.com", "Someone") };
mail.SendMail(
receivers,
null, // CC
null, // attachments
"Your nightly report is ready",
"<p>The report finished at 03:00.</p>");
The same thing, asynchronously, with cancellation:
IMailAsync mail = new Mail(configuration);
await mail.SendMailAsync(
receivers,
null,
null,
"Your nightly report is ready",
"<p>The report finished at 03:00.</p>",
true,
cancellationToken);
With CC and an attachment:
var cc = new List<MailInfo> { new MailInfo("manager@example.com", "Manager") };
var attachments = new List<AttachmentsInfo>
{
new AttachmentsInfo
{
FileName = "report.pdf",
FileType = MediaTypeNames.Application.Pdf,
FileStream = File.OpenRead("report.pdf"),
},
};
await mail.SendMailAsync(receivers, cc, attachments, "Monthly report", "<p>Attached.</p>");
The attachment streams are disposed for you once the send succeeds — see the stream ownership contract.
What this is
A small SMTP sending library with a deliberately narrow surface:
SendMail/SendMailAsync— multiple recipients, CC, attachments, HTML or plain-text bodies- Configuration from
appsettings.json, or straight from aVSmtpMailConfigobject when your settings come from environment variables - Two interfaces so you can inject either style:
IMail(sync) andIMailAsync(async)
And what it is not: no BCC, no templating engine, no queueing or retry layer, no mail-merge, no IMAP/POP. If you need those, use MailKit directly — that is what this library is built on.
Why it is different
MailKit underneath, not System.Net.Mail. The .NET built-in SmtpClient only speaks STARTTLS, so port 465 (implicit SSL) simply never worked — a classic afternoon lost to "my settings are right but nothing sends". Since 1.1.0 the transport is MailKit with SecureSocketOptions.Auto, which negotiates the right mode for 465, 587 and 25 without you telling it which.
Errors that tell you where to look. A failed send does not surface a raw socket error. It throws InvalidOperationException with the four things actually worth checking — network, host/port, firewall, credentials — with your configured host and port interpolated, and the original exception preserved as InnerException for your logs.
No learning curve to pay off. MailKit is excellent and correspondingly large: SmtpClient, MimeMessage, BodyBuilder, MimePart, ContentType, SecureSocketOptions. To send a notification mail you should not have to learn any of them. Here the vocabulary is MailInfo, AttachmentsInfo, and one method.
Sync is really sync. The synchronous path calls MailKit's synchronous API. There is no .Result or .Wait() hidden inside waiting to deadlock your UI or ASP.NET context.
Install and send your first mail
dotnet add package Ozakboy.Mail
1. Add the SMTP section to appsettings.json:
{
"SmtpMailConfig": {
"Host": "smtp.gmail.com",
"Port": 587,
"UserName": "your-email@gmail.com",
"Password": "your-app-password",
"Name": "Sender display name"
}
}
Gmail requires an app password, not your account password. Port 587 (STARTTLS) and 465 (implicit SSL) both work.
Need the sender address to differ from the login account (AWS SES, unauthenticated relays)? Add the optional
"SenderAddress"field — new in 1.2.0. See Configuration.
2. Register it (ASP.NET Core):
builder.Services.AddScoped<IMail, Mail>();
builder.Services.AddScoped<IMailAsync, Mail>();
Or build it by hand in a console app:
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
IMail mail = new Mail(configuration);
Settings living in environment variables instead? Skip IConfiguration entirely:
IMailAsync mail = new Mail(new VSmtpMailConfig
{
Host = Environment.GetEnvironmentVariable("SMTP_HOST"),
Port = int.Parse(Environment.GetEnvironmentVariable("SMTP_PORT")),
UserName = Environment.GetEnvironmentVariable("SMTP_USER"),
Password = Environment.GetEnvironmentVariable("SMTP_PASSWORD"),
Name = "Sender display name",
});
3. Send. That is the whole setup.
✈ · · · ✉
Compatibility
| Target framework | Supported |
|---|---|
| .NET 10.0 | ✅ |
| .NET 9.0 | ✅ |
| .NET 8.0 | ✅ |
| .NET Standard 2.1 | ✅ |
| .NET Standard 2.0 | ✅ |
.NET Standard 2.0 covers .NET Framework 4.6.1+, .NET Core 2.0+ and Mono/Xamarin/Unity.
Dependencies: MailKit, Microsoft.Extensions.Configuration.Abstractions, Microsoft.Extensions.Configuration.Binder.
Upgrading from 1.0.x? Nothing to change — see the migration guide.
Documentation
| Getting Started | Install, configure, first mail — sync and async |
| Configuration | Every SmtpMailConfig field, environment variables, common mail providers |
| API Reference | Every public member, parameter and exception |
| Migration | 1.0.x → 1.1.0 |
| Changelog | Version history |
License
MIT.
Support
| 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 is compatible. 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 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. |
| .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 is compatible. |
| .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
- MailKit (>= 4.17.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 8.0.2)
-
.NETStandard 2.1
- MailKit (>= 4.17.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 8.0.2)
-
net10.0
- MailKit (>= 4.17.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 8.0.2)
-
net8.0
- MailKit (>= 4.17.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 8.0.2)
-
net9.0
- MailKit (>= 4.17.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 8.0.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
v1.2.0 — the sender address and the login account can now be separate. No breaking changes: leaving SenderAddress empty behaves exactly like 1.1.0.
ADDED:
- VSmtpMailConfig.SenderAddress (optional, defaults to an empty string). When set, it becomes the sender address and UserName is used for SMTP authentication only — for providers whose SMTP credentials are not a mailbox address, such as AWS SES. When empty, UserName is still used as the sender address, exactly as in 1.1.0.
- Unauthenticated relays can now carry a valid sender. Leave UserName empty to skip authentication (as in 1.1.0) and set SenderAddress to the address to send from; on 1.1.0 that combination produced a message with no sender address.
TECHNICAL:
- xUnit tests covering the sender selection logic: SenderAddress set, SenderAddress empty falling back to UserName, and UserName empty combined with SenderAddress.
Full changelog: https://github.com/ozakboy/ozakboy.Mail/blob/main/docs/en/changelog.md