SkillAgentSearch skills...

Zetian

A professional, high-performance SMTP server library for .NET with minimal dependencies. Features TLS/SSL, authentication, rate limiting, and extensible architecture for building custom email servers.

Install / Use

/learn @Taiizor/Zetian

README

Logo

Dot-Net-Version C-Sharp-Version IDE-Version NuGet-Version NuGet-Download Stack Overflow

.NET CodeQL .NET Desktop

Discord-Server

Zetian SMTP Server

A professional, high-performance SMTP server library for .NET with minimal dependencies. Build custom SMTP servers with ease using a fluent API and extensible architecture.

Features

  • 🔒 Security: Full TLS/SSL support with STARTTLS
  • 📦 Minimal Dependencies: Only essential packages required
  • 🎯 Multi-Framework: Supports .NET 6.0, 7.0, 8.0, 9.0, and 10.0
  • 🛡️ Rate Limiting: Protect against abuse with configurable rate limits
  • 🔑 Authentication: Built-in PLAIN and LOGIN mechanisms, easily extensible
  • 📊 Event-Driven: Rich event system for message processing and monitoring
  • 🚀 High Performance: Efficient async/await patterns and optimized I/O operations
  • 🔧 Extensible: Plugin architecture for custom authentication, filtering, and processing

Installation

dotnet add package Zetian

Or via NuGet Package Manager:

Install-Package Zetian

Quick Start

Basic SMTP Server

using Zetian.Server;

// Create and start a basic SMTP server
using var server = SmtpServerBuilder.CreateBasic();

server.MessageReceived += (sender, e) =>
{
    Console.WriteLine($"Received message from {e.Message.From}");
    Console.WriteLine($"Subject: {e.Message.Subject}");
};

await server.StartAsync();
Console.WriteLine($"Server running on {server.Endpoint}");

// Keep running...
Console.ReadKey();
await server.StopAsync();

Authenticated SMTP Server

using Zetian.Server;

using var server = new SmtpServerBuilder()
    .Port(587)
    .RequireAuthentication()
    .AllowPlainTextAuthentication()
    .SimpleAuthentication("user", "password")
    .Build();

await server.StartAsync();

Secure SMTP Server with TLS

using Zetian.Server;

using var server = new SmtpServerBuilder()
    .Port(587)
    .Certificate("certificate.pfx", "password")
    .RequireSecureConnection()
    .Build();

await server.StartAsync();

Advanced Configuration

Using the Fluent Builder

using System.Net;
using Zetian.Models;
using Zetian.Server;

var server = new SmtpServerBuilder()
    .Port(587)
    .BindTo(IPAddress.Any)
    .ServerName("My SMTP Server")
    .MaxMessageSizeMB(25)
    .MaxRecipients(100)
    .MaxConnections(50)
    .MaxConnectionsPerIP(5)
    
    // Security
    .Certificate(certificate)
    .RequireAuthentication()
    .RequireSecureConnection()
    
    // Authentication
    .AddAuthenticationMechanism("PLAIN")
    .AddAuthenticationMechanism("LOGIN")
    .AuthenticationHandler(async (username, password) =>
    {
        // Your authentication logic
        if (await ValidateUser(username, password))
            return AuthenticationResult.Succeed(username);
        return AuthenticationResult.Fail();
    })
    
    // Features
    .EnablePipelining()
    .Enable8BitMime()
    
    // Timeouts
    .ConnectionTimeout(TimeSpan.FromMinutes(5))
    .CommandTimeout(TimeSpan.FromSeconds(30))
    .DataTimeout(TimeSpan.FromMinutes(2))
    
    // Retry Configuration
    .MaxRetryCount(3)
    
    // Logging
    .LoggerFactory(loggerFactory)
    .EnableVerboseLogging()
    
    .Build();

Extensions

Rate Limiting

using Zetian.Models;
using Zetian.Extensions;

server.AddRateLimiting(
    RateLimitConfiguration.PerHour(100)
);

Message Filtering

🔍 Important: Two Filtering Approaches

Zetian provides two different filtering approaches:

  1. Protocol-Level Filtering (via Builder) - Rejects at SMTP command level

    • Applied during MAIL FROM/RCPT TO commands
    • More efficient, saves bandwidth
    • Use WithSenderDomainWhitelist, WithRecipientDomainWhitelist etc.
  2. Event-Based Filtering (via Extensions) - Filters after message received

    • Applied after the entire message is received
    • More flexible for complex logic
    • Use AddSpamFilter, AddSizeFilter, AddMessageFilter etc.

Choose based on your needs:

  • Use Protocol-Level for early rejection and better performance
  • Use Event-Based for complex filtering logic or when you need the full message

Protocol-Level Filtering (Early Rejection)

