Configuring Simple Java Mail
Simple Java Mail provides full configuration through programmatic API as well as system variables, environment variables and properties files (including Spring).
The Java API and config files complement each other. If you provide the overlapping configuration, the programmatic API takes priority, overriding system and environment values, overriding property values.
Central to configuring Simple Java Mail is the MailerBuilder. As the library grew maintaining all the constructors and setters proved unwieldy and so it moved to a completely builder based fluent API which produces a largely immutable
Mailer instance.
Second, there is the ConfigLoader, which contains all the preconfigured defaults read initially from .properties files. It contains programmatic API to clear, add or replace default values.
- Programmatic API - common settings
- Programmatic API - other settings
- Properties files
- Available properties
- Mailer level email defaults and overrides
- Combining everything for multiple environments
- Spring support
- Batch and clustering support
- How connections work without batch-module
- Reusing connections with batch-module's connection pool
- Advanced performance tuning: Clustering with multiple connection pools
Programmatic API - common settings
Everything can be configured through the java API. Specifically the builders are the entry point to creating Mailers and Emails and everything can be configured through them.
// start with a builder
MailerBuilder.withSMTPServer("smtp.host.com", 25, "username", "password");
// or
MailerBuilder
.withSMTPServerHost("smtp.host.com")
.withSMTPServerPort(25)
.withSMTPServerUsername("username")
.withSMTPServerPassword("password");
// you can even leave out some details for an anonymous SMTP server
MailerBuilder.withSMTPServer("smtp.host.com", 25);
// or
MailerBuilder
.withSMTPServerHost("smtp.host.com")
.withSMTPServerPort(25);
// adding the transport strategy...
currentMailerBuilder.withTransportStrategy(TransportStrategy.SMTP_TLS)
// or instead adding anonymous proxy configuration
currentMailerBuilder.withProxy("proxy.host.com", 1080);
// or
currentMailerBuilder
.withProxyHost("proxy.host.com")
.withProxyPort(1080);
// or authenticated proxy
currentMailerBuilder
.withProxy("proxy.host.com", 1080, "proxy username", "proxy password");
// or
currentMailerBuilder
.withProxyHost("proxy.host.com")
.withProxyPort(1080)
.withProxyUsername("proxy username")
.withProxyPassword("proxy password");
// anonymous smtp + anonymous proxy + default SMTP protocol strategy
currentMailerBuilder
.withSMTPServerHost("smtp.host.com").withSMTPServerPort(25)
.withProxyHost("proxy.host.com").withProxyPort(1080);
// configure everything!
MailerBuilder
.withSMTPServer("smtp.host.com", 587, "user@host.com", "password")
.withTransportStrategy(TransportStrategy.SMTP_TLS)
.withProxy("socksproxy.host.com", 1080, "proxy user", "proxy password")
.buildMailer()
.sendMail(email);
// preconfigured Session?
MailerBuilder.usingSession(session);
// preconfigured but you need anonymous proxy?
MailerBuilder
.usingSession(session)
.withProxy("socksproxy.host.com", 1080);
// preconfigured but you need authenticated proxy?
MailerBuilder
.usingSession(session)
.withProxy("socksproxy.host.com", 1080, "proxy user", "proxy password");
Programmatic API - other settings
Aside from transport strategy, SMTP and Proxy server details, there are a few other more generic settings.
// ignore invalid email addresses (if email validator was provided)
// and ignore CRLF injection suspicions
// useful in case you want to delegate all responsibility to the server
currentMailerBuilder.disablingAllClientValidation(true);
// make the underlying Jakarta Mail Session produce debug output
currentMailerBuilder
.withDebugLogging(true)
.withDebugOutput(SessionDebugOutput.SLF4J); // STDOUT, STDERR or SLF4J
// skip actually sending email, just log it
currentMailerBuilder.withTransportModeLoggingOnly(true);
// custom SSL connection factory (note: breaks setups with authenticated proxy!)
currentMailerBuilder.withCustomSSLFactoryClass("org.mypackage.MySSLFactory");
currentMailerBuilder.withCustomSSLFactoryInstance(mySSLFactoryInstance);
// change email validation strategy
currentMailerBuilder.withEmailValidator(
JMail.strictValidator()
.requireOnlyTopLevelDomains(TopLevelDomain.DOT_COM)
.withRule(email -> email.localPart().startsWith("allowed"))
)
// reset to default RFC compliant checks:
currentMailerBuilder.clearEmailValidator();
// deactivate email validation completely:
currentMailerBuilder.resetEmailValidator();
// change SOCKS5 bridge port in case of authenticated proxy
currentMailerBuilder.withProxyBridgePort(1081); // always localhost
// set custom properties
currentMailerBuilder.withProperties(new Properties());
currentMailerBuilder.withProperties(new HashMap());
currentMailerBuilder.withProperty("mail.smtp.sendpartial", true);
// or directly modify the internal Session instance:
mailer.getSession().getProperties().setProperty("mail.smtp.sendpartial", true);
/* Regarding the following config on trusting hosts,
the Javadoc has more detailed info (in the mailer builder api). */
// trust all hosts for SSL connections
currentMailerBuilder.trustingAllHosts(true);
// or white list hosts for SSL connections (identity key validation notwithstanding)
currentMailerBuilder.trustingSSLHosts("a", "b", "c", ...);
// or clearing these options
currentMailerBuilder.clearTrustedSSLHosts();
currentMailerBuilder.resetTrustingAllHosts();
/* Regarding the following config on identifying hosts,
the Javadoc has more detailed info (in the mailer builder api). */
// don't validate keys thus not verifying server hosts
currentMailerBuilder.verifyingServerIdentity(false);
currentMailerBuilder.resetVerifyingServerIdentity();
// batch-module executor defaults (used when batch-module is loaded)
currentMailerBuilder.withThreadPoolSize(3);
// 0: core threads stay alive, !0: threads die after delay (default 1)
currentMailerBuilder.withThreadPoolKeepAliveTime(5000);
// completely replace the thread pool executor with your own
// this negates all related properties such as pool size and keepAliveTime
currentMailerBuilder.withExecutorService(new MyAwesomeCustomThreadPoolExecutor())
// change the SMTP session timeout (affects socket connect-, read- and write timeouts)
currentMailerBuilder.withSessionTimeout(10 * 1000); // 10 seconds for quick disconnect
// bind the outgoing SMTP socket to a local/source address
// the local port is advanced and usually should be omitted
currentMailerBuilder.withLocalBindAddress("203.0.113.10");
currentMailerBuilder.withLocalBindAddress("203.0.113.10", 25252);
// identify the SMTP client hostname sent in EHLO/HELO
// useful for corporate relay policy, logging, tracing and diagnostics
currentMailerBuilder.withSmtpClientHostname("orders-service.prod.example.com");
currentMailerBuilder.clearSmtpClientHostname();
// configure DKIM once for all emails sent through this mailer
// individual emails can still provide their own DKIM config
currentMailerBuilder.withDefaultDkimSigning(defaultDkimConfig);
// change the default sending logic to your own approach
currentMailerBuilder.withCustomMailer(yourOwnCustomMailerImpl); // send emails, test connections
Properties files
With properties files you can define defaults and overrides. You can also provide overriding value by defining system variables.
Simple Java Mail will automatically load properties from simplejavamail.properties, if available on the classpath.
Alternatively, you can manually load additional properties files in a number of ways.
Properties are loaded in order of priority from high to low:
- Programmatic values
- System variables
- Environment variables
- Properties from config files
ConfigLoader.loadProperties("overrides-on-classpath.properties", /* addProperties = */ true);
ConfigLoader.loadProperties(new File("d:/replace-from-environment.properties"), /* addProperties = */ false);
ConfigLoader.loadProperties(usingMyOwnInputStream, addProperties);
ConfigLoader.loadProperties(usingMyOwnPropertiesObject, addProperties);
This clears everything:
ConfigLoader.loadProperties(new Properties(), /* addProperties = */ false);
Available properties
Almost everything can be set as a default property. This way you can easily configure environments without changing the code.
# Debugging and transport
simplejavamail.javaxmail.debug=true
simplejavamail.javaxmail.debug.out=SLF4J
simplejavamail.transportstrategy=SMTP_TLS
simplejavamail.transport.mode.logging.only=false
simplejavamail.opportunistic.tls=false
# SMTP destination, protocol identity and local/source binding
simplejavamail.smtp.host=smtp.default.com
simplejavamail.smtp.port=587
simplejavamail.smtp.username=username
simplejavamail.smtp.password=password
simplejavamail.smtp.clienthostname=orders-service.prod.example.com
simplejavamail.smtp.localaddress=203.0.113.10
simplejavamail.smtp.localport=25252
# Validation, SSL and proxying
simplejavamail.disable.all.clientvalidation=false
simplejavamail.custom.sslfactory.class=org.mypackage.ssl.MySSLSocketFactoryClass
simplejavamail.proxy.host=proxy.default.com
simplejavamail.proxy.port=1080
simplejavamail.proxy.username=username proxy
simplejavamail.proxy.password=password proxy
simplejavamail.proxy.socks5bridge.port=1081
simplejavamail.defaults.sessiontimeoutmillis=60000
simplejavamail.defaults.trustallhosts=false
simplejavamail.defaults.trustedhosts=192.168.1.122;mymailserver.com;ix55432y
simplejavamail.defaults.verifyserveridentity=true
# Email defaults
simplejavamail.defaults.subject=Sweet News
simplejavamail.defaults.from.name=From Default
simplejavamail.defaults.from.address=from@default.com
simplejavamail.defaults.replyto.name=Reply-To Default
simplejavamail.defaults.replyto.address=reply-to@default.com
simplejavamail.defaults.bounceto.name=Bounce-To Default
simplejavamail.defaults.bounceto.address=bounce-to@default.com
simplejavamail.defaults.to.name=To Default
simplejavamail.defaults.to.address=to@default.com
simplejavamail.defaults.cc.name=CC Default
simplejavamail.defaults.cc.address=cc@default.com
simplejavamail.defaults.bcc.name=
simplejavamail.defaults.bcc.address=bcc1@default.com;bcc2@default.com
# Delivery Status Notification defaults
simplejavamail.defaults.delivery.status.notification.notify=FAILURE,DELAY
simplejavamail.defaults.delivery.status.notification.return.option=HEADERS_ONLY
# Content-Transfer-Encoding defaults
simplejavamail.defaults.content.transfer.encoding=QUOTED_PRINTABLE
simplejavamail.defaults.body.text.content.transfer.encoding=BIT7
simplejavamail.defaults.body.html.content.transfer.encoding=BASE_64
simplejavamail.defaults.body.calendar.content.transfer.encoding=QUOTED_PRINTABLE
# Batch-module executor defaults
simplejavamail.defaults.poolsize=10
simplejavamail.defaults.poolsize.keepalivetime=2000
# Batch-module connection pool defaults
simplejavamail.defaults.connectionpool.clusterkey.uuid=38400000-8cf0-11bd-b23e-10b96e4ef00d
simplejavamail.defaults.connectionpool.coresize=0
simplejavamail.defaults.connectionpool.maxsize=4
simplejavamail.defaults.connectionpool.claimtimeout.millis=10000
simplejavamail.defaults.connectionpool.expireafter.millis=5000
simplejavamail.defaults.connectionpool.loadbalancing.strategy=ROUND_ROBIN
# Batch-module per-cluster defaults
simplejavamail.defaults.connectionpool.clusters.orders.clusterkey.uuid=00000000-0000-0000-0000-000000000301
simplejavamail.defaults.connectionpool.clusters.orders.coresize=0
simplejavamail.defaults.connectionpool.clusters.orders.maxsize=3
simplejavamail.defaults.connectionpool.clusters.orders.claimtimeout.millis=30000
simplejavamail.defaults.connectionpool.clusters.orders.expireafter.millis=600000
simplejavamail.defaults.connectionpool.clusters.orders.loadbalancing.strategy=RANDOM_ACCESS
simplejavamail.defaults.connectionpool.clusters.00000000-0000-0000-0000-000000000302.maxsize=8
# S/MIME defaults
simplejavamail.smime.signing.keystore=my_keystore.pkcs12
simplejavamail.smime.signing.keystore_password=keystore_password
simplejavamail.smime.signing.key_alias=key_alias
simplejavamail.smime.signing.key_password=key_password
simplejavamail.smime.signing.algorithm=SHA3-256withECDSA
simplejavamail.smime.encryption.certificate=x509inStandardPEM.crt
simplejavamail.smime.encryption.key_encapsulation_algorithm=RSA_OAEP_SHA384
simplejavamail.smime.encryption.cipher=DES_EDE3_WRAP
# DKIM defaults
simplejavamail.dkim.signing.private_key_file_or_data=my_dkim_key.der
simplejavamail.dkim.signing.selector=dkim1
simplejavamail.dkim.signing.signing_domain=your-domain.com
simplejavamail.dkim.signing.use_length_param=false
simplejavamail.dkim.signing.excluded_headers_from_default_signing_list=From
simplejavamail.dkim.signing.header_canonicalization=RELAXED
simplejavamail.dkim.signing.body_canonicalization=RELAXED
simplejavamail.dkim.signing.algorithm=SHA256_WITH_RSA
# Embedded image dynamic resolution
simplejavamail.embeddedimages.dynamicresolution.enable.dir=true
simplejavamail.embeddedimages.dynamicresolution.enable.url=false
simplejavamail.embeddedimages.dynamicresolution.enable.classpath=true
simplejavamail.embeddedimages.dynamicresolution.base.dir=/var/opt/static
simplejavamail.embeddedimages.dynamicresolution.base.url=
simplejavamail.embeddedimages.dynamicresolution.base.classpath=/static
simplejavamail.embeddedimages.dynamicresolution.outside.base.dir=true
simplejavamail.embeddedimages.dynamicresolution.outside.base.classpath=false
simplejavamail.embeddedimages.dynamicresolution.outside.base.url=false
simplejavamail.embeddedimages.dynamicresolution.mustbesuccesful=true
Spring Boot users can use the same canonical property names. A few generated metadata aliases exist for IDE compatibility where Spring's relaxed binding cannot represent the original name cleanly.
simplejavamail.javaxmail.debug-out=SLF4J
simplejavamail.custom.sslfactory.clazz=org.mypackage.ssl.MySSLSocketFactoryClass
simplejavamail.defaults.poolsize-more.keepalivetime=2000
simplejavamail.smime.signing.keystore-password=keystore_password
simplejavamail.smime.signing.key-alias=key_alias
simplejavamail.smime.signing.key-password=key_password
simplejavamail.dkim.signing.private-key-file-or-data=my_dkim_key.der
simplejavamail.dkim.signing.signing-domain=your-domain.com
simplejavamail.dkim.signing.excluded-headers-from-default-signing-list=From
Then there are extra properties which will directly go on the internal Session object when building a Mailer instance.
simplejavamail.extraproperties.my.extra.property=value
simplejavamail.extraproperties.mail.smtp.ssl.socketFactory.class=org.mypackage.MySSLSocketFactory
simplejavamail.extraproperties.mail.smtp.timeout=30000
Mailer level email defaults and overrides
With property files and system variables you can define global defaults. But sometimes that is not enough.
With Simple Java Mail, you can set both defaults and overrides on the Mailer level using Java code. This will override
global defaults loaded from a property file for example. However, before using it as defaults Email reference, you can still have your defaults
initialized with global defaults, by using emailBuilder.buildEmailCompletedWithDefaultsAndOverrides().
Email yourServerLevelDefaults = EmailBuilder.startingBlank()
/* set your defaults here */
.buildEmailCompletedWithDefaultsAndOverrides(); // complete the instance with global defaults
Mailer adminServer = mailerBuilder
(..)
.withEmailDefaults(yourServerLevelDefaults)
.withEmailOverrides(yourServerLevelOverrides)
.buildMailer();
One use case for this is when you have multiple mailer instances, each for a separate SMTP server clustered together (see clustering), you may want to define defaults or overrides for a specific server. You can do that by defining a reference Email instance and set it as defaults or overrides parameter.
// for example: force FROM to be always the same for the
// specific SMTP adminServer from the previous example:
Email yourServerLevelOverrides = EmailBuilder.startingBlank()
.from("Admin", "admin@yourcompany.com")
.buildEmail();
You can exclude specific emails completely from having defaults or overrides applied by using ignoreDefaults or ignoreOverrides.
emailBuilder.ignoringDefaults(true)
emailBuilder.ignoringOverrides(true)
You can even exclude specific fields from the defaults or overrides if you want to make very specific exceptions.
emailBuilder
.dontApplyDefaultValueFor(EmailProperty.FROM, EmailProperty.REPLY_TO)
.dontApplyOverrideValueFor(EmailProperty.DKIM_SIGNING_CONFIG)
Combining everything for multiple environments
Let's set up configuration for a test, acceptance and production environment.
Properties for the environments
#global default properties (simplejavamail.properties on classpath)
# anonoymous SMTP inside 'safe' DMZ
simplejavamail.smtp.host=dmz.smtp.candyshop.com
simplejavamail.smtp.port=25
# default sender and reply-to address
simplejavamail.defaults.from.name=The Candy App
simplejavamail.defaults.from.address=candyapp@candystore.com
simplejavamail.defaults.replyto.name=Candystore Helpdesk
simplejavamail.defaults.replyto.address=helpdesk@candystore.com
#overrides from TEST and UAT .../config/candystore/simplejavamail.properties
# always send a copy to test archive
simplejavamail.defaults.bcc.name=Archive TST UAT
simplejavamail.defaults.bcc.address=test-archive@candyshop.com
#overrides from PRODUCTION .../config/candystore/simplejavamail.properties
# always send a copy to production archive
simplejavamail.defaults.bcc.name=Archive PRODUCTION
simplejavamail.defaults.bcc.address=prod-archive@candyshop.com
# smtp server in production is protected
simplejavamail.smtp.username=creamcake
simplejavamail.smtp.password=crusty_l0llyp0p
# sending mails in production must go through proxy
simplejavamail.proxy.host=proxy.candyshop.com
simplejavamail.proxy.port=1080
simplejavamail.proxy.username=candyman
simplejavamail.proxy.password=I has the sugarcanes!!1!
Now for the programmatic part
// simplejavamail.properties is automatically loaded
// assume that every environment provides its own property file
ConfigLoader.loadProperties(new File(".../config/candystore/simplejavamail.properties"));
// see if we need to do some specific override for some reason
if (someSpecialCondition) {
ConfigLoader.loadProperties("special-override.properties", true);
}
// or maybe we want to ditch all defaults and trust someone else's config completely
if (ditchOwnAndTrustOtherSource) {
ConfigLoader.loadProperties(someFileOrInputSource, false);
}
// maybe the config service has something?
ConfigLoader.loadProperties(socket.getInputStream(), true);
// or you have your own Properties source?
ConfigLoader.loadProperties(myOwnProperties, true);
Maybe we want to connect slightly different for some reason:
// override only the port and connection type, leave everything else to config files
Mailer mailer = MailerBuilder
.withSMTPServerPort(587)
.withTransportStrategy(TransportStrategy.SMTP_TLS)
.buildMailer();
Spring support
Everything can be configured through Spring properties, allowing for robust profile-based configuration.
To enable this, include the spring-module on your classpath.
By importing the Spring support bean from Simple Java Mail, whatever properties are provided through Spring are then transferred to Simple Java Mail using the ConfigLoader. It will add or overwrite whatever properties have been loaded before that (including the regular simplejavamail.properties).
Here's a sample configuration using Java style configuration.
@Component
@Import(SimpleJavaMailSpringSupport.class)
public class YourEmailService {
@Autowired // or roll your own, as long as SimpleJavaMailSpringSupport is processed first
private Mailer mailer;
}
Or obtaining the intermediate builder and customize:
@Configuration
@Import(SimpleJavaMailSpringSupport.class)
public class YourEmailService {
@Autowired
private MailerGenericBuilder mailerGenericBuilder;
@Bean
public Mailer customMailer() {
return mailerGenericBuilder
.resetThreadPoolSize()
.withThreadPoolKeepAliveTime(5000)
.withProxyBridgePort(7777)
.withExecutorService(new MyAwesomeCustomThreadPoolExecutor())
.buildMailer();
}
}
default and production):
#application.properties
simplejavamail.javaxmail.debug=true
simplejavamail.smtp.host=smtp.host
simplejavamail.smtp.port=25
simplejavamail.transportstrategy=SMTP
#application-production.properties
simplejavamail.javaxmail.debug=false
simplejavamail.smtp.host=smtp.production.host
simplejavamail.smtp.port=443
simplejavamail.transportstrategy=SMTPS
simplejavamail.smtp.username=<username>
simplejavamail.smtp.password=<password>
simplejavamail.proxy.username=<proxy_username>
simplejavamail.proxy.password=<proxy_password>
For per-cluster batch-module settings, Spring properties use the same dynamic namespace as regular property files:
#application.properties
simplejavamail.defaults.connectionpool.clusters.orders.clusterkey.uuid=00000000-0000-0000-0000-000000000301
simplejavamail.defaults.connectionpool.clusters.orders.coresize=0
simplejavamail.defaults.connectionpool.clusters.orders.maxsize=3
simplejavamail.defaults.connectionpool.clusters.orders.claimtimeout.millis=30000
simplejavamail.defaults.connectionpool.clusters.orders.expireafter.millis=600000
simplejavamail.defaults.connectionpool.clusters.orders.loadbalancing.strategy=RANDOM_ACCESS
# or key the config directly by UUID
simplejavamail.defaults.connectionpool.clusters.00000000-0000-0000-0000-000000000302.maxsize=8
simplejavamail.defaults.connectionpool.clusters.00000000-0000-0000-0000-000000000302.loadbalancing.strategy=ROUND_ROBIN
Batch and clustering support
Simple Java Mail provides four ways of sending more than one email, each meant for a different level of control and throughput:
- regular synchronous sending, one email at a time
- asynchronous sending using the mailer's configured
ExecutorService - simple sequential batch sending over one SMTP connection, without the batch-module
- pooled and clustered sending with the batch-module
How connections work without batch-module
Without the batch-module, Simple Java Mail supports asynchronous email sending by scheduling
send tasks on the mailer's configured ExecutorService. Each send still opens and closes its own Transport
connection, managed independently of a connection pool. By default this non-batch executor is a single-thread executor; provide a custom
ExecutorService if you want different concurrency. However, without the batch-module, every concurrent
send still needs its own SMTP connection, which is less efficient in high-volume scenarios. Additionally, you may find you will hit your maximum
concurrent connections for your server more easily without the batch module.
Mailer regularMailer = mailerBuilder.(..).buildMailer();
regularMailer.sendMail(email); // blocks
Mailer defaultAsyncMailer = mailerBuilder.(..).async().buildMailer();
defaultAsyncMailer.sendMail(email); // doesn't block
/* or be explicit about it: */
mailer.sendMail(email, /*async = */ true);
Defining your own thread pool using an ExecutorService (without batch-module the default is
Executors.newSingleThreadExecutor; with batch-module the default executor uses the batch-module pool settings):
currentMailerBuilder.withExecutorService(new MyAwesomeCustomThreadPoolExecutor())
When reconnecting for every message is the only problem you need to solve, use simple sequential batch sending. It sends all provided emails over one SMTP connection and does not require the batch-module. It is not pooled, concurrent, retried or clustered.
mailer.sendMailsInSimpleBatch(emails); // uses the mailer's async default
mailer.sendMailsInSimpleBatch(emails, false); // blocks until the batch is done
mailer.sendMailsInSimpleBatch(emails, true); // schedules the whole batch asynchronously
For sustained high-volume sending, prefer the batch-module connection pool.
When the caller owns the queue and needs custom work between successful sends, use
withOpenConnection(...). Simple Java Mail owns and closes the SMTP connection for the callback, while the
callback decides what to send next. This is useful for database-backed queues where a message should be marked as sent before the next one is read.
mailer.withOpenConnection(sender -> {
while (database.hasPendingMail()) {
Email next = database.nextPendingMail();
sender.sendMail(next);
database.markSent(next);
}
});
The scoped sender is sequential, not asynchronous, not pooled and not clustered. For throughput, retries, queueing or clustered sending, use the batch-module connection pool.
Use sender.sendMailAndGetReceipt(next) instead when the checkpoint needs the SMTP submission response
that was returned by the server.
Reusing connections with batch-module's connection pool
The batch-module significantly boosts performance by introducing connection pooling. Simply include the module in your classpath (add the Maven dependency) to enable up to four pooled connections. The default setup provides up to four pooled connections, which automatically close if inactive for five seconds following their last use. This mechanism is optimal for managing bursts of email traffic, keeping up to four connections active as long as needed.
The connection pool configuration is flexible, allowing adjustments to core connection pool size, maximum pool size, and connection expiry policy.
While the default setup without the batch-module allows for the creation of a custom thread pool to manage email sending, the batch-module introduces a connection pool. This addition enables threads to reuse Transport connections more efficiently, significantly improving performance even with fewer threads, as it eliminates the need to repeatedly open and close connections for each email.
Mailer pooledMailer = mailerBuilder
.(..)
.withConnectionPoolCoreSize(2) // keep 2 connections up at all times, automatically refreshed after expiry policy closes it (default 0)
.withConnectionPoolMaxSize(10) // scale up to max 10 connections until expiry policy kicks in and cleans up (default 4)
.withConnectionPoolClaimTimeoutMillis(TimeUnit.MINUTES.toMillis(1)) // wait max 1 minute for available connection (default forever)
.withConnectionPoolExpireAfterMillis(TimeUnit.MINUTES.toMillis(30)) // keep connections spinning for half an hour (default 5 seconds)
.buildMailer();
Or using properties:
simplejavamail.defaults.connectionpool.coresize=2
simplejavamail.defaults.connectionpool.maxsize=10
simplejavamail.defaults.connectionpool.claimtimeout.millis=60000
simplejavamail.defaults.connectionpool.expireafter.millis=1800000
Note that with the batch-module enabled, the JVM will not shut down by itself anymore, as the connection pool stays alive until
shutdown manually. To do this, just call mailer.shutdownConnectionPool() (repeat with each mailer you might have
in a cluster).
Advanced performance tuning: Clustering with multiple connection pools
To enable high-availability / fail-over or to handle truly enormous email batch loads, Simple Java Mail enables you to easily configure cluster(s) of SMTP servers.
For example, for a simple fail-over setup with three SMTP servers, You can define a cluster with low/default connection pool settings and have three Mailer instances use the same cluster key. Then sending an email with any of these Mailer instances will result in a send-action resolved using the cluster.
UUID ordersCluster = UUID.fromString("00000000-0000-0000-0000-000000000301");
Mailer clusteredMailer = mailerBuilder
.(..) // normal SMTP settings
.withClusterKey(ordersCluster)
.withConnectionPoolCoreSize(2)
.withConnectionPoolMaxSize(10)
.withConnectionPoolClaimTimeoutMillis(60000)
.withConnectionPoolExpireAfterMillis(1800000)
.withConnectionPoolLoadBalancingStrategy(LoadBalancingStrategy.ROUND_ROBIN)
.buildMailer();
or using properties:
simplejavamail.defaults.connectionpool.clusterkey.uuid=00000000-0000-0000-0000-000000000301
simplejavamail.defaults.connectionpool.loadbalancing.strategy=ROUND_ROBIN
# valid values: ROUND_ROBIN, RANDOM_ACCESS
Mailer behavior in a cluster setup:
Mailer mailer1InFailoverCluster = mailerBuilderServer1.(..).withClusterKey(myClusterKey).buildMailer();
Mailer mailer2InFailoverCluster = mailerBuilderServer2.(..).withClusterKey(myClusterKey).buildMailer();
Mailer mailer3InFailoverCluster = mailerBuilderServer3.(..).withClusterKey(myClusterKey).buildMailer();
// or default cluster using property:
// simplejavamail.defaults.connectionpool.clusterkey.uuid=38400000-8cf0-11bd-b23e-10b96e4ef00d
mailer1InFailoverCluster.sendMail(email); // 1 of 3 servers is selected
mailer2InFailoverCluster.sendMail(email); // 1 of 3 servers is selected
mailer3InFailoverCluster.sendMail(email); // 1 of 3 servers is selected
// now server 2 breaks down and becomes unreachable or produces errors
// server 2 is removed from the cluster, but all mailers still work:
mailer1InFailoverCluster.sendMail(email); // 1 of 2 servers is selected
mailer2InFailoverCluster.sendMail(email); // 1 of 2 servers is selected
mailer3InFailoverCluster.sendMail(email); // 1 of 2 servers is selected
Note 1: The send-actions don't automatically recover from errors and are not retried automatically. This is because Simple Java
Mail cannot determine how a send-action was botched and what the next course of action should be. You can monitor individual emails using
the CompletableFuture async return value obtained from
mailer.sendMail() and determine followup-actions for errored-out results.
Note 2: Connection pool defaults are scoped by cluster key. The first Mailer instance in a cluster sets that cluster's pool defaults; later Mailer instances in the same cluster cannot change them. Different cluster keys can use different pool defaults.
Note 3: Using Java API, you can define any number of clusters. Using
simplejavamail.defaults.connectionpool.clusterkey.uuid, you define one default cluster key.
Using simplejavamail.defaults.connectionpool.clusters.*, property files and Spring can also define
separate pool defaults for multiple cluster keys.
Property-defined cluster configs:
# Alias-based config. The alias is only a property-file name; clusterkey.uuid is the actual cluster key.
simplejavamail.defaults.connectionpool.clusters.orders.clusterkey.uuid=00000000-0000-0000-0000-000000000301
simplejavamail.defaults.connectionpool.clusters.orders.coresize=0
simplejavamail.defaults.connectionpool.clusters.orders.maxsize=3
simplejavamail.defaults.connectionpool.clusters.orders.claimtimeout.millis=30000
simplejavamail.defaults.connectionpool.clusters.orders.expireafter.millis=600000
simplejavamail.defaults.connectionpool.clusters.orders.loadbalancing.strategy=RANDOM_ACCESS
# Direct UUID-keyed config. If clusterkey.uuid is omitted, the alias itself must be a UUID.
simplejavamail.defaults.connectionpool.clusters.00000000-0000-0000-0000-000000000302.maxsize=8
simplejavamail.defaults.connectionpool.clusters.00000000-0000-0000-0000-000000000302.loadbalancing.strategy=ROUND_ROBIN
You set the limit
So really, there's no limit to the email performance you are looking for except maybe in the client which generates the emails. You can add as many
servers as you like to a cluster, use multiple clusters for different purposes and have as many pooled connections as you want dormant or spinned up
at all time!