Pinqponq.Mail
1.0.1
Prefix Reserved
dotnet add package Pinqponq.Mail --version 1.0.1
NuGet\Install-Package Pinqponq.Mail -Version 1.0.1
<PackageReference Include="Pinqponq.Mail" Version="1.0.1" />
<PackageVersion Include="Pinqponq.Mail" Version="1.0.1" />
<PackageReference Include="Pinqponq.Mail" />
paket add Pinqponq.Mail --version 1.0.1
#r "nuget: Pinqponq.Mail, 1.0.1"
#:package Pinqponq.Mail@1.0.1
#addin nuget:?package=Pinqponq.Mail&version=1.0.1
#tool nuget:?package=Pinqponq.Mail&version=1.0.1
Pinqponq.Mail
SMTP-based email sending wrapper with a standard IEmailSender interface, built
directly on System.Net.Mail — no third-party mail client dependency. Supports
comma/semicolon-separated To/Cc/Bcc lists and file attachments confined to a
configured root directory.
Install
dotnet add package Pinqponq.Mail
Requirements
- .NET 8.0, 9.0, or 10.0
- An SMTP server (host, port, and optionally credentials)
Quick start
Configure inline:
using Pinqponq.Mail;
using Pinqponq.Mail.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddPinqponqMail(options =>
{
options.SmtpHost = "smtp.example.com";
options.SmtpPort = 587;
options.SmtpUsername = builder.Configuration["Smtp:Username"];
options.SmtpPassword = builder.Configuration["Smtp:Password"];
options.FromEmail = "no-reply@example.com";
options.FromName = "My App";
});
var app = builder.Build();
...or bind from configuration (defaults to the "Smtp" section):
builder.Services.AddPinqponqMail(builder.Configuration);
// or a custom section name:
builder.Services.AddPinqponqMail(builder.Configuration, sectionName: "Mail:Smtp");
{
"Smtp": {
"SmtpHost": "smtp.example.com",
"SmtpPort": 587,
"SmtpUsername": "apikey",
"SmtpPassword": "secret",
"FromEmail": "no-reply@example.com",
"FromName": "My App",
"EnableSsl": true
}
}
Send a message from anywhere IEmailSender is injected:
public sealed class WelcomeEmailService(IEmailSender emailSender)
{
public Task SendAsync(string to, CancellationToken cancellationToken) =>
emailSender.SendAsync(
new EmailMessage
{
To = to,
Subject = "Welcome!",
Body = "<p>Thanks for signing up.</p>",
IsBodyHtml = true,
},
cancellationToken);
}
Configuration
Two overloads of AddPinqponqMail register IEmailSender (as SmtpEmailSender)
and validate SmtpOptions on startup via ValidateOnStart():
AddPinqponqMail(Action<SmtpOptions> configure)— inline configuration.AddPinqponqMail(IConfiguration configuration, string sectionName = "Smtp")— binds from a configuration section. ThrowsInvalidOperationExceptionat registration time if the section doesn't exist.
| Option | Default | Notes |
|---|---|---|
SmtpHost |
"" |
Required. |
SmtpPort |
0 |
Required; must be between 1 and 65535. |
SmtpUsername |
"" |
Optional. When set, SmtpClient.Credentials is configured with it (and SmtpPassword); when blank, the connection is anonymous. |
SmtpPassword |
"" |
Paired with SmtpUsername. |
FromEmail |
"" |
Required. Used as the From address on every outgoing message. |
FromName |
null |
Optional From display name. |
EnableSsl |
true |
Whether SmtpClient uses SSL/TLS. |
AttachmentRoot |
null |
Root directory that attachment paths must resolve under. Required when EmailMessage.Attachments is non-empty — see Notes / behavior. |
SmtpOptionsValidator (an internal IValidateOptions<SmtpOptions>) enforces
SmtpHost, SmtpPort, and FromEmail at startup; a missing AttachmentRoot is
only surfaced later, when a message with attachments is actually sent.
Main types
AddPinqponqMail—IServiceCollectionextensions (inline orIConfiguration-bound) that registerIEmailSenderand its options.IEmailSender— the sending contract:Task SendAsync(EmailMessage message, CancellationToken cancellationToken = default).EmailMessage—ToandSubject/Bodyarerequired;Cc,Bccare optional comma/semicolon-separated address lists;IsBodyHtmldefaults totrue;Attachmentsis an optional list of file paths.SmtpOptions— configuration as described above.SmtpEmailSender— theIEmailSenderimplementation, built onSystem.Net.Mail.SmtpClientandMailMessage.
Notes / behavior
- Attachments require
AttachmentRoot— this is a path jail, not just a config toggle. IfEmailMessage.Attachmentsis non-empty butSmtpOptions.AttachmentRootis blank,SendAsyncthrowsArgumentExceptionimmediately. WhenAttachmentRootis set, every attachment path is resolved withPath.GetFullPathand must land inside that root directory (or equal it exactly); a path that resolves outside the root — including via..traversal — is rejected withArgumentExceptionrather than being opened. A path that doesn't exist on disk after resolution also throwsArgumentException. This exists to stop caller-supplied paths from being used to read arbitrary files off the host as "attachments." To/Cc/Bccaccept lists. Each is split on,and;, trimmed, and empty entries are dropped.Tomust resolve to at least one address orSendAsyncthrowsArgumentException.- Validation order: recipient, subject, and body are checked before any SMTP connection is attempted; attachment path validation happens after that, still before the message is sent.
- No built-in retry: unlike
Pinqponq.Sms, this package does not wrap sends in a Polly retry pipeline —SmtpClient.SendMailAsyncfailures propagate directly. Add your own retry policy aroundIEmailSender.SendAsyncif you need one. - Because it's built on
System.Net.Mail, this package requires no external SMTP client library, but also inheritsSmtpClient's behavior and limitations (e.g. synchronous DNS/connection setup, no built-in connection pooling across calls).
Related packages
Pinqponq.Identity.Otp— sends one-time passwords over email (and SMS) using this package'sIEmailSenderas its email delivery channel.
Samples
Try this package in the browser via Pinqponq.Playground — see samples/README.md.
Repository
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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. |
-
net10.0
- Microsoft.Extensions.Configuration (>= 10.0.10)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Options (>= 10.0.10)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.10)
-
net8.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Options (>= 8.0.2)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 8.0.0)
-
net9.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.18)
- Microsoft.Extensions.Options (>= 9.0.18)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 9.0.18)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Pinqponq.Mail:
| Package | Downloads |
|---|---|
|
Pinqponq.Identity.Otp
One-time code send/verify flow over email or SMS; channel routing (mail/sms) lives in the package. Storage interface is left to the consumer. |
GitHub repositories
This package is not used by any popular GitHub repositories.