nithril / smtp-connection-pool

SMTP Connection Pool
Apache License 2.0
47 stars 19 forks source link

Errors when sending emails after updating to version 2.0.0 (Not connected) #29

Open lucasbasquerotto opened 6 months ago

lucasbasquerotto commented 6 months ago

We updated Wildfly that is used as our java server, and with this change, we updated Java 11 to Java 20 and occurrences of javax to jakarta, but we received errors in some libraries, this being one of them.

The version we used of this lib was 1.2.2, and the javax to jakarta was adressed in the most recent version.

After the version of this lib was updated, we received no compile errors, and we were able to send emails. But soon after, we receive errors in the following order:

The first error when trying to send emails is:

Caused by: org.eclipse.angus.mail.smtp.SMTPSendFailedException: 451 4.4.2 Timeout waiting for data from client.
     at org.eclipse.angus.mail//org.eclipse.angus.mail.smtp.SMTPTransport.issueSendCommand(SMTPTransport.java:2407)
     at org.eclipse.angus.mail//org.eclipse.angus.mail.smtp.SMTPTransport.mailFrom(SMTPTransport.java:1812)
     at org.eclipse.angus.mail//org.eclipse.angus.mail.smtp.SMTPTransport.sendMessage(SMTPTransport.java:1290)
     at deployment.app.war//org.nlab.smtp.transport.connection.DefaultClosableSmtpConnection.doSend(DefaultClosableSmtpConnection.java:127)
     at deployment.app.war//org.nlab.smtp.transport.connection.DefaultClosableSmtpConnection.sendMessage(DefaultClosableSmtpConnection.java:47)
     at deployment.app.war//my.app.util.EmailUtil.sendEmail(EmailUtil.java:111)
     ... 81 more

The next error (when we try to send again):

Caused by: jakarta.mail.MessagingException: Can't send command to SMTP host;
  nested exception is:
    java.net.SocketException: Connection or outbound has closed
    at org.eclipse.angus.mail//org.eclipse.angus.mail.smtp.SMTPTransport.sendCommand(SMTPTransport.java:2464)
    at org.eclipse.angus.mail//org.eclipse.angus.mail.smtp.SMTPTransport.sendCommand(SMTPTransport.java:2451)
    at org.eclipse.angus.mail//org.eclipse.angus.mail.smtp.SMTPTransport.issueSendCommand(SMTPTransport.java:2381)
    at org.eclipse.angus.mail//org.eclipse.angus.mail.smtp.SMTPTransport.mailFrom(SMTPTransport.java:1812)
    at org.eclipse.angus.mail//org.eclipse.angus.mail.smtp.SMTPTransport.sendMessage(SMTPTransport.java:1290)
    at deployment.app.war//org.nlab.smtp.transport.connection.DefaultClosableSmtpConnection.doSend(DefaultClosableSmtpConnection.java:127)
    at deployment.app.war//org.nlab.smtp.transport.connection.DefaultClosableSmtpConnection.sendMessage(DefaultClosableSmtpConnection.java:47)
    at deployment.app.war//my.app.util.EmailUtil.sendEmail(EmailUtil.java:111)
    ... 81 more
Caused by: java.net.SocketException: Connection or outbound has closed
    at java.base/sun.security.ssl.SSLSocketImpl$AppOutputStream.write(SSLSocketImpl.java:1297)
    at org.eclipse.angus.mail//org.eclipse.angus.mail.util.TraceOutputStream.write(TraceOutputStream.java:120)
    at java.base/java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:125)
    at java.base/java.io.BufferedOutputStream.implFlush(BufferedOutputStream.java:252)
    at java.base/java.io.BufferedOutputStream.flush(BufferedOutputStream.java:240)
    at org.eclipse.angus.mail//org.eclipse.angus.mail.smtp.SMTPTransport.sendCommand(SMTPTransport.java:2462)
    ... 88 more

If we try to send again, we receive the following error:

Caused by: java.lang.IllegalStateException: Not connected
    at org.eclipse.angus.mail//org.eclipse.angus.mail.smtp.SMTPTransport.checkConnected(SMTPTransport.java:2550)
    at org.eclipse.angus.mail//org.eclipse.angus.mail.smtp.SMTPTransport.sendMessage(SMTPTransport.java:1246)
    at deployment.app.war//org.nlab.smtp.transport.connection.DefaultClosableSmtpConnection.doSend(DefaultClosableSmtpConnection.java:127)
    at deployment.app.war//org.nlab.smtp.transport.connection.DefaultClosableSmtpConnection.sendMessage(DefaultClosableSmtpConnection.java:47)
    at deployment.app.war//my.app.util.EmailUtil.sendEmail(EmailUtil.java:111)
    ... 81 more

From then on, we receive the above error for every call.

If we restart Wildfly, the emails are sent again for a short time, then the errors above start happening and never ends (before restarting again).

