Wednesday 9 June 2010

JavaMail, Commons Mail or Spring Mail - Which does the job?

I thought since the previous culprit focuses on e-mail, it would be essential if I hit the nail on the head with choosing the right API for your e-mail function. For a starter, building e-mail function can be a little fustrating task.

If you are like most developers, you delve in JavaMail API thinking that is it, you have found a boiler plate code which does the job. You then go ahead to customize it to suite your enviroment, finally deployed it to realise your pain has just began.

Take for for instance below JavaMail API generic code:

Email Interface
  public interface EmailEngine
  {
    public void MailTransport(String from, String to, String cc, String subject,
     String message, String doc, String bcc) throws Exception;
  }

Actual Implementation
 public class EmailEngineImpl implements EmailEngine

    public final void MailTransport(String from, String to, String cc, String subject,
     String message, String doc, String bcc)
    {
      Properties props = new Properties();
      props.put(PropsFactory.PropsLookup("mail.host.props"), 
               PropsFactory.PropsLookup("mail.host"));
      Session session = Session.getInstance(props);
      try
      {
        Message msg = new MimeMessage(session);
        MimeMultipart mpRoot = new MimeMultipart();
        MimeMultipart mpAl = new MimeMultipart("alternative");
        MimeBodyPart content = new MimeBodyPart();

        msg.setSubject(subject);
        msg.setSentDate(Today.getDate());
        msg.setFrom(new InternetAddress(from));
        msg.setRecipients(Message.RecipientType.TO, getAddress(to));

        if (cc != null)
        {
          msg.setRecipients(Message.RecipientType.CC, getAddress(cc));
        }
        if (!(bcc == null))
        {
          msg.setRecipients(Message.RecipientType.BCC, getAddress(bcc));
        }
        
        if (doc != null)
        {
          MimeBodyPart p1 = new MimeBodyPart();
          Resource file = new ClassPathResource(doc);
          FileDataSource fds = new FileDataSource(file.getFile())
          {
            public String getContentType()
            {
              return "application/octet-stream";
            }
          };

          p1.setDataHandler(new DataHandler(fds));
          p1.setFileName(fds.getName());
          mpRoot.addBodyPart(p1);
        }
        MimeBodyPart p2 = new MimeBodyPart();
        p2.setText(message);
        mpAl.addBodyPart(p2);

        content.setContent(mpAl);
        mpRoot.addBodyPart(content);

        msg.setContent(mpRoot);
        msg.saveChanges();

        Transport.send(msg);
      }
      catch (Exception ex)
      {
        ex.printStackTrace();
      }
    }
  }
Everything looks fine except you deploy it to realise, Some Email clients typically outlook will work fine but little issues with others. And so you dig around to uncover you'll have to follow the MimeMultiPart structure, WHAP!

You have little time to waste therefore decided to try native Spring Email API. Bravo! Take time to read the API document, did you see the line which says:


Warning regarding multipart mails: Simple MIME messages that just contain HTML text but no inline elements or attachments will work on more or less any email client that is capable of HTML rendering. However, inline elements and attachments are still a major compatibility issue between email clients: It's virtually impossible to get inline elements and attachments working across Microsoft Outlook, Lotus Notes and Mac Mail. Consider choosing a specific multipart mode for your needs: The javadoc on the MULTIPART_MODE constants contains more detailed information.

You avoid the warning and implement Spring Generic Email API as shown below, perhaps tweaking static MULTIPART_MODE to see if it does support various multi email clients. WGL!
NB. Inject org.springframework.mail.javamail.JavaMailSender

  public final void MailTransport(String from, String to, String cc, 
   String subject, String content, String doc, String bcc)
  {
    try
    {
      MimeMessage message = mailSender.createMimeMessage();
      // use the true flag to indicate you need a multipart message
      MimeMessageHelper helper = new MimeMessageHelper(message, true);
      helper.setFrom(from);
      helper.setTo(getAddress(to));
      helper.setSubject(subject.toString());

      if (!(cc == null))
      {
        helper.setCc(getAddress(cc));
      }
      if (!(bcc == null))
      {
        helper.setBcc(getAddress(bcc));
      }

      if (!(doc == null))
      {
        Resource file = new ClassPathResource(doc);
        helper.addAttachment(file.getFilename(), file);
      }

      helper.setText(content);
      mailSender.send(message);
    }
    catch (Exception ex)
    {
      ex.printStackTrace();
    }
  }

If you do have luck with either Generic JavaMail or Spring implementation, I suppose your first reaction will be, why bother trying Apache Commons Email! You want MHO, go ahead and try it. It saves time and does the job. Well each of these API has it flaws, but TMHO Apache Commons Email ticks most boxes. Get the dependency or the jar file onto your classpath and try this;


  public class EmailEngineImpl implements EmailEngine
  {
    public final void MailTransport(String from, String to, String cc, String subject, String message, String doc, String bcc)
    {
      // Create Multi-Part document
      MultiPartEmail email = new MultiPartEmail();
      try
      {
        if (!(doc == null))
        {
          // Create the attachment
          EmailAttachment attachment = new EmailAttachment();
          Resource file = new ClassPathResource(doc);
          
          attachment.setURL(file.getURL());
          attachment.setDisposition(EmailAttachment.ATTACHMENT);
          attachment.setName(file.getFilename());

          // add the attachment
          email.attach(attachment);
        }

        // Set Mail server location
        email.setHostName(PropsFactory.PropsLookup("mail.host"));
        email.setTo(getAddress(to));
        email.setFrom(from);

        if (!(cc == null))
        {
          email.setCc(getAddress(cc));
        }

        if (!(bcc == null))
        {
          email.setBcc(getAddress(bcc));
        }
        email.setSubject(subject);
        email.setMsg(message);

        // Send message
        email.send();
      }
      catch (Exception ex)
      {
        ex.printStackTrace();
      }
    }
  }
I did try these and realised, most little issues like multi client and attachment was dealt with. Like I said this is my opinion let me know what you think. And oh if you wondering WTF is the method getAddress(..), see below:

Implementation for Apache Commons
  private Collection getAddress(String arg)
  {
    Collection address = new ArrayList();
    try
    {
      ArrayList ccArray = new ArrayList();
      StringTokenizer st = new StringTokenizer(arg, ",");
      while (st.hasMoreTokens())
      {
        ccArray.add(new InternetAddress(st.nextToken()));
      }

      for (int i = 0; i < ccArray.size(); i++)
      {
        address.add(ccArray.get(i));
      }

    }
    catch (Exception ex)
    {
      ex.printStackTrace();
    }
    return address;
  }
Implementation for JavaMail or Spring
 private InternetAddress[] getAddress(String arg)
  {
    InternetAddress[] address = null;
    try
    {
      ArrayList ccArray = new ArrayList();
      StringTokenizer st = new StringTokenizer(arg, ",");

      while (st.hasMoreTokens())
      {
        ccArray.add(new InternetAddress(st.nextToken()));
      }

      int size = ccArray.size();
      address = new InternetAddress[size];
      for (int i = 0; i < size; i++)
      {
        address[i] = new InternetAddress(ccArray.get(i).toString());
      }

    }
    catch (Exception ex)
    {
      ex.printStackTrace();
    }
    return address;
  }

Drop me a comment to let me know what you think.

1 comment:

  1. Is it a way to send bulk of email(approx 100K in 1 hour) using javamail and smtp port?IS it possible with multihreading? If yes then how can I achieve it because transport.connect method is thread safe.

    ReplyDelete