Zetian.Storage.PostgreSQL 1.0.3

Prefix Reserved
There is a newer version of this package available.
See the version list below for details.
dotnet add package Zetian.Storage.PostgreSQL --version 1.0.3
                    
NuGet\Install-Package Zetian.Storage.PostgreSQL -Version 1.0.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="Zetian.Storage.PostgreSQL" Version="1.0.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Zetian.Storage.PostgreSQL" Version="1.0.3" />
                    
Directory.Packages.props
<PackageReference Include="Zetian.Storage.PostgreSQL" />
                    
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 Zetian.Storage.PostgreSQL --version 1.0.3
                    
#r "nuget: Zetian.Storage.PostgreSQL, 1.0.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 Zetian.Storage.PostgreSQL@1.0.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=Zetian.Storage.PostgreSQL&version=1.0.3
                    
Install as a Cake Addin
#tool nuget:?package=Zetian.Storage.PostgreSQL&version=1.0.3
                    
Install as a Cake Tool

Zetian.Storage.PostgreSQL - PostgreSQL Storage

NuGet-Version NuGet-Download License

PostgreSQL storage provider for Zetian SMTP Server. Provides enterprise-grade message persistence to PostgreSQL databases with advanced features including JSONB header storage, automatic table partitioning, GIN indexing for full-text search, message compression, and built-in retry logic. Ideal for high-volume environments requiring scalable, open-source database storage with powerful querying capabilities.

⚑ Features

  • πŸ—Ώ Schema Support - Organize tables in custom schemas
  • πŸ”„ Retry Logic - Built-in retry mechanism for transient failures
  • πŸ” GIN Indexing - Full-text search capabilities on JSONB fields
  • πŸ“¦ Batch Operations - Efficient bulk insert with COPY command
  • πŸ—œοΈ Compression - Optional GZIP compression for message bodies
  • πŸ“ˆ Table Partitioning - Automatic time-based partitioning for scalability
  • πŸŽ‰ JSONB Support - Native JSONB storage for headers with fast querying
  • 🎯 Advanced Queries - Leverage PostgreSQL's powerful query capabilities
  • πŸ—ΊοΈ Automatic Setup - Tables, partitions, and indexes created automatically
  • 🌐 Open Source - Built on PostgreSQL, the world's most advanced open source database

πŸ“¦ Installation

# Install SMTP Server and Storage Provider
dotnet add package Zetian
dotnet add package Zetian.Storage.PostgreSQL

πŸš€ Quick Start

Basic Configuration

using Zetian.Server;
using Zetian.Storage.PostgreSQL.Extensions;

// Configure with connection string
var server = new SmtpServerBuilder()
    .Port(25)
    .WithPostgreSqlStorage("Host=localhost;Database=smtp_db;Username=postgres;Password=secret")
    .Build();

await server.StartAsync();

Advanced Configuration

var server = new SmtpServerBuilder()
    .Port(25)
    .WithPostgreSqlStorage(
        "Host=localhost;Database=smtp_storage;Username=postgres;Password=password;",
        config =>
        {
            config.TableName = "smtp_messages";
            config.SchemaName = "public";
            config.AutoCreateTable = true;
            config.EnablePartitioning = true;
            config.PartitionInterval = PartitionInterval.Monthly;
            config.CompressMessageBody = true;
            config.UseJsonbForHeaders = true;
            config.CreateIndexes = true;
        })
    .Build();

πŸ› οΈ Configuration Options

Option Type Default Description
ConnectionString string required PostgreSQL connection string
TableName string "smtp_messages" Name of the messages table
SchemaName string "public" Database schema name
AutoCreateTable bool true Auto-create table and indexes
UseJsonbForHeaders bool true Store headers as JSONB
EnablePartitioning bool false Enable table partitioning
PartitionInterval enum Monthly Partition interval (Daily, Weekly, Monthly, Yearly)
CreateIndexes bool true Create indexes automatically
MaxMessageSizeMB double 100 Maximum message size in MB
CompressMessageBody bool false Compress message bodies
EnableRetry bool true Enable retry logic
MaxRetryAttempts int 3 Maximum retry attempts
RetryDelayMs int 1000 Delay between retries
ConnectionTimeoutSeconds int 30 Connection timeout
LogErrors bool true Whether to log errors

πŸ“ Database Schema

Messages Table

