Skip to content

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


Switch branches/tags

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time

APACHE v2 License Latest Release Javadocs Codacy


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 solutions to creating, converting and sending emails.


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.




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<Transport> 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();
        .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


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