E-mail - Java 8 Recipes, 2th Edition (2014)

Java 8 Recipes, 2th Edition (2014)

CHAPTER 19. E-mail

E-mail notification is an integral part of today’s enterprise systems. Java enables e-mail notification by offering JavaMail API. Using this API, you can send e-mail communications in response to an event (say a completed form or a finalized script). You can also use the JavaMail API to check an IMAP or POP3 mailbox.

To follow along with the recipes in this chapter, make sure that you have set up your firewall to allow e-mail communication. Most of the time, firewalls allow outbound communications to e-mail servers without an issue, but if you are running your own local SMTP (e-mail) server, you may need to configure your firewall to allow the e-mail server to operate correctly.

Image Note The JavaMail API is included as part of the Java EE download. If you are using Java SE, you will need to download and install the JavaMail API.

19-1. Installing JavaMail

Problem

You want to install JavaMail for use by your application in sending e-mail notifications.

Solution

Download JavaMail from Oracle’s JavaMail website. Currently, the download you need is found at

http://www.oracle.com/technetwork/java/javamail/.

Once you download it, unzip it and add the JavaMail .jar files as dependencies from your project (both mail.jar and lib\*.jar).

How It Works

The JavaMail API is included in the Java EE SDK, but if you are working with the Java SE SDK, you will need to download and add the JavaMail API to your Java SE project. By downloading and adding the dependencies, you get access to the robust e-mail API that allows you to send and receive e-mails.

Image Note If you are not using Java SE 6 or newer, you will also need the JavaBeans Activation Framework (JAF) to use JavaMail. It is included in Java SE 6 and newer.

19-2. Sending an E-mail

Problem

You need your application to send an e-mail.

Solution

Using the Transport() methods, you can send an e-mail to specific recipients. In this solution, an e-mail message is constructed and sent through the smtp.somewhere.com server:

private void start() {
Properties properties = new Properties();
properties.put("mail.smtp.host", "smtp.somewhere.com");
properties.put("mail.smtp.auth", "true");

Session session = Session.getDefaultInstance(properties, new MessageAuthenticator("username","password"));

Message message = new MimeMessage(session);
try {
message.setFrom(new InternetAddress("someone@somewhere.com"));
message.setRecipient(Message.RecipientType.TO,
new InternetAddress("someone@somewhere.com"));
message.setSubject("Subject");
message.setContent("This is a test message", "text/plain");
Transport.send(message);
} catch (MessagingException e) {
e.printStackTrace();
}
}

class MessageAuthenticator extends Authenticator {
PasswordAuthentication authentication = null;

public MessageAuthenticator(String username, String password) {
authentication = new PasswordAuthentication(username,password);
}

@Override
protected PasswordAuthentication getPasswordAuthentication() {
return authentication;
}
}

How It Works

To utilize the JavaMail API, start by creating a Properties object that works as a standard Map object (in fact, it inherits from it), in which you put the different properties that the JavaMail service might need. The hostname is set using the mail.smtp.host property, and if the host requires authentication then you must set the mail.smtp.auth property to true. After the properties object is configured, fetch a javax.mail.Session that will hold the connection information for the e-mail message.

When you’re creating a session, you can specify the login information if the service requires authentication. This might be necessary when connecting to an SMTP service that is outside of your local area network. To specify the login information, you must create an Authenticatorobject, which will contain the getPasswordAuthentication() method. In this example, there is a new class identified as MessageAuthenticator, which extends the Authenticator class. By making the getPasswordAuthentication() method return aPasswordAuthentication object, you can specify the username/password used for the SMTP service.

The Message object represents an actual e-mail message and exposes e-mail properties such as From/To/Subject and Content. After setting these properties, you call the Transport.send() static method to send the e-mail message.

Image Tip If you don’t need authentication information, you can call the Session.getDefaultInstance(properties, null) method, passing a null for the Authenticator parameter.

19-3. Attaching Files to an E-Mail Message

Problem

You need to attach one or more files to an e-mail message.

Solution