CREATE TABLE mail.messages (
    id BIGSERIAL PRIMARY KEY,
    message_id VARCHAR(255) NOT NULL UNIQUE,
    session_id VARCHAR(255) NOT NULL,
    from_address VARCHAR(500),
    to_addresses TEXT NOT NULL,
    cc_addresses TEXT,
    bcc_addresses TEXT,
    subject VARCHAR(1000),
    received_date TIMESTAMP WITH TIME ZONE NOT NULL,
    message_size BIGINT NOT NULL,
    message_body BYTEA NOT NULL,
    is_compressed BOOLEAN NOT NULL DEFAULT FALSE,
    headers JSONB,
    has_attachments BOOLEAN NOT NULL DEFAULT FALSE,
    attachment_count INTEGER NOT NULL DEFAULT 0,
    attachments JSONB,
    priority VARCHAR(50),
    remote_ip INET,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
) PARTITION BY RANGE (received_date);  -- When partitioning is enabled

-- Indexes
CREATE INDEX idx_messages_message_id ON mail.messages (message_id);
CREATE INDEX idx_messages_received_date ON mail.messages (received_date DESC);
CREATE INDEX idx_messages_from_address ON mail.messages (from_address);
CREATE INDEX idx_messages_session_id ON mail.messages (session_id);
CREATE INDEX idx_messages_headers ON mail.messages USING GIN (headers);  -- JSONB GIN index

🎯 Usage Examples

Connection String Options

// Basic connection
.WithPostgreSqlStorage("Host=localhost;Database=smtp_db;Username=postgres;Password=secret")

// With SSL
.WithPostgreSqlStorage("Host=localhost;Database=smtp_db;Username=postgres;Password=secret;SSL Mode=Require")

// With connection pooling
.WithPostgreSqlStorage("Host=localhost;Database=smtp_db;Username=postgres;Password=secret;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=100")

// With specific schema
.WithPostgreSqlStorage("Host=localhost;Database=smtp_db;Username=postgres;Password=secret;Search Path=mail,public")

JSONB Header Queries

-- Find messages with specific header
SELECT * FROM mail.messages 
WHERE headers @> '{"X-Priority": "High"}';

-- Find messages from specific domain
SELECT * FROM mail.messages 
WHERE headers->>'From' LIKE '%@example.com';

-- Complex JSONB query
SELECT message_id, headers->>'Subject' as subject
FROM mail.messages
WHERE headers @> '{"X-Spam-Score": "0"}'
AND received_date > NOW() - INTERVAL '7 days';

Table Partitioning

// Configure with partitioning
.WithPostgreSqlStorage(
    connectionString,
    config =>
    {
        config.EnablePartitioning = true;
        config.PartitionInterval = PartitionInterval.Monthly;
    })

// Use JSONB for headers
.WithPostgreSqlStorage(
    connectionString,
    config =>
    {
        config.UseJsonbForHeaders = true;
        config.CreateIndexes = true; // Create indexes including GIN for JSONB
    })

Partition Management

-- Create future partitions
CREATE TABLE mail.messages_2024_02 PARTITION OF mail.messages
    FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');

CREATE TABLE mail.messages_2024_03 PARTITION OF mail.messages
    FOR VALUES FROM ('2024-03-01') TO ('2024-04-01');

-- List partitions
SELECT 
    schemaname,
    tablename,
    pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size
FROM pg_tables 
WHERE tablename LIKE 'messages_%'
ORDER BY tablename;

-- Drop old partitions
DROP TABLE mail.messages_2023_01;

πŸ”§ Performance Optimization

Connection Pooling

config.ConnectionString = "Host=localhost;Database=smtp_db;" +
    "Username=postgres;Password=secret;" +
    "Pooling=true;" +
    "Minimum Pool Size=10;" +
    "Maximum Pool Size=100;" +
    "Connection Idle Lifetime=300;";

Additional Indexes

-- Full-text search index
CREATE INDEX idx_messages_fulltext ON mail.messages 
USING GIN (to_tsvector('english', subject || ' ' || message_body));

-- Composite index for common queries
CREATE INDEX idx_messages_date_from ON mail.messages (received_date DESC, from_address);

-- Partial index for unread messages
CREATE INDEX idx_messages_unread ON mail.messages (received_date DESC) 
WHERE headers->>'X-Read' IS NULL;

Query Optimization

-- Use EXPLAIN ANALYZE to optimize queries
EXPLAIN (ANALYZE, BUFFERS) 
SELECT * FROM mail.messages 
WHERE received_date > NOW() - INTERVAL '7 days';

-- Vacuum and analyze for statistics
VACUUM ANALYZE mail.messages;

-- Auto-vacuum configuration
ALTER TABLE mail.messages SET (
    autovacuum_vacuum_scale_factor = 0.1,
    autovacuum_analyze_scale_factor = 0.05
);

🌐 Replication & High Availability

Streaming Replication Setup

// Primary server
.WithPostgreSqlStorage("Host=primary.db;Database=smtp_db;Username=postgres;Password=secret")

// Read replica for queries
.WithPostgreSqlStorage("Host=replica.db;Database=smtp_db;Username=postgres;Password=secret;Target Session Attributes=read-only")

