Summary
mail.sslPort / mail.ssl-port is required to be a valid Int even when SSL is disabled (sslEnabled = false). If MAIL_SSL_PORT resolves to an empty string, config load throws and the notification entity crashes at send time — so mail is never attempted, with a misleading error.
Observed error
When MAIL_SSL_ENABLED=false and MAIL_SSL_PORT is unset/blank (only the plaintext MAIL_PORT is provided):
com.typesafe.config.ConfigException$Generic:
[notification.mail.ssl-port] ... ssl-port has type STRING value '' rather than int (32-bit integer)
at app.softnetwork.notification.config.MailSettings.MailConfig(MailSettings.scala:13)
at app.softnetwork.notification.spi.SimpleMailProvider.mailConfig(SimpleMailProvider.scala:21)
at app.softnetwork.notification.spi.SimpleMailProvider.sendMail(SimpleMailProvider.scala:61)
Caused by: java.lang.NumberFormatException: null
Caused by: java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
The supervisor then stops the entity (StopSupervisor saw failure), so the email stays Pending/Undelivered and the caller just times out waiting for it.
Root cause
In reference.conf (notification-core):
mail {
port = 25
port = ${?MAIL_PORT}
sslPort = 465
sslPort = ${?MAIL_SSL_PORT}
ssl-port = ${notification.mail.sslPort}
sslEnabled = true
sslEnabled = ${?MAIL_SSL_ENABLED}
}
SimpleMailProvider always reads sslPort, regardless of sslEnabled:
commonsMail.setSmtpPort(mailConfig.port)
commonsMail.setSslSmtpPort(mailConfig.sslPort.toString) // always read
commonsMail.setSSLOnConnect(mailConfig.sslEnabled)
Two problems:
- Blank env vars override the default.
sslPort = ${?MAIL_SSL_PORT} substitutes when the variable is defined, including when it is the empty string. A MAIL_SSL_PORT="" (common in templated/compose/k8s env) overrides the 465 default with "", which then fails Int parsing. There's no blank-as-absent guard.
sslPort is mandatory even when SSL is off. When sslEnabled = false the connection uses the plain port, yet sslPort must still parse as an Int or the whole MailConfig fails to load. A plaintext-only deployment (e.g. a dev/test SMTP sink like mailpit) is forced to also supply a dummy MAIL_SSL_PORT.
Impact
Any deployment pointing at a plaintext SMTP server with MAIL_SSL_ENABLED=false crashes config load unless a throwaway MAIL_SSL_PORT int is also set. The failure surfaces only at first send, as a config exception unrelated to the actual misconfiguration — hard to diagnose.
Suggested fixes (any one)
- Treat blank env values as absent (e.g. read via an
Option/nonBlank helper, falling back to the 465 default) for sslPort, port, and the other numeric mail settings.
- Make
sslPort optional / lazily required only when sslEnabled || startTLSEnabled, so plaintext-only configs don't need it.
- In
SimpleMailProvider, only call setSslSmtpPort(...) when sslEnabled is true.
Workaround (downstream)
Set MAIL_SSL_PORT to a valid int even when SSL is disabled:
MAIL_HOST: mailpit
MAIL_PORT: "1025"
MAIL_SSL_PORT: "1025" # unused, but must parse as Int
MAIL_SSL_ENABLED: "false"
MAIL_START_TLS_ENABLED: "false"
Found while wiring the license-server compose-smoke e2e against a mailpit SMTP sink.
Summary
mail.sslPort/mail.ssl-portis required to be a validInteven when SSL is disabled (sslEnabled = false). IfMAIL_SSL_PORTresolves to an empty string, config load throws and the notification entity crashes at send time — so mail is never attempted, with a misleading error.Observed error
When
MAIL_SSL_ENABLED=falseandMAIL_SSL_PORTis unset/blank (only the plaintextMAIL_PORTis provided):The supervisor then stops the entity (
StopSupervisor saw failure), so the email staysPending/Undeliveredand the caller just times out waiting for it.Root cause
In
reference.conf(notification-core):SimpleMailProvideralways readssslPort, regardless ofsslEnabled:commonsMail.setSmtpPort(mailConfig.port) commonsMail.setSslSmtpPort(mailConfig.sslPort.toString) // always read commonsMail.setSSLOnConnect(mailConfig.sslEnabled)Two problems:
sslPort = ${?MAIL_SSL_PORT}substitutes when the variable is defined, including when it is the empty string. AMAIL_SSL_PORT=""(common in templated/compose/k8s env) overrides the465default with"", which then failsIntparsing. There's no blank-as-absent guard.sslPortis mandatory even when SSL is off. WhensslEnabled = falsethe connection uses the plainport, yetsslPortmust still parse as anIntor the wholeMailConfigfails to load. A plaintext-only deployment (e.g. a dev/test SMTP sink like mailpit) is forced to also supply a dummyMAIL_SSL_PORT.Impact
Any deployment pointing at a plaintext SMTP server with
MAIL_SSL_ENABLED=falsecrashes config load unless a throwawayMAIL_SSL_PORTint is also set. The failure surfaces only at first send, as a config exception unrelated to the actual misconfiguration — hard to diagnose.Suggested fixes (any one)
Option/nonBlankhelper, falling back to the465default) forsslPort,port, and the other numeric mail settings.sslPortoptional / lazily required only whensslEnabled || startTLSEnabled, so plaintext-only configs don't need it.SimpleMailProvider, only callsetSslSmtpPort(...)whensslEnabledis true.Workaround (downstream)
Set
MAIL_SSL_PORTto a valid int even when SSL is disabled:Found while wiring the license-server compose-smoke e2e against a mailpit SMTP sink.