// Configure filtering at build time - rejects at SMTP protocol level
var server = new SmtpServerBuilder()
    .Port(25)
    .WithSenderDomainWhitelist("trusted.com", "example.com")  // Rejects at MAIL FROM
    .WithSenderDomainBlacklist("spam.com", "junk.org")        // Rejects at MAIL FROM
    .WithRecipientDomainWhitelist("mydomain.com")             // Rejects at RCPT TO
    .MaxMessageSize(10 * 1024 * 1024)                         // Rejects at MAIL FROM
    .WithFileMessageStore(@"C:\mail")                         // Stores at protocol level
    .Build();

Event-Based Filtering (Late Rejection)

// Configure filtering via extensions - processes after message is received
// Add spam filter
server.AddSpamFilter(new[] { "spam.com", "junk.org" });

// Add size filter (10MB max)
server.AddSizeFilter(10 * 1024 * 1024);

// Add custom filter
server.AddMessageFilter(message => 
{
    // Your filtering logic
    return !message.Subject?.Contains("SPAM") ?? true;
});

Message Storage

// Event-based approach - saves after message is received
server.SaveMessagesToDirectory(@"C:\smtp_messages");

// Protocol-level approach - integrated storage during SMTP transaction
var server = new SmtpServerBuilder()
    .Port(25)
    .WithFileMessageStore(@"C:\smtp_messages")  // Automatic storage
    .Build();

// Custom message processing (event-based)
server.MessageReceived += async (sender, e) =>
{
    // Save to database
    await SaveToDatabase(e.Message);
    
    // Forward to another service
    await ForwardMessage(e.Message);
    
    // Send notification
    await NotifyAdministrator(e.Message);
};

Domain Validation

// Event-based approach - validates after message is received
server.AddAllowedDomains("example.com", "mycompany.com");

// Custom recipient validation
server.AddRecipientValidation(recipient =>
{
    return IsValidRecipient(recipient.Address);
});

// Protocol-level approach - validates at SMTP command level
var server = new SmtpServerBuilder()
    .Port(25)
    .WithRecipientDomainWhitelist("example.com", "mycompany.com")
    .Build();

Event Handling

// Session events
server.SessionCreated += (s, e) =>
    Console.WriteLine($"New session from {e.Session.RemoteEndPoint}");

server.SessionCompleted += (s, e) =>
    Console.WriteLine($"Session completed: {e.Session.Id}");

// Message events
server.MessageReceived += (s, e) =>
{
    Console.WriteLine($"Message: {e.Message.Subject}");
    
    // Reject message if needed
    if (IsSpam(e.Message))
    {
        e.Cancel = true;
        e.Response = new SmtpResponse(550, "Message rejected as spam");
    }
};

// Error events
server.ErrorOccurred += (s, e) =>
    Console.WriteLine($"Error: {e.Exception.Message}");

Custom Authentication

// Option 1: Create a custom authenticator class
using Zetian.Models;
using Zetian.Abstractions;

public class CustomAuthenticator : IAuthenticator
{
    public string Mechanism => "CUSTOM";
    
    public async Task<AuthenticationResult> AuthenticateAsync(
        ISmtpSession session,
        string? initialResponse,
        StreamReader reader,
        StreamWriter writer,
        CancellationToken cancellationToken)
    {
        // Implement your custom authentication logic here
        // ...
    }
}

// Register custom authenticator
AuthenticatorFactory.Register("CUSTOM", () => 
    new CustomAuthenticator());

// Option 2: Use the authentication handler with builder
using Zetian.Models;
using Zetian.Server;

var server = new SmtpServerBuilder()
    .Port(587)
    .RequireAuthentication()
    .AuthenticationHandler(async (username, password) =>
    {
        // Check against database
        var user = await db.GetUser(username);
        if (user != null && VerifyPassword(password, user.PasswordHash))
        {
            return AuthenticationResult.Succeed(username);
        }
        return AuthenticationResult.Fail();
    })
    .AddAuthenticationMechanism("PLAIN")
    .AddAuthenticationMechanism("LOGIN")
    .Build();

Message Processing

server.MessageReceived += async (sender, e) =>
{
    var message = e.Message;
    
    // Access message properties
    Console.WriteLine($"ID: {message.Id}");
    Console.WriteLine($"From: {message.From?.Address}");
    Console.WriteLine($"To: {string.Join(", ", message.Recipients)}");
    Console.WriteLine($"Subject: {message.Subject}");
    Console.WriteLine($"Size: {message.Size} bytes");
    Console.WriteLine($"Has Attachments: {message.HasAttachments}");
    Cons

Related Skills

View on GitHub
GitHub Stars39
CategoryDevelopment
Updated11d ago
Forks2

Languages

C#

Security Score

95/100

Audited on Mar 16, 2026

No findings