Creating a message that contains different parts (called a multipart message) is what allows you to send attachments such as files and images. You can specify the body of the e-mail message and an attachment. Messages that contain different parts are referred to as Multipurpose Internet Mail Extensions (MIME) messages. They are represented in the javax.mail API by the MimeMessage class. The following code creates such a message:

Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.setRecipient(Message.RecipientType.TO, new InternetAddress(to));
message.setSubject("Subject");

// Create Mime "Message" part
MimeBodyPart messageBodyPart = new MimeBodyPart();
messageBodyPart.setContent("This is a test message", "text/plain");

// Create Mime "File" part
MimeBodyPart fileBodyPart = new MimeBodyPart();
fileBodyPart.attachFile("<path-to-attachment>/attach.txt");

MimeBodyPart fileBodyPart2 = new MimeBodyPart();
fileBodyPart2.attachFile("<path-to-attachment>/attach2.txt");

// Piece the body parts together
Multipart multipart = new MimeMultipart();
multipart.addBodyPart(messageBodyPart);
multipart.addBodyPart(fileBodyPart);
//add another body part to supply another attachment
multipart.addBodyPart(fileBodyPart2);

// Set the content of the message to be the MultiPart
message.setContent(multipart);
Transport.send(message);

How It Works

Within the JavaMail API you can create a Multipurpose Internet Mail Extensions (MIME) e-mail. This type of message allows it to contain different body parts. In the example, a plain text body part is generated (which contains the text that the e-mail displays), and then two attachment body parts containing the attachments you are trying to send are created. Depending on the type of attachments, the Java API will automatically choose an appropriate encoding for the attachment body part.

After each of the body parts are created, they are combined by creating a MultiPart object and adding each individual part (the plain text and the attachments) to it. Once the MultiPart object has been assembled to contain all the parts, it is assigned as the content of theMimeMessage and sent (just like in Recipe 19-2).

19-4. Sending an HTML E-Mail

Problem

You want to send an e-mail that contains HTML.

Solution

You specify the content type of the e-mail as text/html and send a string of HTML as the message body. In the following example, an e-mail is constructed using HTML content and then it is sent.

MimeMessage message = new MimeMessage(session);
try {
message.setFrom(new InternetAddress(from));
message.setRecipient(Message.RecipientType.TO, new InternetAddress(to));
message.setSubject("Subject Test");

// Create Mime Content
MimeBodyPart messageBodyPart = new MimeBodyPart();
String html = "<H1>Important Message</H1>" +
"<b>This is an important message...</b>"+
"<br/><br/>" +
"<i>Be sure to code your Java today!</i>" +
"<H2>It is the right thing to do!</H2>";
messageBodyPart.setContent(html, "text/html; charset=utf-8");

MimeBodyPart fileBodyPart = new MimeBodyPart();
fileBodyPart.attachFile("/path-to/attach.txt");

MimeBodyPart fileBodyPart2 = new MimeBodyPart();
fileBodyPart2.attachFile("/path-to/attach2.txt");

Multipart multipart = new MimeMultipart();
multipart.addBodyPart(messageBodyPart);
multipart.addBodyPart(fileBodyPart);
//add another body part to supply another attachment
multipart.addBodyPart(fileBodyPart2);
message.setContent(multipart);
Transport.send(message);
} catch (MessagingException | IOException e) {
e.printStackTrace();
}

How It Works

Sending an e-mail message that contains HTML content is basically the same as sending an e-mail with standard text—the only difference is the content type. When you’re setting the content on the message body part of the e-mail, you set the content to text/html to have the content treated as HTML. There are various ways to construct the HTML content, including using links, photos, or any other valid HTML markup. In this example, a few basic HTML tags have been embedded into a string.

Although the example code may not be very useful in real-life systems, it is easy to generate dynamic HTML content for inclusion within an e-mail. At its most basic form, dynamically generated HTML can be strings of text that are concatenated to formulate the HTML.

19-5. Sending E-Mail to a Group of Recipients

Problem

You want to send the same e-mail to multiple recipients.

Solution

Use the setRecipients() method from the JavaMail API to send e-mail to multiple recipients. The setRecipients() method allows you to specify more than one recipient at a time. For example:

