Krugertech.CSharp-SMTP-Server 1.1.6-krugertech.3

This is a prerelease version of Krugertech.CSharp-SMTP-Server.
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package Krugertech.CSharp-SMTP-Server --version 1.1.6-krugertech.3
                    
NuGet\Install-Package Krugertech.CSharp-SMTP-Server -Version 1.1.6-krugertech.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Krugertech.CSharp-SMTP-Server" Version="1.1.6-krugertech.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Krugertech.CSharp-SMTP-Server" Version="1.1.6-krugertech.3" />
                    
Directory.Packages.props
<PackageReference Include="Krugertech.CSharp-SMTP-Server" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Krugertech.CSharp-SMTP-Server --version 1.1.6-krugertech.3
                    
#r "nuget: Krugertech.CSharp-SMTP-Server, 1.1.6-krugertech.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Krugertech.CSharp-SMTP-Server@1.1.6-krugertech.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Krugertech.CSharp-SMTP-Server&version=1.1.6-krugertech.3&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Krugertech.CSharp-SMTP-Server&version=1.1.6-krugertech.3&prerelease
                    
Install as a Cake Tool

CSharp-SMTP-Server — ACK-Gated Fork

This is a fork of zabszk/CSharp-SMTP-Server v1.1.6 (released 23 Dec 2023). It was branched specifically to add ACK gating to the DATA command. It is not affiliated with or endorsed by the original author. The original library is available on NuGet.

Simple (receive-only) SMTP server library for C#.


What is ACK gating and why does it matter?

In the original library the DATA command fires the delivery handler in a background task and immediately returns 250 OK to the sending MTA — a pattern called fire-and-forget. This means the sending MTA considers the message delivered the moment the server acknowledges it, even though your application code has not yet finished (or even started) processing it.

This fork changes that contract:

  • The server awaits your delivery handler before sending any SMTP response.
  • Your handler returns a SmtpDeliveryResult that controls exactly what code the client sees.
  • 250 OK is sent only after your handler returns SmtpDeliveryResult.Ok(...).
  • A transient failure (451) tells the sender to retry later.
  • A permanent failure (554) tells the sender not to retry.
  • An unhandled exception in your handler produces a 451 so the sender retries rather than silently losing the message.

This makes the SMTP 250 OK a true durability guarantee: the sending MTA will not discard its copy of the message until your handler says it has been safely accepted.

What this means if you are migrating from the original library

Original This fork
Task EmailReceived(MailTransaction transaction) Task<SmtpDeliveryResult> EmailReceivedAsync(MailTransaction transaction, CancellationToken cancellationToken = default)
Return value ignored — always sends 250 OK Return value determines the SMTP response sent to the client
Delivery runs in the background Server blocks the SMTP session until delivery completes
Exception in handler is silently swallowed Exception produces 451; sending MTA will retry

You must rename and update the signature of your EmailReceived implementation. No other interface changes are required.


Supported features

  • TLS and STARTTLS
  • AUTH LOGIN and AUTH PLAIN
  • ACK-gated delivery (this fork)

Compatible with

  • RFC 822 (STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES)
  • RFC 1869 (SMTP Service Extensions)
  • RFC 2554 (SMTP Service Extension for Authentication)
  • RFC 3463 (Enhanced Mail System Status Codes)
  • RFC 4616 (The PLAIN Simple Authentication and Security Layer (SASL) Mechanism)
  • RFC 4954 (SMTP Service Extension for Authentication)
  • RFC 5321 (SMTP Protocol)
  • RFC 7208 (Sender Policy Framework)
  • RFC 7372 (Email Authentication Status Codes)
  • RFC 7489 (Domain-based Message Authentication, Reporting, and Conformance (DMARC)) [Partially Supported]

Basic usage

Server setup

var server = new SMTPServer(new[]
{
    new ListeningParameters(IPAddress.IPv6Any, new ushort[] { 25, 587 }, new ushort[] { 465 }, true)
}, new ServerOptions { ServerName = "My SMTP Server", RequireEncryptionForAuth = false },
   new DeliveryInterface(),
   new LoggerInterface());

// With TLS certificate:
// }, new ServerOptions { ServerName = "My SMTP Server", RequireEncryptionForAuth = true },
//    new DeliveryInterface(), new LoggerInterface(),
//    new X509Certificate2("PathToCertWithKey.pfx"));

server.SetAuthLogin(new AuthenticationInterface());
server.SetFilter(new FilterInterface());
server.Start();

SmtpDeliveryResult

Your delivery handler returns one of three factory results:

// Message accepted — sends 250 OK to the client
SmtpDeliveryResult.Ok()
SmtpDeliveryResult.Ok("Message queued for delivery")

// Transient failure — sends 451; the sending MTA will retry
SmtpDeliveryResult.TemporaryFailure()
SmtpDeliveryResult.TemporaryFailure("Storage unavailable, try again later")