Connection Failover

// Multiple hosts with failover
config.ConnectionString = 
    "Host=primary.db,replica1.db,replica2.db;" +
    "Database=smtp_db;" +
    "Username=postgres;" +
    "Password=secret;" +
    "Target Session Attributes=read-write;" +
    "Load Balance Hosts=true;";

πŸ“Š Advanced Features

LISTEN/NOTIFY for Real-time Updates

-- Create trigger for notifications
CREATE OR REPLACE FUNCTION notify_new_message() RETURNS trigger AS $$
BEGIN
    PERFORM pg_notify('new_message', NEW.message_id::text);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER message_notify 
AFTER INSERT ON mail.messages 
FOR EACH ROW EXECUTE FUNCTION notify_new_message();
// Listen for notifications in C#
await using var conn = new NpgsqlConnection(connectionString);
await conn.OpenAsync();
conn.Notification += (o, e) => 
{
    Console.WriteLine($"New message: {e.Payload}");
};
await using (var cmd = new NpgsqlCommand("LISTEN new_message", conn))
    await cmd.ExecuteNonQueryAsync();

Row-Level Security

-- Enable row-level security
ALTER TABLE mail.messages ENABLE ROW LEVEL SECURITY;

-- Create policy for user access
CREATE POLICY user_messages ON mail.messages
    FOR ALL
    TO smtp_user
    USING (to_addresses LIKE '%' || current_user || '%');

πŸ” Monitoring & Maintenance

Health Checks

-- Check table sizes
SELECT 
    schemaname,
    tablename,
    pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size,
    n_live_tup as row_count
FROM pg_stat_user_tables 
WHERE schemaname = 'mail';

-- Check slow queries
SELECT 
    query,
    calls,
    mean_exec_time,
    total_exec_time
FROM pg_stat_statements 
WHERE query LIKE '%messages%'
ORDER BY mean_exec_time DESC
LIMIT 10;

Maintenance Tasks

-- Reindex periodically
REINDEX TABLE mail.messages;

-- Update table statistics
ANALYZE mail.messages;

-- Clean up old messages
DELETE FROM mail.messages 
WHERE received_date < NOW() - INTERVAL '6 months';

-- Vacuum to reclaim space
VACUUM (VERBOSE, ANALYZE) mail.messages;

☁️ Cloud Deployments

Amazon RDS PostgreSQL

config.ConnectionString = 
    "Host=mydb.123456789012.us-east-1.rds.amazonaws.com;" +
    "Port=5432;" +
    "Database=smtp_db;" +
    "Username=postgres;" +
    "Password=secret;" +
    "SSL Mode=Require;";

Azure Database for PostgreSQL

config.ConnectionString = 
    "Host=myserver.postgres.database.azure.com;" +
    "Database=smtp_db;" +
    "Username=postgres@myserver;" +
    "Password=secret;" +
    "SSL Mode=Require;";

Google Cloud SQL

config.ConnectionString = 
    "Host=/cloudsql/project:region:instance;" +
    "Database=smtp_db;" +
    "Username=postgres;" +
    "Password=secret;";

πŸ”§ Troubleshooting

Common Issues

Connection Refused

# Check PostgreSQL is running
sudo systemctl status postgresql

# Check pg_hba.conf for authentication
sudo nano /etc/postgresql/14/main/pg_hba.conf

Permission Denied

-- Grant necessary permissions
GRANT ALL PRIVILEGES ON DATABASE smtp_db TO smtp_user;
GRANT ALL ON SCHEMA mail TO smtp_user;
GRANT ALL ON ALL TABLES IN SCHEMA mail TO smtp_user;
GRANT USAGE ON ALL SEQUENCES IN SCHEMA mail TO smtp_user;

Partition Creation Failed

-- Check if table is already partitioned
SELECT relname, relkind 
FROM pg_class 
WHERE relname = 'messages';

-- Convert existing table to partitioned
-- (Requires PostgreSQL 11+ and data migration)

πŸ“‹ Requirements

  • Npgsql package (included)
  • .NET 6.0, 7.0, 8.0, 9.0, or 10.0
  • Zetian SMTP Server package
  • PostgreSQL 11 or later (for partitioning)
  • PostgreSQL 9.4 or later (without partitioning)

πŸ“š Documentation & Support

πŸ“„ License

MIT License - see LICENSE


Built with ❀️ for the .NET community

Product Compatible and additional computed target framework versions.
.NET 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 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. 
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
1.0.4 677 12/1/2025
1.0.3 77 11/1/2025
1.0.2 215 10/29/2025
1.0.1 103 10/25/2025
1.0.0 174,767 10/25/2025

All changes are detailed at https://zetian.soferity.com/changelog.