// Main send body
message.setFrom(new InternetAddress("someone@somewhere.com"));
message.setRecipients(Message.RecipientType.TO, getRecipients(emails));
message.setSubject("Subject");
message.setContent("This is a test message", "text/plain");
Transport.send(message);

// ------------------

private Address[] getRecipients(List<String> emails) throws AddressException {
Address[] addresses = new Address[emails.size()];
for (int i =0;i < emails.size();i++) {
addresses[i] = new InternetAddress(emails.get(i));
}
return addresses;
}

How It Works

By using the setRecipients() method of the Message object, you can specify multiple recipients on the same message. The setRecipients() method accepts an array of Address objects. In this recipe, because you have a collection of strings, you create the array as the size of the collection and create InternetAddress objects to fill the array. Sending e-mails using multiple e-mail addresses (as opposed to individual e-mails) is much more efficient because only one message is sent from your client to the target mail servers. Each target mail server will then deliver to all recipients that it has mailboxes for. For example, if you’re sending to five different yahoo.com accounts, the yahoo.com mail server will need to receive only one copy of the message and it will deliver the message to all the yahoo.com recipients specified in the message.

Image Tip If you want to send bulk messages, you might want to specify the Recipient Type as BCC, so that the e-mail received doesn’t show everyone else that is getting the e-mail. To do so, specify Message.RecipientType.BCC in the setRecipients() method.

19-6. Checking E-Mail

Problem

You need to check if a new e-mail has arrived for a specified e-mail account.

Solution

You can use javax.mail.Store to connect, query, and retrieve messages from an Internet Message Access Protocol (IMAP) e-mail account. For example, the following code connects to an IMAP account, retrieves the last five messages from that IMAP account, and marks the messages as read.

Session session = Session.getDefaultInstance(properties, null);
Store store = session.getStore("imaps");
store.connect(host,username,password);
System.out.println(store);
Folder inbox = store.getFolder(folder);
inbox.open(Folder.READ_WRITE);
int messageCount = inbox.getMessageCount();
int startMessage = messageCount - 5;
int endMessage = messageCount;
if (messageCount < 5) startMessage =0;
Message messages[] = inbox.getMessages(startMessage,endMessage);
for (Message message : messages) {
boolean hasBeenRead = false;
for (Flags.Flag flag : message.getFlags().getSystemFlags()) {
if (flag == Flags.Flag.SEEN) {
hasBeenRead = true;
break;
}
}
message.setFlag(Flags.Flag.SEEN, true);
System.out.println(message.getSubject() + " "+ (hasBeenRead? "(read)" : "") + message.getContent());

}
inbox.close(true);

How It Works

A Store object allows you to access e-mail mailbox information. By creating a Store and then requesting the Inbox folder, you gain access to the messages in the main mailbox of your IMAP account. With the folder object, you can request to download the messages from the inbox. To do so, you use the getMessages (start, end) method. The inbox also provides a getMessageCount() method, which allows you to know how many e-mails are in the inbox. Keep in mind that the messages start at index 1.

Each message will have a set of flags that can then tell whether the message has been read (Flags.Flag.SEEN) or whether the message has been replied to (Flags.Flag.ANSWERED). By parsing the SEEN flag, you can then process messages that haven’t been read before.

To set a message as being read (or answered), call the message.setFlag() method. This method allows you to set (or reset) e-mail flags. If you’re setting message flags, you need to open the folder as READ_WRITE, which allows you to make changes to e-mail flags. You also need to call inbox.close(true) at the end of your code, which will tell the JavaMail API to flush the changes to the IMAP store.

Image Tip For IMAP over SSL, you should use session.getStore("imaps"). This creates a secure IMAP store.

19-7. Monitoring an E-Mail Account

Problem

You want to monitor when e-mails arrive to a certain account, and you want to process them depending upon their content.

Solution

Begin with the implementation from Recipe 19-6. Then add IMAP flag manipulation to create a robust e-mail monitor for your application. In the following example, the checkForMail() method is used to process mail that is being sent to a mailing list. In this scenario, users can subscribe or unsubscribe from the list by placing one of those words in the subject line. The following example checks the subject of new messages and deals with them appropriately. The example also uses message flags to delete processed messages so they need not be read twice. Messages that can’t be processed are marked as read but left in the server for troubleshooting by a human.

