Amazon S3 and S3-compatible storage provider for Zetian SMTP Server. Delivers enterprise-grade cloud message persistence to AWS S3 or compatible services (MinIO, Wasabi, DigitalOcean Spaces) with advanced features including automatic lifecycle rules for cost optimization, server-side KMS encryption, object versioning, transfer acceleration, customizable storage classes, IAM role support, and built-in compression. Ideal for AWS-based deployments requiring durable, cost-effective message archival with 99.999999999% durability and global accessibility.
$ dotnet add package Zetian.Storage.AmazonS3Amazon S3 and S3-compatible storage provider for Zetian SMTP Server. Provides enterprise-grade object storage with features including server-side encryption, lifecycle management, versioning, transfer acceleration, intelligent tiering, and support for S3-compatible services like MinIO, Wasabi, and DigitalOcean Spaces. Perfect for organizations requiring scalable, durable, and cost-effective message storage with global accessibility.
# Install SMTP Server and Storage Provider
dotnet add package Zetian
dotnet add package Zetian.Storage.AmazonS3
using Zetian.Server;
using Zetian.Storage.AmazonS3.Extensions;
// Configure with AWS S3
var server = new SmtpServerBuilder()
.Port(25)
.WithS3Storage(
"AKIAIOSFODNN7EXAMPLE",
"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"my-smtp-bucket")
.Build();
await server.StartAsync();
var server = new SmtpServerBuilder()
.Port(25)
.WithS3Storage(
"AKIAIOSFODNN7EXAMPLE",
"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"my-smtp-bucket",
config =>
{
config.Region = "us-west-2";
config.KeyPrefix = "messages/";
config.NamingFormat = S3NamingFormat.DateHierarchy;
config.StorageClass = S3StorageClass.Standard;
config.EnableServerSideEncryption = true;
config.CompressMessageBody = true;
config.EnableVersioning = true;
config.EnableLifecycleRules = true;
config.TransitionToIADays = 30;
config.TransitionToGlacierDays = 90;
config.ExpirationDays = 365;
})
.Build();
// MinIO
var server = new SmtpServerBuilder()
.Port(25)
.WithS3CompatibleStorage(
"http://localhost:9000",
"minioadmin",
"minioadmin",
"smtp-messages",
config =>
{
config.ForcePathStyle = true; // Required for MinIO
config.Region = "us-east-1";
config.NamingFormat = S3NamingFormat.YearMonth;
config.CompressMessageBody = true;
})
.Build();
| Option | Type | Default | Description |
|---|---|---|---|
AccessKeyId | string | - | AWS Access Key ID |
SecretAccessKey | string | - | AWS Secret Access Key |
BucketName | string | - | S3 bucket name (required) |
Region | string | "us-east-1" | AWS region |
ServiceUrl | string | - | Custom endpoint for S3-compatible services |
ForcePathStyle | bool | false | Use path-style addressing |
KeyPrefix | string | "smtp/" | Prefix for all object keys |
NamingFormat | enum | DateHierarchy | Object naming format |
StorageClass | enum | Standard | S3 storage class |
EnableServerSideEncryption | bool | true | Enable server-side encryption |
KmsKeyId | string | - | KMS key ID for encryption |
AutoCreateBucket | bool | true | Create bucket if it doesn't exist |
EnableVersioning | bool | false | Enable object versioning |
UseTransferAcceleration | bool | false | Enable transfer acceleration |
EnableLifecycleRules | bool | false | Configure lifecycle rules |
TransitionToIADays | int | 30 | Days before moving to Infrequent Access |
TransitionToGlacierDays | int | 90 | Days before moving to Glacier |
ExpirationDays | int | 365 | Days before automatic deletion |
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 |
// Standard storage class
config.StorageClass = S3StorageClass.Standard;
// Archive storage
config.StorageClass = S3StorageClass.Glacier;
config.StorageClass = S3StorageClass.DeepArchive;
// Intelligent Tiering (automatic)
config.StorageClass = S3StorageClass.IntelligentTiering;
// Infrequent Access
config.StorageClass = S3StorageClass.StandardInfrequentAccess;
// Date hierarchy: /2024/01/15/msg-123.eml
config.NamingFormat = S3NamingFormat.DateHierarchy;
// Year-Month: /2024-01/msg-123.eml
config.NamingFormat = S3NamingFormat.YearMonth;
// Hourly partition: /2024/01/15/14/msg-123.eml
config.NamingFormat = S3NamingFormat.HourlyPartition;
// Flat structure - all messages in prefix
config.NamingFormat = S3NamingFormat.Flat;
// Traditional authentication
.WithS3Storage(
accessKeyId: "AKIAIOSFODNN7EXAMPLE",
secretAccessKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
bucketName: "my-smtp-bucket")
// Use with IAM role (empty credentials)
.WithS3Storage(
"", // Empty access key for IAM role
"", // Empty secret key for IAM role
"my-smtp-bucket",
config =>
{
config.Region = "us-west-2";
})
// With minimal configuration
.WithS3Storage(
"AKIAIOSFODNN7EXAMPLE",
"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"my-smtp-bucket") // Uses default region and settings
.WithS3CompatibleStorage(
serviceUrl: "http://localhost:9000",
accessKeyId: "minioadmin",
secretAccessKey: "minioadmin",
bucketName: "smtp-messages",
config =>
{
config.ForcePathStyle = true; // Required for MinIO
})
.WithS3CompatibleStorage(
serviceUrl: "https://s3.wasabisys.com",
accessKeyId: "WASABI_ACCESS_KEY",
secretAccessKey: "WASABI_SECRET_KEY",
bucketName: "smtp-messages",
config =>
{
config.Region = "us-east-1";
})
.WithS3CompatibleStorage(
serviceUrl: "https://nyc3.digitaloceanspaces.com",
accessKeyId: "DO_SPACES_KEY",
secretAccessKey: "DO_SPACES_SECRET",
bucketName: "smtp-messages",
config =>
{
config.Region = "nyc3";
})
.WithS3CompatibleStorage(
serviceUrl: "https://s3.us-west-000.backblazeb2.com",
accessKeyId: "B2_KEY_ID",
secretAccessKey: "B2_APPLICATION_KEY",
bucketName: "smtp-messages")
config.EnableLifecycleRules = true;
// Transition rules
config.LifecycleRules = new[]
{
new S3LifecycleRule
{
Id = "MoveToIA",
DaysAfterCreation = 30,
Transition = S3StorageClass.StandardIA
},
new S3LifecycleRule
{
Id = "MoveToGlacier",
DaysAfterCreation = 90,
Transition = S3StorageClass.Glacier
},
new S3LifecycleRule
{
Id = "DeleteOld",
DaysAfterCreation = 365,
Expiration = true
}
};
// SSE-S3 (AES-256)
config.EnableServerSideEncryption = true;
// SSE-KMS
config.EnableServerSideEncryption = true;
config.KmsKeyId = "arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab";
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:user/smtp-app"},
"Action": ["s3:PutObject", "s3:GetObject"],
"Resource": "arn:aws:s3:::my-smtp-bucket/*"
}
]
}
// Enable versioning
config.EnableVersioning = true;
// List all versions
var versions = await s3Client.ListObjectVersionsAsync(
new ListObjectVersionsRequest
{
BucketName = "my-smtp-bucket",
Prefix = "messages/"
});
// Restore specific version
await s3Client.CopyObjectAsync(
sourceBucket: "my-smtp-bucket",
sourceKey: "messages/msg-123.eml",
sourceVersionId: "versionId123",
destinationBucket: "my-smtp-bucket",
destinationKey: "messages/msg-123-restored.eml");
// Enable for faster global uploads
config.UseTransferAcceleration = true;
Access Denied
// Check IAM permissions
// Required: s3:PutObject, s3:GetObject, s3:DeleteObject
// For bucket creation: s3:CreateBucket, s3:PutBucketPolicy
Slow Uploads
// Enable transfer acceleration
config.UseTransferAcceleration = true;
// Use appropriate region
config.Region = "us-east-1"; // Same as bucket region
Glacier Retrieval
// Initiate restore
await s3Client.RestoreObjectAsync(new RestoreObjectRequest
{
BucketName = "my-smtp-bucket",
Key = "messages/msg-123.eml",
Days = 1,
RestoreRequest = new RestoreRequest
{
Days = 1,
Tier = RestoreTier.Expedited // 1-5 minutes
// Tier = RestoreTier.Standard // 3-5 hours
// Tier = RestoreTier.Bulk // 5-12 hours
}
});
MIT License - see LICENSE
Built with ❤️ for the .NET community