smtp connection pool

Lightweight SMTP connection pool with clustering support, wait/release mechanism, connection lifecycle management, eager/lazy loading pool with load balancing and auto-expiry policy support

APACHE v2 License
Latest Release
Javadocs
Codacy

smtp-connection-pool

smtp-connection-pool is an ultra lightweight SMTP connection pool with clustering support, claim/wait/release mechanism,
connection lifecycle management, eager/lazy loading pool with auto-expiry policy support.

This library does not take care of creating or sending emails; it just pools (hot) reusable Transport instances using Session
instances provided by the user.

This SMTP connection pool is used by Simple Java Mail, which offers a complete solution to creating, converting and sending emails.

about

This library aims to improve performance for sending emails using Java Mail (now Jakarta Mail).

It represents three improvements over usual manual Session.getTransport().connect() approach:

  1. Support Transport (open) connection reuse over multiple threads
  2. Implement an SMTP connection pool so we have multiple reusable Transport connections (including lazy / eager initialization)
  3. Take performance to the next level and support clustered SMTP servers, so you can really scale up SMPT servers if your use-case requires it.

This library builds on top of clustered-object-pool.

Note: This library doesn’t configure mail Session instances itself: it only manages the connection you can make with them.
This library leaves it up to the user on how the connection behaves (to which server, proxy, SSL, TLS, session / connection timeouts etc).
Simple Java Mail offers a complete solution for sending emails (which uses SMTP Connection Pool).

possible approaches

There are a couple of scenario’s you can solve with clustered-object-pool:

  • Have 1 cluster with 1 pool of size 1. Where you have one SMTP connection, but can share/reuse it among threads.
  • Have 1 cluster with 1 pool of size n. Where multiple resources are shared/reused among threads.
  • Have 1 cluster with n pools of size 1. If you have one cluster with rotating pools to draw a shareable/reusable SMTP connection from. Useful when you want to spread load around different servers.
  • Have 1 cluster with n pools of size n. Same as above, except with multiple SMTP connections. For example multiple connections to multiple servers.
  • Have n clusters … Same as all the above except you have dedicated clusters for different purposes. For example a cluster for handling internal mails and a cluster for outgoing mails.

To keep API simple, this library provides both a simple Connection Pool class as well as a Clustered Connection Pool class. The only difference is in the generics for key-types they pass on to the
superclass.

Essential performance boost of using a connection pool

A very common scenario is to have a single connection being reused over many email-sending threads and usually this is enough. This can be achieved by having 1 cluster with 1 pool of
size 1. This already gives a real boost over not using a connection pool, since threads using the same transport but each establishing a new connection each takes half of the time of sending the
email itself.

Scale up performance with multple concurrent connections

The next solution satisfies most performance needs by far: having 1 cluster with 1 pool, but multiple connections. This takes the above approach to the next level by allowing multiple concurrent
connections to your mail server. If your server can handle it, you really scale up on performance. Try benchmarking your server with test emails with different pool sizes to see when performance
starts to degrade.

Take on the world with a cluster of mail servers

Finally, the next solution satisfies if you really need to send a lot of emails in a reasonable time. Define a cluster of several mail servers to which you can have one or multiple concurrent
connections. You rarely need this kind of performance, but sending news letters or world wide updates become can benefit greatly from this.

Setup

<dependency>
	<groupId>org.simplejavamail</groupId>
	<artifactId>smtp-connection-pool</artifactId>
	<version>2.3.0</version>
</dependency>

Usage

Creating a simple SMTP connection pool

// Simple on-demand (lazy loading) connection pool with default size of 4, 
// where the connections remain open until the pool is shut down.
SmtpConnectionPool pool = new SmtpConnectionPool(new SmtpClusterConfig());

PoolableObject<SessionTransport> poolableTransport = pool.claimResourceFromCluster(session);
// ... send the email
poolableTransport.release(); // make available in the connection pool again

The pool looks like a cluster and you still claim connections from a cluster, but for each server (backed by a Session) a new cluster is defined under the hood so effectively nothing is
clustered.

Creating a completely customized clustering SMTP connection pool

Let’s see what options we have:

SmtpClusterConfig smtpClusterConfig = new SmtpClusterConfig();
smtpClusterConfig.getConfigBuilder()
        .allocatorFactory(new MyCustomTransportAllocatorFactory())
        .defaultCorePoolSize(10) // eagerly start making up to 10 SMTP connections
        .defaultMaxPoolSize(10) // maximum pool size, after which claims become blocking
        // default is never-expire, this one closes connections randomly between 5 to 10 seconds after last use
        .defaultExpirationPolicy(new SpreadedTimeoutSinceLastAllocationExpirationPolicy<Transport>(5, 10, SECONDS)) 
        .cyclingStrategy(new RandomAccessCyclingStrategy()) // default is round-robin
        .claimTimeout(new Timeout(30, SECONDS)); // wait for available connection until max 30 seconds, default is indefinitely
        
SmtpConnectionPoolClustered pool = new SmtpConnectionPoolClustered(smtpClusterConfig);

New clusters and pools are created on-demand with the global defaults, based on cluster keys (for example a UUID) and pool keys (Session instances) passed to the claim invocations. You can however…

Configure different behavior for specific clusters and pools (servers)

// continuing the above code example...
UUID keyCluster1 = UUID.randomUUID();
UUID keyCluster2 = UUID.randomUUID();

Session sessionServerA = ...;
Session sessionServerB = ...;

// define different behavior only for server A in cluster 1
pool.registerResourcePool(new ResourceClusterAndPoolKey<>(keyCluster1, sessionServerA),
    new TimeoutSinceCreationExpirationPolicy<Transport>(30, SECONDS),
    4, // core pool size of eagerly opened and available connections
    10); // max pool size
A note on OAUTH2 tokens

Since acquiring SMTP Transport instances works a little differently when using OAuth2, you need to supply your OAuth2 token in the Session under a predefined property:

session.getProperties().setProperty(SmtpConnectionPool.OAUTH2_TOKEN_PROPERTY, yourOAuth2Token);