Core storage abstraction layer for Zetian SMTP Server. Provides essential interfaces (IMessageStore) and base configurations for building custom storage providers. This package serves as the foundation for all Zetian storage implementations including SQL Server, PostgreSQL, MongoDB, Redis, and cloud storage providers.
$ dotnet add package Zetian.StorageCore storage abstraction layer for Zetian SMTP Server. Provides essential interfaces, base classes, and contracts for building custom message storage providers. This package serves as the foundation for all Zetian storage implementations.
# Install the core package (required for custom providers)
dotnet add package Zetian
dotnet add package Zetian.Storage
Choose from our pre-built storage providers:
using Zetian.Server;
using Zetian.Storage.SqlServer.Extensions;
// Configure SMTP server with SQL Server storage
var server = new SmtpServerBuilder()
.Port(25)
.WithSqlServerStorage(
"Server=localhost;Database=SmtpStorage;Integrated Security=true;",
config =>
{
config.MaxMessageSizeMB = 25;
config.AutoCreateTables = true;
config.CompressMessageBody = true;
})
.Build();
await server.StartAsync();The main interface all storage providers must implement:
public interface IMessageStore
{
/// <summary>
/// Save the given message to the underlying storage system
/// </summary>
Task<bool> SaveAsync(
ISmtpSession session,
ISmtpMessage message,
CancellationToken cancellationToken = default);
}Base configuration class for all storage providers:
public abstract class BaseStorageConfiguration
{
public double MaxMessageSizeMB { get; set; } = 25.0;
public bool CompressMessageBody { get; set; } = false;
public bool EnableRetry { get; set; } = true;
public int MaxRetryAttempts { get; set; } = 3;
public int RetryDelayMs { get; set; } = 1000;
public int ConnectionTimeoutSeconds { get; set; } = 30;
public bool LogErrors { get; set; } = true;
}using Zetian.Abstractions;
public class MyCustomMessageStore : IMessageStore
{
private readonly MyCustomConfiguration _config;
public MyCustomMessageStore(MyCustomConfiguration config)
{
_config = config;
}
public async Task<bool> SaveAsync(
ISmtpSession session,
ISmtpMessage message,
CancellationToken cancellationToken = default)
{
// Your implementation
try
{
// Save message to your storage
await SaveToCustomStorageAsync(session, message);
return true;
}
catch
{
return false;
}
}
}using Zetian.Storage.Configuration;
public class MyCustomConfiguration : BaseStorageConfiguration
{
public string ConnectionString { get; set; } = string.Empty;
public string TableName { get; set; } = "SmtpMessages";
public bool UseEncryption { get; set; } = false;
// Add custom properties
}using Zetian.Server;
using Zetian.Storage.Extensions;
using Microsoft.Extensions.Logging;
public static class MyCustomStorageExtensions
{
public static SmtpServerBuilder WithMyCustomStorage(
this SmtpServerBuilder builder,
string connectionString,
Action<MyCustomConfiguration>? configure = null)
{
var configuration = new MyCustomConfiguration
{
ConnectionString = connectionString
};
configure?.Invoke(configuration);
configuration.Validate();
ILogger<MyCustomMessageStore>? logger = builder.GetLogger<MyCustomMessageStore>();
var store = new MyCustomMessageStore(configuration, logger);
return builder.MessageStore(store);
}
}var server = new SmtpServerBuilder()
.Port(25)
.WithMyCustomStorage(
"custom://localhost",
config =>
{
config.TableName = "EmailArchive";
config.UseEncryption = true;
})
.Build();protected byte[] CompressData(byte[] data)
{
if (!_config.CompressMessageBody || data.Length < 1024)
return data;
using var output = new MemoryStream();
using (var gzip = new GZipStream(output, CompressionMode.Compress))
{
gzip.Write(data, 0, data.Length);
}
return output.ToArray();
}protected async Task<T> ExecuteWithRetryAsync<T>(
Func<Task<T>> operation,
CancellationToken cancellationToken = default)
{
int attempts = 0;
while (attempts < _config.MaxRetryAttempts)
{
try
{
return await operation();
}
catch (Exception ex) when (_config.EnableRetry && attempts < _config.MaxRetryAttempts - 1)
{
attempts++;
await Task.Delay(_config.RetryDelayMs, cancellationToken);
if (_config.LogErrors)
{
_logger?.LogWarning(
"Retry attempt {Attempt} after error: {Error}",
attempts, ex.Message);
}
}
}
throw new StorageException($"Operation failed after {attempts} attempts");
}MIT License - see LICENSE
Built with ❤️ for the .NET community