Fundamentals of The Javamail Api
Fundamentals of The Javamail Api
The JavaMail API is designed to provide protocol-independent access for sending and
receiving messages by dividing the API into two parts:
• The first part of the API is the focus of this course. Basically, how to send and
receive messages independent of the provider/protocol.
• The second part speaks the protocol-specific languages, like SMTP, POP,
IMAP, and NNTP. With the JavaMail API, in order to communicate with a server,
you need a provider for a protocol. The creation of protocol-specific providers is
not covered in this course as Sun provides a sufficient set for free.
• SMTP
• POP
• IMAP
• MIME
You will also run across NNTP and some others. Understanding the basics of all the
protocols will help you understand how to use the JavaMail API. While the API is
designed to be protocol agnostic, you can't overcome the limitations of the underlying
protocols. If a capability isn't supported by a chosen protocol, the JavaMail API doesn't
magically add the capability on top of it. (As you'll soon see, this usually is a problem
when working with POP.)
SMTP
The Simple Mail Transfer Protocol (SMTP) is the mechanism for delivery of email. In
the context of the JavaMail API, your JavaMail-based program will communicate with
your company or Internet Service Provider's (ISP's) SMTP server. That SMTP server
will relay the message on to the SMTP server of the recipient(s) to eventually be
acquired by the user(s) through POP or IMAP. This does not require your SMTP server
to be an open relay, as authentication is supported, but it is your responsibility to ensure
the SMTP server is configured properly. There is nothing in the JavaMail API for tasks
like configuring a server to relay messages or to add and remove email accounts.
POP
POP stands for Post Office Protocol. Currently in version 3, also known as POP3, RFC
1939 defines this protocol. POP is the mechanism most people on the Internet use to get
their mail. It defines support for a single mailbox for each user. That is all it does, and
that is also the source of most confusion. Much of what people are familiar with when
using POP, like the ability to see how many new mail messages they have, are not
supported by POP at all. These capabilities are built into programs like Eudora or
Microsoft Outlook, which remember things like the last mail received and calculate
how many are new for you. So, when using the JavaMail API, if you want this type of
information, you have to calculate it yourself.
IMAP
IMAP is a more advanced protocol for receiving messages. Defined in RFC 2060,
IMAP stands for Internet Message Access Protocol, and is currently in version 4, also
known as IMAP4. When using IMAP, your mail server must support the protocol. You
can't just change your program to use IMAP instead of POP and expect everything in
IMAP to be supported. Assuming your mail server supports IMAP, your JavaMail-
based program can take advantage of users having multiple folders on the server and
these folders can be shared by multiple users.
Due to the more advanced capabilities, you might think IMAP would be used by
everyone. It isn't. It places a much heavier burden on the mail server, requiring the
server to receive the new messages, deliver them to users when requested, and maintain
them in multiple folders for each user. While this does centralize backups, as users'
long-term mail folders get larger and larger, everyone suffers when disk space is
exhausted. With POP, saved messages get offloaded from the mail server.
MIME
MIME stands for Multipurpose Internet Mail Extensions. It is not a mail transfer
protocol. Instead, it defines the content of what is transferred: the format of the
messages, attachments, and so on. There are many different documents that take effect
here: RFC 822, RFC 2045, RFC 2046, and RFC 2047. As a user of the JavaMail API,
you usually don't need to worry about these formats. However, these formats do exist
and are used by your programs.
Installing
To use the JavaMail API, download the JavaMail implementation, unbundle the
javamail-[version].zip file, and add the mail.jar file to your CLASSPATH. The
implementation comes with an SMTP, IMAP4, and POP3 provider besides the core
classes.
You should now have added mail.jar and activation.jar to your CLASSPATH.
If you don't want to change the CLASSPATH environment variable, copy the JAR files
to your lib/ext directory under the Java Runtime Environment (JRE) directory. For
instance, for the J2SE 1.3 release, the default directory would be
C:\jdk1.3\jre\lib\ext on a Windows platform.
Session
The Session class defines a basic mail session. It is through this session that
everything else works. The Session object takes advantage of a
java.util.Properties object to get information like mail server, username,
password, and other information that can be shared across your entire application.
The constructors for the class are private. You can get a single default session that can
be shared with the getDefaultInstance() method:
Properties props = new Properties();
// fill props with any information
Session session = Session.getDefaultInstance(props, null);
Or, you can create a unique session with getInstance():
Properties props = new Properties();
// fill props with any information
Session session = Session.getInstance(props, null);
In both cases here the null argument is an Authenticator object which is not being
used at this time. More on Authenticator shortly.
In most cases, it is sufficient to use the shared session, even if working with mail
sessions for multiple user mailboxes. You can add the username and password
combination in at a later step in the communication process, keeping everything
separate.
Message
Once you have your Session object, it is time to move on to creating the message to
send. This is done with a type of Message. Being an abstract class, you must work with
a subclass, in most cases javax.mail.internet.MimeMessage. A MimeMessage is a
email message that understands MIME types and headers, as defined in the different
RFCs. Message headers are restricted to US-ASCII characters only, though non-ASCII
characters can be encoded in certain header fields.
To create a Message, pass along the Session object to the MimeMessage constructor:
MimeMessage message = new MimeMessage(session);
Note: There are other constructors, like for creating messages from
RFC822-formatted input streams.
Once you have your message, you can set its parts, as Message implements the Part
interface (with MimeMessage implementing MimePart). The basic mechanism to set the
content is the setContent() method, with arguments for the content and the mime
type:
message.setContent("Hello", "text/plain");
If, however, you know you are working with a MimeMessage and your message is plain
text, you can use its setText() method which only requires the actual content,
defaulting to the MIME type of text/plain:
message.setText("Hello");
For plain text messages, the latter form is the preferred mechanism to set the content.
For sending other kinds of messages, like HTML messages, use the former. More on
HTML messages later though.
To create an address with just the email address, pass the email address to the
constructor:
Address address = new InternetAddress("president@whitehouse.gov");
If you want a name to appear next to the email address, you can pass that along to the
constructor, too:
Address address = new InternetAddress("president@whitehouse.gov",
"George Bush");
You will need to create address objects for the message's from field as well as the to
field. Unless your mail server prevents you, there is nothing stopping you from sending
a message that appears to be from anyone.
Once you've created the addresses, you connect them to a message in one of two ways.
For identifying the sender, you use the setFrom() and setReplyTo() methods.
message.setFrom(address)
If your message needs to show multiple from addresses, use the addFrom() method:
Address address[] = ...;
message.addFrom(address);
For identifying the message recipients, you use the addRecipient() method. This
method requires a Message.RecipientType besides the address.
message.addRecipient(type, address)
The three predefined types of address are:
• Message.RecipientType.TO
• Message.RecipientType.CC
• Message.RecipientType.BCC
So, if the message was to go to the vice president, sending a carbon copy to the first
lady, the following would be appropriate:
Address toAddress = new
InternetAddress("vice.president@whitehouse.gov");
Address ccAddress = new InternetAddress("first.lady@whitehouse.gov");
message.addRecipient(Message.RecipientType.TO, toAddress);
message.addRecipient(Message.RecipientType.CC, ccAddress);
The JavaMail API provides no mechanism to check for the validity of an email address.
While you can program in support to scan for valid characters (as defined by RFC 822)
or verify the MX (mail exchange) record yourself, these are all beyond the scope of the
JavaMail API.
Authenticator
Like the java.net classes, the JavaMail API can take advantage of an Authenticator
to access protected resources via a username and password. For the JavaMail API, that
resource is the mail server. The JavaMail Authenticator is found in the javax.mail
package and is different from the java.net class of the same name. The two don't
share the same Authenticator as the JavaMail API works with Java 1.1, which didn't
have the java.net variety.
To use the Authenticator, you subclass the abstract class and return a
PasswordAuthentication instance from the getPasswordAuthentication()
method. You must register the Authenticator with the session when created. Then,
your Authenticator will be notified when authentication is necessary. You could
popup a window or read the username and password from a configuration file (though
if not encrypted it is not secure), returning them to the caller as a
PasswordAuthentication object.
This latter way is best when you need to send multiple messages, as it will keep the
connection with the mail server active between messages. The basic send() mechanism
makes a separate connection to the server for each method call.
Note: To watch the mail commands go by to the mail server, set the
debug flag with session.setDebug(true).
Store and Folder
Getting messages starts similarly to sending messages, with a Session. However, after
getting the session, you connect to a Store, quite possibly with a username and
password or Authenticator. Like Transport, you tell the Store what protocol to use:
// Store store = session.getStore("imap");
Store store = session.getStore("pop3");
store.connect(host, username, password);
After connecting to the Store, you can then get a Folder, which must be opened
before you can read messages from it:
Folder folder = store.getFolder("INBOX");
folder.open(Folder.READ_ONLY);
Message message[] = folder.getMessages();
For POP3, the only folder available is the INBOX. If you are using IMAP, you can have
other folders available.
Once you have a Message to read, you can get its content with getContent() or write
its content to a stream with writeTo(). The getContent() method only gets the
message content, while writeTo() output includes headers.
System.out.println(((MimeMessage)message).getContent());
Once you're done reading mail, close the connection to the folder and store.
folder.close(aBoolean);
store.close();
The boolean passed to the close() method of folder states whether or not to update the
folder by removing deleted messages.
Moving On
Essentially, understanding how to use these seven classes is all you need for nearly
everything with the JavaMail API. Most of the other capabilities of the JavaMail API
build off these seven classes to do something a little different or in a particular way,
like if the content is an attachment. Certain tasks, like searching, are isolated, and are
discussed later.
// Get session
Session session = Session.getDefaultInstance(props, null);
// Define message
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO,
new InternetAddress(to));
message.setSubject("Hello JavaMail");
message.setText("Welcome to JavaMail");
// Send message
Transport.send(message);
You should place the code in a try-catch block, as setting up the message and sending it
can throw exceptions.
Exercise
....
Fetching Messages
For reading mail, you get a session, get and connect to an appropriate store for your
mailbox, open the appropriate folder, and get your message(s). Also, don't forget to
close the connection when done.
// Get session
Session session = Session.getDefaultInstance(props, null);
// Get folder
Folder folder = store.getFolder("INBOX");
folder.open(Folder.READ_ONLY);
// Get directory
Message message[] = folder.getMessages();
// Close connection
folder.close(false);
store.close();
What you do with each message is up to you. The above code block just displays who
the message is from and the subject. Technically speaking, the list of from addresses
could be empty and the getFrom()[0] call could throw an exception.
To display the whole message, you can prompt the user after seeing the from and
subject fields, and then call the message's writeTo() method if they want to see it.
// Get directory
Message message[] = folder.getMessages();
for (int i=0, n=message.length; i<n; i++) {
System.out.println(i + ": " + message[i].getFrom()[0]
+ "\t" + message[i].getSubject());
Exercise
• Flags.Flag.ANSWERED
• Flags.Flag.DELETED
• Flags.Flag.DRAFT
• Flags.Flag.FLAGGED
• Flags.Flag.RECENT
• Flags.Flag.SEEN
• Flags.Flag.USER
Just because a flag exists doesn't mean the flag is supported by all mail
servers/providers. For instance, besides deleting messages, the POP protocol supports
none of them. Checking for new mail is not a POP task but one built into mail clients.
To find out what flags are supported, ask the folder with getPermanentFlags().
To unset a flag, just pass false to the setFlag() method. To see if a flag is set, check
with isSet().
Authenticating Yourself
You previously learned that you can use an Authenticator to prompt for username
and password when needed, instead of passing them in as strings. Here you'll actually
see how to more fully use authentication.
Instead of connecting to the Store with the host, username, and password, you
configure the Properties to have the host, and tell the Session about your custom
Authenticator instance, as shown here:
// Setup properties
Properties props = System.getProperties();
props.put("mail.pop3.host", host);
// Setup authentication, get session
Authenticator auth = new PopupAuthenticator();
Session session = Session.getDefaultInstance(props, auth);
import javax.mail.*;
import javax.swing.*;
import java.util.*;
Replying to Messages
The Message class includes a reply() method to configure a new Message with the
proper recipient and subject, adding "Re: " if not already there. This does not add any
content to the message, only copying the from or reply-to header to the new recipient.
The method takes a boolean parameter indicating whether to reply to only the sender
(false) or reply to all (true).
MimeMessage reply = (MimeMessage)message.reply(false);
reply.setFrom(new InternetAddress("president@whitehouse.gov"));
reply.setText("Thanks");
Transport.send(reply);
To configure the reply-to address when sending a message, use the setReplyTo()
method.
Forwarding Messages
Forwarding messages is a little more involved. There is no single method to call, and
you build up the message to forward by working with the parts that make up a message.
A mail message can be made up of multiple parts. Each part is a BodyPart, or more
specifically, a MimeBodyPart when working with MIME messages. The different body
parts get combined into a container called Multipart or, again, more specifically a
MimeMultipart. To forward a message, you create one part for the text of your
message and a second part with the message to forward, and combine the two into a
multipart. Then you add the multipart to a properly addressed message and send it.
That's essentially it. To copy the content from one message to another, just copy over
its DataHandler, a class from the JavaBeans Activation Framework.
// Fill in header
forward.setSubject("Fwd: " + message.getSubject());
forward.setFrom(new InternetAddress(from));
forward.addRecipient(Message.RecipientType.TO,
new InternetAddress(to));
// Send message
Transport.send(forward);
Sending Attachments
Sending attachments is quite like forwarding messages. You build up the parts to make
the complete message. After the first part, your message text, you add other parts where
the DataHandler for each is your attachment, instead of the shared handler in the case
of a forwarded message. If you are reading the attachment from a file, your attachment
data source is a FileataSource. Reading from a URL, it is a URLDataSource. Once
you have your DataSource, just pass it on to the DataHandler constructor, before
finally attaching it to the BodyPart with setDataHandler(). Assuming you want to
retain the original filename for the attachment, the last thing to do is to set the filename
associated with the attachment with the setFileName() method of BodyPart. All this
is shown here:
// Define message
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO,
new InternetAddress(to));
message.setSubject("Hello JavaMail Attachment");
When including attachments with your messages, if your program is a servlet, your
users must upload the attachment besides tell you where to send the message.
Uploading each file can be handled with a form encoding type of multipart/form-
data.
<FORM ENCTYPE="multipart/form-data"
method=post action="/myservlet">
<INPUT TYPE="file" NAME="thefile">
<INPUT TYPE="submit" VALUE="Upload">
</FORM>
Note: Message size is limited by your SMTP server, not the JavaMail
API. If you run into problems, consider increasing the Java heap size
by setting the ms and mx parameters.
Getting Attachments
Getting attachments out of your messages is a little more involved then sending them,
as MIME has no simple notion of attachments. The content of your message is a
Multipart object when it has attachments. You then need to process each Part, to get
the main content and the attachment(s). Parts marked with a disposition of
Part.ATTACHMENT from part.getDisposition() are clearly attachments. However,
attachments can also come across with no disposition (and a non-text MIME type) or a
disposition of Part.INLINE. When the disposition is either Part.ATTACHMENT or
Part.INLINE, you can save off the content for that message part. Just get the original
filename with getFileName() and the input stream with getInputStream().
Multipart mp = (Multipart)message.getContent();
The saveFile() method just creates a File from the filename, reads the bytes from the
input stream, and writes them off to the file. In case the file already exists, a number is
added to the end of the filename until one is found that doesn't exist.
// from saveFile()
File file = new File(filename);
for (int i=0; file.exists(); i++) {
file = new File(filename+i);
}
The code above covers the simplest case where message parts are flagged
appropriately. To cover all cases, handle when the disposition is null and get the MIME
type of the part to handle accordingly.
if (disposition == null) {
// Check if plain
MimeBodyPart mbp = (MimeBodyPart)part;
if (mbp.isMimeType("text/plain")) {
// Handle plain
} else {
// Special non-attachment cases here of
// image/gif, text/html, ...
}
...
}
if (message.getContentType().equals("text/html")) {
String content = (String)message.getContent();
JFrame frame = new JFrame();
JEditorPane text = new JEditorPane("text/html", content);
text.setEditable(false);
JScrollPane pane = new JScrollPane(text);
frame.getContentPane().add(pane);
frame.setSize(300, 300);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.show();
}
The process of embedding an image is quite similar to attaching a file to a message, the
only difference is that you have to tell the MimeMultipart that the parts are related by
setting its subtype in the constructor (or with setSubType()) and set the Content-ID
header for the image to a random string which is used as the src for the image in the
img tag. The following demonstrates this completely.
Essentially, you build up a logical expression for matching messages, then search. For
instance the following term searches for messages with a (partial) subject string of ADV
or a from field of friend@public.com. You might consider periodically running this
query and automatically deleting any messages returned.
SearchTerm st =
new OrTerm(
new SubjectTerm("ADV:"),
new FromStringTerm("friend@public.com"));
Message[] msgs = folder.search(st);
Resources
You can do much more with the JavaMail API than what's described here. The lessons
and exercises found here can be supplemented by the following resources: