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/
├── 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-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- SMTPUTF8/IDN boundary handling
- PROXY protocol (v1), rate limiting
- ETRN/VRFY/EXPN (feature flags)
- Spool/retry/DSN (RFC 3464) handling
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
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.
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:bootRundocs/STATUS.md: Current progressdocs/ROADMAP.md: Remaining work prioritiesdocs/THIN_ARCHITECTURE.md: Runtime/boundary summarydocs/PUBLIC_API_CANDIDATES.md: Public API boundariesdocs/RELAY_MODULES.md: Relay module design/policy
Apache License 2.0 (LICENSE)