// Permanent failure — sends 554; the sending MTA will not retry
SmtpDeliveryResult.PermanentFailure()
SmtpDeliveryResult.PermanentFailure("Message policy violation")

Delivery interface

class DeliveryInterface : IMailDelivery
{
    public async Task<SmtpDeliveryResult> EmailReceivedAsync(
        MailTransaction transaction,
        CancellationToken cancellationToken = default)
    {
        try
        {
            // Do your durable work here — write to disk, insert to DB, etc.
            // The sending MTA will not receive 250 OK until this method returns.
            await SaveMessageAsync(transaction, cancellationToken);
            return SmtpDeliveryResult.Ok();
        }
        catch (StorageUnavailableException)
        {
            // Transient — ask the sender to retry
            return SmtpDeliveryResult.TemporaryFailure("Storage unavailable, please retry");
        }
        catch (PolicyViolationException ex)
        {
            // Permanent — do not retry
            return SmtpDeliveryResult.PermanentFailure(ex.Message);
        }
        // Any unhandled exception becomes a 451 automatically
    }

    // Called during RCPT TO — return DestinationAddressValid to accept the recipient
    public Task<UserExistsCodes> DoesUserExist(string emailAddress) =>
        Task.FromResult(emailAddress.EndsWith("@example.com", StringComparison.OrdinalIgnoreCase)
            ? UserExistsCodes.DestinationAddressValid
            : UserExistsCodes.BadDestinationSystemAddress);
}

Logger interface

class LoggerInterface : ILogger
{
    public void LogError(string text) => Console.WriteLine("[LOG] " + text);
}

Authentication interface

class AuthenticationInterface : IAuthLogin
{
    // 123 is the password for all users — NOT SECURE, DEMO ONLY
    public Task<bool> AuthPlain(string authorizationIdentity, string authenticationIdentity,
        string password, EndPoint remoteEndPoint, bool secureConnection) =>
        Task.FromResult(password == "123");

    public Task<bool> AuthLogin(string login, string password,
        EndPoint remoteEndPoint, bool secureConnection) =>
        Task.FromResult(password == "123");
}

Filter interface

class FilterInterface : IMailFilter
{
    // Allow all connections
    public Task<SmtpResult> IsConnectionAllowed(EndPoint ep) =>
        Task.FromResult(new SmtpResult(SmtpResultType.Success));

    // Block .invalid TLD
    public Task<SmtpResult> IsAllowedSender(string source, EndPoint ep) =>
        Task.FromResult(source.TrimEnd().EndsWith(".invalid")
            ? new SmtpResult(SmtpResultType.PermanentFail)
            : new SmtpResult(SmtpResultType.Success));

    // Reject SPF Softfail
    public Task<SmtpResult> IsAllowedSenderSpfVerified(string source, EndPoint? ep,
        string? username, ValidationResult spfResult) =>
        Task.FromResult(spfResult == ValidationResult.Softfail
            ? new SmtpResult(SmtpResultType.PermanentFail)
            : new SmtpResult(SmtpResultType.Success));

    // Block emails addressed to root@*
    public Task<SmtpResult> CanDeliver(string source, string destination,
        bool authenticated, string? username, EndPoint? ep) =>
        Task.FromResult(destination.TrimStart().StartsWith("root@", StringComparison.OrdinalIgnoreCase)
            ? new SmtpResult(SmtpResultType.PermanentFail)
            : new SmtpResult(SmtpResultType.Success));

    // Reject messages containing "spam"
    public Task<SmtpResult> CanProcessTransaction(MailTransaction transaction) =>
        Task.FromResult(transaction.GetMessageBody() != null &&
                        transaction.GetMessageBody()!.Contains("spam", StringComparison.OrdinalIgnoreCase)
            ? new SmtpResult(SmtpResultType.PermanentFail)
            : new SmtpResult(SmtpResultType.Success));
}

3rd party services and libraries

  • By default this library uses Cloudflare Public DNS (1.1.1.1) for SPF and DMARC validation. The DNS endpoint can be changed or both validations disabled via ServerOptions.
  • By default this library downloads the Public Suffix List managed by the Mozilla Foundation from GitHub (licensed under MPL v2.0). The URL can be changed in ServerOptions. The list is not downloaded when DnsServerEndpoint is null.
  • This library uses MimeKit 4.17.0 by the .NET Foundation and Contributors, licensed under the MIT License.

Generating a PFX from PEM keys

openssl pkcs12 -export -in public.pem -inkey private.pem -out CertWithKey.pfx
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  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 is compatible.  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 was computed.  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 netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
2.0.0-krugertech.4 49 9/8/2026
2.0.0-krugertech.3 47 9/8/2026
2.0.0-krugertech.2 52 9/8/2026
1.1.6-krugertech.3 76 6/4/2026
1.1.6-krugertech.2 68 6/4/2026
1.1.6-krugertech.1 69 6/4/2026