Narendra
Narendra

Reputation: 5773

How to Send bulk mails using javax.mail API efficiently? & Can we use reuse authenticated sessions to improve speed?

I am able to send a mail using javax.mail API. But the problem here is on an average for each mail it taking around 4.3 seconds to send to destination.

If I am sending a 20 mails sequentially, it takes around 86.599 seconds. For my requirement this approach will not work. I am looking for an approach which can send large number of mails in less time.

When I looked at the debug log, the API is trying to authenticate to SMTP server for each and every message it sending. But I am creating a session only one time and using the same session for all the mails I am sending. Now my question is Isn't it a overhead process every time authenticating itself to the smtp server. Isn't there a better approach ?

Below is the log trace you may find helpful.

250-AUTH LOGIN PLAIN XOAUTH XOAUTH2
250 ENHANCEDSTATUSCODES
DEBUG SMTP: Found extension "SIZE", arg "35882577"
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: Found extension "AUTH", arg "LOGIN PLAIN XOAUTH XOAUTH2"
DEBUG SMTP: Found extension "ENHANCEDSTATUSCODES", arg ""
DEBUG SMTP: Attempt to authenticate
DEBUG SMTP: check mechanisms: LOGIN PLAIN DIGEST-MD5 NTLM 
DEBUG SMTP: AUTH LOGIN command trace suppressed
DEBUG SMTP: AUTH LOGIN succeeded

Please let me know your thoughts on this and any help on this is really appreciated.

-Narendra

Upvotes: 20

Views: 30644

Answers (6)

Rajib Sharma
Rajib Sharma

Reputation: 60

I developed a way to send 1000 emails in less than 1 min. This email has only text. Heavy HTML takes approx 2 mins.

@Async("bulk-email")
void sendBulkEmail(List<EmailMessage> emailMessages){
     // EmailMessage is a custom object with properties like to, cc, subject.
     Collection<List<EmailMessage>> partitionedList = Lists.partition(emailMessages, 
         50); // Google Guava library.
     
     try{
        ForkJoinPool customThreadPool = new ForkJoinPool(10);
        customThreadPool.submit(()-> 
        partitionedList.parallelStream.forEach(this::sendEmails)).get();
     }catch(Exception e){
         e.printStackTrace();
     }
}

This will create 10 threads with each thread will send 50 emails with one Session and one connection.

private void sendEmails(List<EmailMessage> messages){
   
   Session session = createSession();
   try (Transport t = session.getTransport()) {
       t.addTransportListener(transportListener);
       t.connect();
       for(Message m : messages) {
          MimeMessage mime = createMimeMessage(session, m);
          mime.saveChanges();
          t.sendMessage(mime, mime.getAllRecipients());
       }
    }catch(Exception e){
      e.printStackTrace();
    }    
}

Upvotes: 0

Kishan Solanki
Kishan Solanki

Reputation: 14618

The above answers are either not relevant or too complex to implement. So here's I am posting the easiest solution to send bulk emails in Spring Boot

Just use

mimeMessageHelper.setBcc(emailList);

Where,

mimeMessageHelper is MimeMessageHelper, emailList is String[] emailList

NOTE:

Make sure that you are not using

mimeMessageHelper.setTo(emailList)

Otherwise, it will show all receivers' email address in the receiver's received email.

For more reference, This and This can help you to learn how to send emails in Spring Boot

Upvotes: 1

Ian Roberts
Ian Roberts

Reputation: 122364

How are you sending the messages? The JavaMail FAQ suggests that the static Transport.send method will open a fresh connection for each message, as it is a convenience method that creates a suitable Transport instance, connects it, calls sendMessage and then closes the connection again. If you get your own Transport instance from the Session you can connect once, then call sendMessage repeatedly to send several messages on the one connection, and finally close it. Something along the lines of (untested):

Transport t = session.getTransport();
t.connect();
try {
  for(Message m : messages) {
    m.saveChanges();
    t.sendMessage(m, m.getAllRecipients());
  }
} finally {
  t.close();
}

Updated to use try with resources block:

try (Transport t = session.getTransport()) {
    t.connect();
    for(Message m : messages) {
        m.saveChanges();
        t.sendMessage(m, m.getAllRecipients());
    }
}

Upvotes: 27

bvyas
bvyas

Reputation: 451

You can use Thread pooling as it gives very good performance.I have implemented and sharing you the below code snippet.

try  {
    ExecutorService executor = Executors.newFixedThreadPool("no. of threads"); // no. of threads is depend on your cpu/memory usage it's better to test with diff. no. of threads.
    Runnable worker = new MyRunnable(message); // message is the javax.mail.Message
    executor.execute(worker);
    executor.shutdown();
    executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
}

Upvotes: -1

Nicolas Labrot
Nicolas Labrot

Reputation: 4106

I got the same requirement at work. I must send bulk emails and standalone email. I do not find simple and satisfactory answer: bulk emails can be sent using a single connection but standalone email cannot until I create an asynchronous buffering to send emails in batch.

Last but not least, using a lot of Transport connection in a short time can lead to a no more socket handles are available because all ports are stuck in the TIME_WAIT state.

I finally conclude the best will be an SMTP connection pool and because no library exists (at least free) I create mine using Apache Common Pool and Java Mail:

//Declare the factory and the connection pool, usually at the application startup
SmtpConnectionPool smtpConnectionPool = new SmtpConnectionPool(SmtpConnectionFactoryBuilder.newSmtpBuilder().build());

//borrow an object in a try-with-resource statement or call `close` by yourself
try (ClosableSmtpConnection transport = smtpConnectionPool.borrowObject()) {
    MimeMessage mimeMessage = new MimeMessage(session);
    MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false);
    mimeMessageHelper.addTo("[email protected]");
    mimeMessageHelper.setFrom("[email protected]");
    mimeMessageHelper.setSubject("Hi!");
    mimeMessageHelper.setText("Hello World!", false);
    transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients());
}

//Close the pool, usually when the application shutdown
smtpConnectionPool.close();

Upvotes: 10

FelixD
FelixD

Reputation: 639

No idea if the standard Java mail API allows what you are trying to accomplish (session reuse), but you may consider using multi-threading:

I would use a ThreadPool and submit mail send jobs to it. Then you do any error handling / resending within the job class code, which is executed by the ThreadPool asynchronously, and your main thread can resume to do other things. Submitting a job will only take milliseconds. It been a while since I implemented something with thread pools in Java, but I remember it was fairly easy and straightforward. If you Google "Java ThreadPool" you find plenty of material.

Upvotes: 0

Related Questions