A Netty-based Kotlin SMTP server library. The core module provides a Spring-free SMTP engine, while the starter modules offer Spring Boot auto-configuration for immediate use.
Kotlin SMTP is a server engine framework, not a monolithic complete mail product.
- What it provides out of the box:
- SMTP receive path (protocol/session/TLS/auth)
- Spool/retry/relay boundary
- Extension SPI for policy, storage, relay, and hooks
- Interceptor chain for extensible command validation
- What you assemble on top for a complete server:
- Anti-spam/anti-malware/policy engines
- User mailbox access protocols (IMAP/POP3/JMAP)
- Admin control plane (queue management/API/UI/ops automation)
See the docs directory for more information.
kotlin-smtp/
├── kotlin-smtp-core # Spring-free SMTP engine
├── kotlin-smtp-spring-boot-starter # Inbound-focused starter (auto-config + default impl)
├── kotlin-smtp-relay # Outbound relay API boundary
├── kotlin-smtp-relay-jakarta-mail # Relay implementation (dnsjava + jakarta-mail)
├── kotlin-smtp-relay-spring-boot-starter # Relay auto-configuration
├── kotlin-smtp-benchmarks # JMH + end-to-end performance profile
└── kotlin-smtp-example-app # Example consumer application
This modular structure is intentional:
core: Protocol/session/TLS/Auth/framing correctnessstarter: Quick startup experience + file-based default implementationrelay*: Outbound delivery boundary separated as optional modules
- RFC 5321 core commands:
EHLO/HELO,MAIL,RCPT,DATA,RSET,QUIT BDAT(Chunking),STARTTLS,AUTH PLAIN/LOGIN- Order-based Command Interceptor Chain (Auth, Envelope, Data stages)
- Rule-based Outbound Relay Chain (Routing and Policy decisions)
- SMTPUTF8/IDN boundary handling
- PROXY protocol (v1), rate limiting
- ETRN/VRFY/EXPN (feature flags)
- Spool/retry/DSN (RFC 3464) handling
- Outbound relay hardening (Null MX detection, permanent/transient classification)
- Automatic
Date/Message-IDsupplementation when missing - Optional outbound policy layer (MTA-STS / DANE basic resolver)
dependencies {
implementation("org.springframework.boot:spring-boot-starter")
implementation("io.github.hgon86:kotlin-smtp-spring-boot-starter:VERSION")
}smtp:
port: 2525
hostname: localhost
routing:
localDomain: local.test
storage:
mailboxDir: ./data/mailboxes
tempDir: ./data/temp
listsDir: ./data/lists
spool:
type: auto # auto | file | redis
dir: ./data/spool
maxRetries: 5
retryDelaySeconds: 60
workerConcurrency: 1
sentArchive:
mode: TRUSTED_SUBMISSION # TRUSTED_SUBMISSION | AUTHENTICATED_ONLY | DISABLEDTo use Redis as the spool backend:
smtp:
spool:
type: redis
dir: ./data/spool
redis:
keyPrefix: kotlin-smtp:spool
maxRawBytes: 26214400
lockTtlSeconds: 900type=autoautomatically selects Redis if aStringRedisTemplatebean exists, otherwise uses file storage.- With
type=redis, queue/lock/metadata are stored in Redis. - Raw
.emlcontent is also stored in Redis (no persistent file storage). - Temporary files are created only at delivery time and cleaned up immediately after use.
- Your application must provide a
StringRedisTemplatebean. - Redis single/cluster/Sentinel configurations follow your application's settings.
The default implementation stores sent mail copies in mailboxDir/<owner>/sent/:
- Authenticated sessions use the AUTH user (
authenticatedUsername) as the owner. - Unauthenticated submissions use the envelope sender's local-part as the owner.
- You can replace this with S3/DB+ObjectStorage by registering a custom
SentMessageStorebean.
Sent mailbox archiving is controlled via smtp.sentArchive.mode:
TRUSTED_SUBMISSION(default): Store messages from AUTH sessions or external relay submissionsAUTHENTICATED_ONLY: Store only AUTH session messagesDISABLED: Do not store sent mail
To restrict unauthenticated relay submissions by IP, use smtp.relay.allowedClientCidrs.
For more complex rules (DB lookups, internal policy engines), implement a custom RelayAccessPolicy bean.
To use distributed rate limiting across multiple instances (optional):
smtp:
rateLimit:
backend: redis
redis:
keyPrefix: kotlin-smtp:conn-ratelimit
connectionCounterTtlSeconds: 900
auth:
rateLimitEnabled: true
rateLimitBackend: redis
rateLimitRedis:
keyPrefix: kotlin-smtp:auth-ratelimitrateLimit.backend=redisenables distributed connection/message limits.auth.rateLimitBackend=redisenables distributed AUTH failure lockouts.- Both modes require a
StringRedisTemplatebean.
To enable outbound domain policy integration (optional):
smtp:
relay:
outboundPolicy:
mtaSts:
enabled: true
connectTimeoutMs: 3000
readTimeoutMs: 5000
dane:
enabled: false- MTA-STS
mode=enforceupgrades relay policy to require TLS + certificate validation. - DANE basic mode uses
_25._tcp.<domain>TLSA presence as a strict TLS signal.
See docs/application.example.yml for a complete configuration example.
import io.github.kotlinsmtp.server.SmtpServer
val server = SmtpServer.create(2525, "smtp.example.com") {
serviceName = "example-smtp"
listener.enableStartTls = true
listener.enableAuth = false
}
server.start()Micrometer integration does not add endpoints to the SMTP port:
- SMTP continues to operate on its existing port (e.g., 2525)
- Metrics are exposed only through the Spring Actuator management channel (opt-in)
Default metrics include:
smtp.connections.activesmtp.sessions.started.total,smtp.sessions.ended.totalsmtp.messages.accepted.total,smtp.messages.rejected.totalsmtp.spool.pending,smtp.spool.queued.total,smtp.spool.completed.totalsmtp.spool.dropped.total,smtp.spool.retry.scheduled.totalsmtp.spool.delivery.recipients.total{result=delivered|transient_failure|permanent_failure}
Prometheus exposure example (optional):
dependencies {
implementation("org.springframework.boot:spring-boot-starter-actuator")
runtimeOnly("io.micrometer:micrometer-registry-prometheus")
}management:
server:
port: 8081
endpoints:
web:
exposure:
include: health,info,prometheusIn this case, /actuator/prometheus is available only on the management port.
./gradlew :kotlin-smtp-example-app:bootRunRun JMH benchmark (automatically starts local SMTP server inside benchmark process):
./gradlew :kotlin-smtp-benchmarks:jmhRun end-to-end profile test (throughput + latency summary):
./gradlew :kotlin-smtp-benchmarks:performanceTestCurrent local baseline example:
~195 emails/sat4KB,8concurrent clients~5.1sper1,000messagesp95 latency ~87.5ms
Override workload for profile test (optional):
./gradlew :kotlin-smtp-benchmarks:performanceTest \
-Dkotlinsmtp.performance.clients=16 \
-Dkotlinsmtp.performance.messagesPerClient=300 \
-Dkotlinsmtp.performance.bodyBytes=16384docs/QUICKSTART.md: 10-minute guide to initial setup and verificationdocs/ARCHITECTURE.md: Runtime architecture and boundariesdocs/CONFIGURATION.md: Configuration referencedocs/EXTENSION.md: Extension strategy, override model, and goal-to-interface mappingdocs/RECIPES.md: Minimal extension recipesdocs/LIFECYCLE.md: Runtime lifecycle and hook timingdocs/SECURITY_RELAY.md: Relay security hardening guide
Apache License 2.0 (LICENSE)