The code we use is the same as the one we used with version 1.2.2 of this lib, except for the change in the imports of javax.mail to jakarta.mail.

If I change the code to not use this library and use the raw java mail (without pooling), it works even after the Wildfly/Java upgrade.

The code below (without pooling) works:

try (Transport transport = session.getTransport()) {
    transport.connect(smtphost, smtpUser, smtpPassword);
    transport.sendMessage(msg, msg.getAllRecipients());
}

The code below works only for a short time, but then starts giving the errors above (but worked previously):

try (ClosableSmtpConnection transport = smtpConnectionPool.borrowObject()) {
    Session session = transport.getSession();
    MimeMessage msg = new MimeMessage(session);
    msg.setFrom(new InternetAddress(from, smtpFromName));
    msg.setRecipient(Message.RecipientType.TO, new InternetAddress(recipient));
    msg.setSubject(subject, "UTF-8");
    msg.setContent(content, contentType);
    transport.sendMessage(msg, msg.getAllRecipients());
}

The connection pool is created as follows:

Properties props = System.getProperties();
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.port", SMTP_PORT);
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.ssl.enable", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.ssl.ciphersuites","SSL_RSA_WITH_RC4_128_MD5 SSL_RSA_WITH_RC4_128_SHA TLS_RSA_WITH_AES_128_CBC_SHA TLS_DHE_RSA_WITH_AES_128_CBC_SHA TLS_DHE_DSS_WITH_AES_128_CBC_SHA SSL_RSA_WITH_3DES_EDE_CBC_SHA SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA SSL_RSA_WITH_DES_CBC_SHA SSL_DHE_RSA_WITH_DES_CBC_SHA SSL_DHE_DSS_WITH_DES_CBC_SHA SSL_RSA_EXPORT_WITH_RC4_40_MD5 SSL_RSA_EXPORT_WITH_DES40_CBC_SHA SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA TLS_EMPTY_RENEGOTIATION_INFO_SCSV");
props.put("mail.smtp.connectiontimeout", "60000");
props.put("mail.smtp.timeout", "60000");
props.put("mail.smtp.writetimeout", "60000");

GenericObjectPoolConfig<Object> config = new GenericObjectPoolConfig<>();
config.setMaxTotal(20);

SmtpConnectionFactory factory = SmtpConnectionFactoryBuilder
    .newSmtpBuilder()
    .session(props)
    .protocol("smtp")
    .host(SMTP_HOST)
    .port(SMTP_PORT)
    .username(SMTP_USER)
    .password(SMTP_PASSWORD)
    .build();

SmtpConnectionPool smtpConnectionPool = new SmtpConnectionPool(factory, config);

When I tested without pooling (which works), we use the same props and get a session using it:

Session session = Session.getDefaultInstance(props);

The MimeMessage was also created the same way in both cases. My guess is that for some reason the connection expires but is not discarded when we use the connection pool (but this error didn't happen previously).

We use SES for sending emails, if that is relevant.

I saw a similar case from 2019 at https://github.com/nithril/smtp-connection-pool/issues/20, but no solution so far.

Is there a way to solve this issue?

nithril commented 6 months ago

Can you try to initialize the connection factory without the builder:

        SmtpConnectionFactory cf = new SmtpConnectionFactory(Session.getDefaultInstance(props), newSessiontStrategy(), 
                newConnectionStrategy(smtphost, smtpUser, smtpPassword), true );

The true at the end is to invalidate the connection in case of any exception.

Connection factory should then be closer to your example by using without the pool.

lucasbasquerotto commented 6 months ago

@nithril Thanks for the response. I don't know the how to call newSessionStrategy() and newConnectionStrategy(smtphost, smtpUser, smtpPassword), in regards with which imports are needed (in the README this method is just exposed briefly with outdated links).

I saw that the true argument is a boolean parameter invalidateConnectionOnException, so I think it's equivalent to define that in the builder. I created the connection factory as follows:

SmtpConnectionFactory factory = SmtpConnectionFactoryBuilder
        .newSmtpBuilder()
        .session(props)
        .protocol(SMTP_PROTOCOL)
        .host(SMTP_HOST)
        .port(SMTP_PORT)
        .username(SMTP_USER)
        .password(SMTP_PASSWORD)
        .invalidateConnectionOnException(true)
        .build();

In my tests, the error 451 4.4.2 Timeout waiting for data from client. still happens, but when I try to send after that it works for some time (I think about 1min), then the error happens again, then it works for some time, then the error happens again, and so on...

This already helps in the sense that the errors won't just happen indefinitely (after the error it starts working again), but it's still far from the ideal. Previously, this error never happened. I don't know what it did under the hood before, if it verified if the connection was active before sending, or retrying when the error happens, but having this error happen about every minute is still very bad, but at least that already helps to mitigate the problem, and I think it's one step closer to a real solution.