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.
- 🔒 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
dotnet add package ZetianOr via NuGet Package Manager:
Install-Package Zetian
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();using Zetian.Server;
using var server = new SmtpServerBuilder()
.Port(587)
.RequireAuthentication()
.AllowPlainTextAuthentication()
.SimpleAuthentication("user", "password")
.Build();
await server.StartAsync();using Zetian.Server;
using var server = new SmtpServerBuilder()
.Port(587)
.Certificate("certificate.pfx", "password")
.RequireSecureConnection()
.Build();
await server.StartAsync();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();using Zetian.Models;
using Zetian.Extensions;
server.AddRateLimiting(
RateLimitConfiguration.PerHour(100)
);Zetian provides two different filtering approaches:
-
Protocol-Level Filtering (via Builder) - Rejects at SMTP command level
- Applied during MAIL FROM/RCPT TO commands
- More efficient, saves bandwidth
- Use
WithSenderDomainWhitelist,WithRecipientDomainWhitelistetc.
-
Event-Based Filtering (via Extensions) - Filters after message received
- Applied after the entire message is received
- More flexible for complex logic
- Use
AddSpamFilter,AddSizeFilter,AddMessageFilteretc.
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
// 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();// 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;
});// 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);
};// 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();// 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}");// 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();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}");
Console.WriteLine($"Priority: {message.Priority}");
// Access headers
var messageId = message.GetHeader("Message-Id");
var contentType = message.GetHeader("Content-Type");
// Get message content
var textBody = message.TextBody;
var htmlBody = message.HtmlBody;
// Save message
await message.SaveToFileAsync($"{message.Id}.eml");
// Get raw data
var rawData = await message.GetRawDataAsync();
};The examples directory contains comprehensive examples:
- SecureExample - SMTP server with TLS/SSL support
- MessageStorageExample - Saving messages to disk
- RateLimitedExample - SMTP server with rate limiting
- AuthenticatedExample - SMTP server with authentication
- BasicExample - Simple SMTP server without authentication
- FullFeaturedExample - Complete SMTP server with all features
- CustomProcessingExample - Custom message processing and filtering
- MaxRetryCountExample - Demonstrates retry mechanism configuration
- ProtocolLevelFilteringExample - Demonstrates the difference between protocol-level and event-based filtering
Zetian is built for high performance:
- Configurable buffer sizes
- Minimal memory allocations
- Connection pooling and throttling
- Optimized network I/O operations
- Efficient async/await patterns throughout
- Validate and sanitize all inputs
- Configure rate limiting to prevent abuse
- Implement proper logging and monitoring
- Implement proper authentication mechanisms
- Always use TLS/SSL in production environments
- Keep the library updated with latest security patches
- Windows, Linux, or macOS
- .NET 6.0, 7.0, 8.0, 9.0, or 10.0
Contributions are welcome! Please feel free to submit issues, feature requests, or pull requests.
This project is licensed under the MIT License - see the LICENSE file for details.
For support, please open an issue on GitHub or contact the maintainers.
Built with ❤️ using modern .NET technologies.