private void checkForMail() {
System.out.println("Checking for mail");
Properties properties = new Properties();
String username = "username";
String password = "password";
String folder = "Inbox";
String host = "imap.server.com";

try {
Session session = Session.getDefaultInstance(properties, null);
Store store = session.getStore("imaps");
store.connect(host,username,password);
Folder inbox = store.getFolder(folder);
inbox.open(Folder.READ_WRITE);
int messageCount = inbox.getMessageCount();
Message messages[] = inbox.getMessages(1,messageCount);
for (Message message : messages) {
boolean hasBeenRead = false;
if (Arrays.asList(message.getFlags().getSystemFlags()).contains(Flags.Flag.SEEN)) {
continue; // not interested in "seen" messages
}
if (processMessage(message)) {
System.out.println("Processed :"+message.getSubject());
message.setFlag(Flags.Flag.DELETED, true);
} else {
System.out.println("Couldn't Understand :"+message.getSubject());
// set it as seen, but keep it around
message.setFlag(Flags.Flag.SEEN, true);
}
}
inbox.close(true);
} catch (MessagingException e) {
e.printStackTrace();
}
}

private boolean processMessage(Message message) throws MessagingException {
boolean result = false;

String subject = message.getSubject().toLowerCase();
if (subject.startsWith("subscribe")) {
String emailAddress = extractAddress (message.getFrom());
if (emailAddress != null) {
subscribeToList(emailAddress);
result = true;
}

} else if (subject.startsWith("unsubscribe")) {
String emailAddress = extractAddress (message.getFrom());
if (emailAddress != null) {
unSubscribeToList(emailAddress);
result = true;
}
}

return result;
}

private String extractAddress(Address[] addressArray) {
if ((addressArray == null) || (addressArray.length < 1)) return null;
if (!(addressArray[0] instanceof InternetAddress)) return null;
InternetAddress internetAddress = (InternetAddress) addressArray[0];
return internetAddress.getAddress();
}

How It Works

After connecting to the IMAP server, the example requests all messages received. The code skips over the ones that are marked as SEEN. To do so, the recipe uses the Arrays.AsList to convert the array of system message flags into an ArrayList. Once the list is created, it is a matter of querying the list to see whether it contains the Flag.SEEN enum value. If that value is present, the example skips to the next item.

When an unread message is found, the message is processed by the processMessage() method. The method subscribes or unsubscribes the sender of the message depending on the start of the subject line. (This is akin to a mailing list, where sending a message with the subject of “subscribe” adds the sender to the mailing list.)

After determining which command to execute, the code proceeds to extract the sender’s e-mail from the message. To do so, the processMessage() calls the extractEmail() method. Each message contains an array of possible “From” addresses. These Address objects are generic because the Address object can represent Internet or newsgroup addresses. After checking that the Address object is indeed an InternetAddress, the code casts the Address object as an InternetAddress and calls the getAddress() method, which contains the actual e-mail address.

Once the e-mail address is extracted, the recipe calls subscribe or unsubscribe, depending on the subject line. If the message could be understood (meaning that the message was processed), the processMessage() method returns true (if it couldn’t understand the message, it returns false). In the checkForMail() method, when the processMessage() method returns true, the message is flagged for deletion (by calling message.setFlag(Flags.Flag.DELETED, true); otherwise, the message is just flagged as Seen. This allows the message to still be around if it wasn’t understood or deleted if it was processed. Finally, to commit the new flags on the messages (and expunge deleted messages), you need to call the inbox.close(true) method.

Summary

Email plays an important role in many systems that we use today. The Java language includes the JavaMail API, which enables developers to include robust email functionality within their Java applications. The recipes in this chapter covered the JavaMail API from installation through advanced usage. To learn more about JavaMail, and also about mail integration with Java applications deployed to enterprise applications servers, please refer to the online documentation: http://www.oracle.com/technetwork/java/javamail/index-141777.html.