- Governance options now follow the email starter
- Asynchronous failures stay with the returned future
- Serialized emails keep their send-ready content
- TLS certificate trust is strict by default
- Extra properties follow normal configuration precedence
- Embedded-image bases now contain resolution
- DKIM signing must keep the From header
- S/MIME signature status reports integrity, not trust
Governance options now follow the email starter
Impact: Code that starts with EmailBuilder.ignoringDefaults(), or calls the ignore methods
on EmailStartingBuilder, must put those options after an email starter when upgrading to 9.2.0.
Before the 8.0 governance overhaul, the email builder itself loaded property defaults. The pre-start ignore method was how you asked for a genuinely blank builder. Defaults and overrides now belong to the Mailer and are applied later, so the old call order no longer matches what the option controls.
The old forwarding logic was also broken: blank emails lost the override opt-out, while copied emails lost the defaults opt-out because the implementation called the override method twice. Moving both options after the starter removes that fragile hand-off.
// Before 9.2.0:
Email email = EmailBuilder.ignoringDefaults()
.ignoringOverrides()
.copying(original)
.buildEmail();
// Since 9.2.0, start with the operation and then configure the email:
Email email = EmailBuilder.copying(original)
.ignoringDefaults()
.ignoringOverrides()
.buildEmail();
The same follow-up methods work after startingBlank(), forwarding(...),
replyingTo(...), and replyingToAll(...). The boolean overloads remain
available for conditional configuration and for the CLI.
This does not make startingBlank() ignore governance. It starts with no email content; unless you add an ignore
option afterward, defaults and overrides remain eligible for application when the Mailer prepares the email.
See GitHub issue #689 for the API history and implementation record.
Asynchronous failures stay with the returned future
Impact: Code that catches validation or preparation errors around sendMail(email, true)
must now handle them through the returned future.
Before 9.2.0, an asynchronous send had two ways to report a problem. Applying defaults and overrides or validating the email could throw directly,
while later failures completed the CompletableFuture exceptionally. Callers needed both a
try/catch around the call and an error handler on the future.
From 9.2.0 onward, the future covers the complete asynchronous operation: email governance, validation, scheduling, conversion, connection and transport. Preparation still takes place on the calling thread; only its error-reporting path changes. Clear API mistakes, such as passing a null email, can still throw immediately.
mailer.sendMail(email, true)
.whenComplete((unused, failure) -> {
if (failure != null) {
log.error("Unable to send email", failure);
}
});
Synchronous calls are unchanged: sendMail(email, false) throws on the calling thread and returns an
already-completed future after success. See GitHub issue #691 for the
implementation record and Handling asynchronous results for the ongoing reference.
Serialized emails keep their send-ready content
Impact: Serializing an email can now read lazy or remote attachment sources and produce a larger result. The result may also contain PKCS12 private-key material and passwords, so store and transport it as sensitive data.
Before 9.2.0, Email implemented Java serialization, but several parts needed to send it were transient.
Attachment, embedded-image and decrypted-attachment metadata survived while their DataSource content vanished.
A forwarded MimeMessage and S/MIME signing configuration vanished as well.
Version 9.2.0 turns serialization into a send-ready snapshot. Resource input streams are read and closed while the email is written, then restored as repeatable, read-only byte sources. Forwarded messages are stored as RFC 822 data and rebuilt with a neutral mail session. S/MIME signing configuration, including its PKCS12 data, is retained. Attachment names, content types, descriptions, content IDs and transfer-encoding choices remain intact.
The snapshot preserves mail content, not the runtime behavior of a custom DataSource. Its concrete class is not
serialized, even if that class implements Serializable. Lazy loading, network access, caching, write support and
custom methods are not available on the restored byte source.
A pre-9.2.0 stream can still be deserialized for its subject, body, recipients, headers and resource metadata. Content that was never written cannot be recovered: forwarded MIME content and signing configuration remain absent, while reading or sending a legacy attachment fails at the point of use with a version-specific error.
See GitHub issue #690 for the implementation record and Serializing Email objects for the ongoing behavior reference.
TLS certificate trust is strict by default
Impact: Applications that rely on a self-signed SMTP certificate, or on a private certificate authority that is not in the JVM trust store, may fail to connect after upgrading to 9.2.0.
Before 9.2.0, Simple Java Mail set mail.*.ssl.trust=* by default. Hostname verification was enabled,
but the certificate issuer was not checked against the JVM trust store. Version 9.2.0 stops setting that wildcard. TLS connections now require both
a trusted certificate chain and a certificate that names the SMTP host.
Most public SMTP services already use a certificate trusted by the JVM, so no change is needed. For private infrastructure, the preferred fix is to add the issuing CA to the JVM trust store. This keeps normal certificate-chain and hostname validation intact.
Mailer mailer = MailerBuilder
.withSMTPServer("smtp.example.com", 587, username, password)
.withTransportStrategy(TransportStrategy.SMTP_TLS)
.buildMailer();
// Since 9.2.0, the defaults are equivalent to:
// .trustingAllHosts(false)
// .verifyingServerIdentity(true)
If the CA cannot be added yet, use the narrowest temporary exception you can. A named-host exception still bypasses normal issuer trust for that host, while trusting all hosts bypasses it everywhere. Keep server identity verification enabled in either case.
Mailer mailer = MailerBuilder
.withSMTPServer("smtp.internal.example", 587, username, password)
.withTransportStrategy(TransportStrategy.SMTP_TLS)
.trustingSSLHosts("smtp.internal.example")
.buildMailer();
// Broad compatibility escape hatch; avoid for production deployments:
// .trustingAllHosts(true)
The same settings are available through properties. The first two values below are the 9.2.0 defaults and normally do not need to be declared.
simplejavamail.defaults.trustallhosts=false
simplejavamail.defaults.verifyserveridentity=true
# Optional named-host exception when the private CA cannot be installed yet:
simplejavamail.defaults.trustedhosts=smtp.internal.example
See GitHub issue #677 for the implementation record and Certificate trust for the full security model.
Extra properties follow normal configuration precedence
Impact: Applications that set the same simplejavamail.extraproperties.* key in
more than one configuration source may resolve a different value after upgrading to 9.2.0.
Before 9.2.0, a property-file value in the extra-property namespace accidentally overrode the matching environment variable and system property. Version 9.2.0 brings these passthrough Jakarta Mail settings in line with the rest of Simple Java Mail: system properties take priority over environment variables, which take priority over property-file values.
# simplejavamail.properties
simplejavamail.extraproperties.mail.smtp.timeout=30000
# A JVM argument now overrides the file value:
# -Dsimplejavamail.extraproperties.mail.smtp.timeout=10000
Nothing changes when a key appears in only one source. If a key is duplicated, keep the intended deployment override in the higher-priority source or remove the duplicate.
See GitHub issue #685 for the implementation record and Available properties for the complete configuration reference.
Embedded-image bases now contain resolution
Impact: Applications that use embedded-image auto-resolution with a configured base may stop resolving references that leave that base. If successful resolution is required, building those messages will now fail instead of reading the outside resource.
Before 9.2.0, the base directory, classpath and URL settings helped locate images, but their outside-base checks could be bypassed with paths such as
../private/logo.png, a similarly named sibling path, an alternate URL origin or a redirect. That made the
base unsuitable as a boundary for HTML edited outside the application.
Version 9.2.0 normalizes every path before checking it. Files are checked at their real location so symbolic links cannot lead outside the base; classpath checks use complete path segments; and URLs must keep the base scheme, host, effective port and path through every redirect.
Email email = EmailBuilder.startingBlank()
.withEmbeddedImageAutoResolutionForFiles(true)
.withEmbeddedImageAutoResolutionForClassPathResources(true)
.withEmbeddedImageAutoResolutionForURLs(true)
.withEmbeddedImageBaseDir(RESOURCES_PATH + "/images")
.withEmbeddedImageBaseClassPath("/images")
.withEmbeddedImageBaseUrl("https://static.example.com/mail/")
.embeddedImageAutoResolutionMustBeSuccesful(true)
.buildEmail();
// Keep the allowingEmbeddedImageOutsideBase... options false.
No base still means unrestricted resolution, and setting the matching
allowingEmbeddedImageOutsideBase...(true) option keeps the old broad behavior. Those modes are deliberate
escape hatches, not safe choices for untrusted or freely editable HTML.
See GitHub issue #678 for the implementation record and Embedding images for the complete configuration.
DKIM signing must keep the From header
Impact: A DKIM configuration that lists From among its header exclusions
now fails when the configuration is created, whether it comes from Java code or properties.
The From header identifies the signed domain and has always been mandatory in a valid DKIM
signature. Version 9.2.0 catches the mistake at configuration time instead of accepting a misleading exclusion.
DkimConfig.builder()
// ... private key, signing domain and selector
// .excludedHeadersFromDkimDefaultSigningList("From", "Subject") // no longer accepted
.excludedHeadersFromDkimDefaultSigningList("Message-ID", "Date") // only if this relay rewrites them
.build();
Remove From from the exclusion list. Keep any remaining exclusions limited to headers that a
specific downstream relay demonstrably rewrites.
See GitHub issue #679 for the implementation record and DKIM signing for the full configuration reference.
S/MIME signature status reports integrity, not trust
Impact: Applications that treated getSmimeSignatureValid() == true as proof of a trusted
sender must add their own certificate and identity validation. Unverified Outlook content can now report null,
and one failed signature now makes a combined result false.
Version 9.2.0 gives the nullable status a strict contract: true means every applicable signature represented
by the metadata was cryptographically checked with its included signer certificate; false means at least one
check failed or could not be completed; and null means no check applied or no check was performed.
OriginalSmimeDetails details = parsedEmail.getOriginalSmimeDetails();
if (!Boolean.TRUE.equals(details.getSmimeSignatureValid())) {
// Invalid, unverifiable, or not checked: do not rely on message integrity.
}
// A true result verifies the cryptographic signature only.
// It does not authenticate the From address.
Simple Java Mail uses the signer certificate carried in the S/MIME message. It does not perform PKIX path validation, check certificate dates,
revocation or key usage, or match the certificate identity to From.
getSmimeSignedBy() is therefore descriptive certificate metadata, not a trusted identity.
Invalid signed content is still parsed when it can be extracted; this change does not undo the lenient conversion from issue #571. See GitHub issue #680 for the implementation record and What the signature status means for the full contract.