Working with James, Part 1: An introduction to Apache's James enterprise e-mail server

Learn the basics about this open source project

This article is the first in a two-part series on the Java Apache Mail Enterprise Server, also known as James. It lays a foundation for understanding James and for developing server-side e-mail applications. The article provides a high-level overview, briefly touches on the Apache group's design objectives, and describes how to install and configure a workable development environment. You can also take a brief tour of the features supported by James. You'll find descriptions for both the matcher and the mailet implementations that come with James, and a comparison of the existing functionality with that found in traditional e-mail servers.

Share:

Claude Duguay (claude.duguay@verizon.net), Senior J2EE architect, Capital Stream Inc.

Claude DuguayClaude Duguay has developed software for more than 20 years. He is passionate about the Java platform, reads about it incessantly, and writes about it as often as possible. You can reach him at claude.duguay@verizon.net.



10 June 2003

Also available in Japanese

The Java Apache Mail Enterprise Server -- generally referred to as James -- is a portable, secure, and 100% Pure Java enterprise mail server built by the Apache group. But it has the potential to be much more than that, thanks to its pluggable protocol architecture and a mailet infrastructure that does for e-mail what servlets do for Web servers. E-mail servers have been around since the early days of DARPA funding for what would eventually become the Internet, but James offers new possibilities for what's often been dubbed the Internet's first killer application.

This is the first of two articles that explore James. It provides an overview of James and a high-level orientation to let us explore its possibilities. In the second article, we'll implement a mailet application that manages unavailability messages. As you'll see, it is surprisingly easy to write applications for James. E-mail is used around the world by millions of people every day, so the possibilities go well beyond the introductory treatment that this series provides. It's my hope, however, that this foundation will serve you well and help you start imagining the possibilities.

How e-mail works

E-mail is simple, in principle. You construct a message with one or more recipient addresses using a mail user agent (MUA). MUAs come in many forms and include text-based, Web-based, and GUI applications; Microsoft Outlook and Netscape Messenger fall into the last category. Each e-mail client is configured to send mail to a mail transfer agent (MTA) and can be used to poll an MTA to fetch e-mail messages sent to the user's address. To do this, you need an e-mail account on a mail server (technically the MTA) and you can, using standard Internet protocols, either work with the e-mail offline (using POP3) or leave the e-mail on the server (using IMAP). The protocol used to send mail both from the client to the MTA and between MTAs is SMTP (Simple Mail Transfer Protocol).

Building a James application

Part 2 of this series builds on your knowledge of the basic James infrastructure to implement a practical application.

What really happens between MTAs is only slightly more interesting. E-mail servers rely heavily on DNS and e-mail-specific records called mail transfer (or MX) records. MX records are slightly different from the DNS records used to resolve URLs, containing some additional priority information used to route mail more effectively. I won't delve into those details here, but it's important to understand that DNS is key to routing e-mail successfully and efficiently. James is an MTA, while the JavaMail API provides a framework for an MUA. In this article, we'll use JavaMail to set up a test for our James installation. In the second article in this series, we'll use the James Mailet API to show how you can develop your own James applications.


James design objectives

James was designed to accommodate certain objectives. For example, it is written entirely in the Java language to maximize portability. It was written to be secure and provides a number of features that both protect the server environment itself and provide secure services. James functions as a multithreaded application that takes advantage of many of the benefits available in the Avalon framework. (Avalon is an Apache Jakarta project that features the Phoenix high-performance server infrastructure. Understanding Avalon is useful but not necessary to developing James applications. See the Resources section for more on Avalon.)

James provides a comprehensive set of services, including many that are usually available only in high-end or well-established e-mail servers. These services are primarily implemented using the Matcher and Mailet APIs, which work together to provide e-mail detection and processing capabilities. James supports the standard e-mail protocols (SMTP, POP3, IMAP), along with a few others, using a loosely coupled plug-in design that keeps the messaging framework abstracted from the protocols. This is a powerful idea that may enable James to act as more of a general messaging server in the future or to support alternative messaging protocols such as instant messaging.

The final and most interesting objective delivered by the James design group is the notion of mailets, which provide a component life cycle and container solution for developing e-mail applications. To be sure, it's always been possible to use other MTAs, such as Sendmail, to do this, given that any program can be called and data piped through executables to do the job, but James provides a common, simple API for accomplishing these goals and makes the work easy, thanks to the objects available for manipulation. We'll take a closer look at both the Matcher and Mailet APIs in this article.


Installing and configuring James

James is available from its home page at the Apache foundation Web site (see Resources for a link). You should download the latest production release to work with; at the time of this writing, that version is 2.1.2. You can find the download area by selecting Downloads > Binaries in the left navigation area of the James home page. From there, scroll down to the Release Builds section and select one of the James 2.1 links. Select either james-2.1.2.tar.gz or james-2.1.2.zip, depending on your preference.

We'll also be using the JavaMail API to test our application, so you'll need to download that as well (see Resources). The version currently available is 1.3 and the file is called javamail-1_3.zip. While you're on the main JavaMail page, you'll notice a link to the JavaBeans Activation Framework (JAF), which the JavaMail API requires. (See Resources for a direct link to the JAF.) The current JAF release is 1.0.2 and the file is called jaf-1_0_2.zip. Once you have all these files, you can set up your system to work with James.

We'll set up a directory structure with all the elements we need for development. In production, the setup has to be quite different, arranged based on considerations of both security and functionality. For our purposes, for example, we can work on localhost, but that's not a viable option when working with a real e-mail server deployment. There's plenty of documentation on configuring James as a primary MTA or with Sendmail, and plenty of help on the mailing lists if you need to deploy the server commercially.

With everything unzipped in a James directory, our hierarchy will look like Listing 1. I've removed a few subdirectories under javadoc, src, and the JavaMail webapp demo to keep things more compact and easier to envision.

Listing 1. James, JavaMail, and JAF directories
James
+---jaf-1.0.2
|   +---demo
|   \---docs
|       \---javadocs
+---james-2.1.2
|   +---apps
|   +---bin
|   |   \---lib
|   +---conf
|   +---docs
|   |   +---images
|   |   \---stylesheets
|   +---ext
|   +---lib
|   +---logs
\---javamail-1.3
    +---demo
    |   +---client
    |   +---servlet
    |   \---webapp
    +---docs
    |   \---javadocs
    \---lib

I am assuming that you have version 1.4 of the Java platform set up, independent of the James files. The James configuration notes suggest that some problems have been observed using Java 1.3.0, so you should work with 1.3.1 or higher. In principle, James should work well on any platform that supports a suitable Java 1.4 VM.

Our first step is to start James, because the configuration files are not unpacked until the server has been run once. You'll find a run script (use either run.bat or run.sh, depending on your operating system) in the james-2.1.2/bin directory. When you run that script, the output should look something like Listing 2 (this sample output is from a Windows system):

Listing 2. Console output from running James
Using PHOENIX_HOME:   D:\James\james-2.1.2
Using PHOENIX_TMPDIR: D:\James\james-2.1.2\temp
Using JAVA_HOME:      c:\programming\java14

Phoenix 4.0.1

James 2.1.2
Remote Manager Service started plain:4555
POP3 Service started plain:110
SMTP Service started plain:25
NNTP Service started plain:119
Fetch POP Disabled

You can use Ctrl+C to exit the program, and you'll get a message from the Phoenix container that it's exiting when you do so. Strictly speaking, the proper way to have James exit is to use the remote management interface. I've used Ctrl+C in my own development with no negative repercussions. In a deployment environment, however, you should always use the shutdown command.

After shutting down James for the first time, you'll find a file in the james-2.1-2/apps/james/SAR-INF folder called config.xml, which you should take a look at. The first thing you would normally change is the administrator account, which is set to root, with the password root, by default. We'll leave it alone for development but it would be unwise to leave it configured this way in a production system for obvious reasons. The next thing to change is usually the DNS server address, which is necessary if James is to operate as a complete e-mail server. We'll leave this alone as well, given that all our tests will be run from localhost, but again this is an important setting that you should be aware of. The rest of the default settings are fine, given our development objectives, though it's important to understand the configuration file. You can find more information in the documentation provided in the james-2.1.2/docs directory.

Let's add a few users before moving on. To do that, telnet to localhost on port 4555 with the command telnet localhost 4555. You can log in with the root user name and password. After the login, we'll add a few users. The adduser command expects a username and password combination. We'll add users named red, green, and blue for this project, each with the same password as the user name. (I'm sure you know that this is a bad idea when creating real users, but it'll make it easy to configure our test cases.) After adding users, you can use the listusers command to verify the entries and then exit the remote manager by typing quit. The whole session should look like Listing 3. I've highlighted text that you would enter yourself.

Listing 3. Adding users with remote management
JAMES Remote Administration Tool 2.1.2
Please enter your login and password
Login id:
root
Password:
root
Welcome root. HELP for a list of commands
adduser red red
User red added
adduser green green
User green added
adduser blue blue
User blue added
listusers
Existing accounts 3
user: blue
user: green
user: red
quit
Bye

Now we're all set to go with a running James server. As you can see, deploying James and using the remote manager to set things up is relatively straightforward. Obviously, you would need to change several configuration parameters if you wanted the mail server to be secure, but that's not a very complicated process. The real key to using mail servers has more to do with setting up DNS correctly in multiuser and multiserver environments. That's beyond the scope of this article, but isn't that complicated a process either.


Testing James with JavaMail

To make sure our setup is functional, we'll write a quick pair of classes that will send messages and list inbox content, simulating the base functionality of a typical e-mail client. We'll use two classes because the MailClient class, shown in Listing 4, can be reused for testing more complex behavior, which we'll do when we develop our James application in the second article in this series.

Listing 4. MailClient: Simulating the basic functionality of an e-mail client
import java.io.*;
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;

public class MailClient
  extends Authenticator
{
  public static final int SHOW_MESSAGES = 1;
  public static final int CLEAR_MESSAGES = 2;
  public static final int SHOW_AND_CLEAR =
    SHOW_MESSAGES + CLEAR_MESSAGES;
  
  protected String from;
  protected Session session;
  protected PasswordAuthentication authentication;
  
  public MailClient(String user, String host)
  {
    this(user, host, false);
  }
  
  public MailClient(String user, String host, boolean debug)
  {
    from = user + '@' + host;
    authentication = new PasswordAuthentication(user, user);
    Properties props = new Properties();
    props.put("mail.user", user);
    props.put("mail.host", host);
    props.put("mail.debug", debug ? "true" : "false");
    props.put("mail.store.protocol", "pop3");
    props.put("mail.transport.protocol", "smtp");
    session = Session.getInstance(props, this);
  }
  
  public PasswordAuthentication getPasswordAuthentication()
  {
    return authentication;
  }
  
  public void sendMessage(
    String to, String subject, String content)
      throws MessagingException
  {
    System.out.println("SENDING message from " + from + " to " + to);
    System.out.println();
    MimeMessage msg = new MimeMessage(session);
    msg.addRecipients(Message.RecipientType.TO, to);
    msg.setSubject(subject);
    msg.setText(content);
    Transport.send(msg);
  }
  
  public void checkInbox(int mode)
    throws MessagingException, IOException
  {
    if (mode == 0) return;
    boolean show = (mode & SHOW_MESSAGES) > 0;
    boolean clear = (mode & CLEAR_MESSAGES) > 0;
    String action =
      (show ? "Show" : "") +
      (show && clear ? " and " : "") +
      (clear ? "Clear" : "");
    System.out.println(action + " INBOX for " + from);
    Store store = session.getStore();
    store.connect();
    Folder root = store.getDefaultFolder();
    Folder inbox = root.getFolder("inbox");
    inbox.open(Folder.READ_WRITE);
    Message[] msgs = inbox.getMessages();
    if (msgs.length == 0 && show)
    {
      System.out.println("No messages in inbox");
    }
    for (int i = 0; i < msgs.length; i++)
    {
      MimeMessage msg = (MimeMessage)msgs[i];
      if (show)
      {
        System.out.println("    From: " + msg.getFrom()[0]);
        System.out.println(" Subject: " + msg.getSubject());
        System.out.println(" Content: " + msg.getContent());
      }
      if (clear)
      {
        msg.setFlag(Flags.Flag.DELETED, true);
      }
    }
    inbox.close(true);
    store.close();
    System.out.println();
  }
}

The MailClient class is primarily designed to let us send messages and to display or delete the list of messages available on the server for a given user. I've declared some useful constants that let us SHOW_MESSAGES, CLEAR_MESSAGES, or both. The MailClient class also implements the Authenticator interface to make it easy to manage the logon process when retrieving e-mail.

I've created two constructors, one of which sets the JavaMail debugging flag explicitly. This prints client/server protocol interactions to the console so that you can see what's going on. The shorter constructor leaves this flag off. The other two arguments are the user name and host. By implication, the e-mail address can be derived from the user and host. We create a PasswordAuthentication object that can be returned by the getPasswordAuthentication() method specified in the Authenticator interface.

The rest of the constructor code sets up the JavaMail properties to reflect the specified user and host, and explicitly specifies the protocols we intend to use. Once we have the Properties object populated, we can call the static Session method getInstance() to get a valid Session reference, which we store in a local variable. Once the constructor has been used with a specified user, we are ready to send and retrieve e-mail for that user on the specified e-mail host.

The sendMessage() method is straightforward as well. It builds a MimeMessage with the specified recipient, subject, and text content, and then sends it using the JavaMail static send() method in the Transport class. To make it easy to see what's going on, we also print a message to the console.

The checkInbox() method does more work because it needs to list messages and, optionally, erase them. It's also possible to just erase messages without looking at them, depending on the flags you use for the mode argument. To actually get the messages, we need to get a reference to the Store through our session object, connect to the server, and then open the inbox folder. After we have a reference to the folder, we can iterate through the messages and show or erase them.

Now that we have our reusable MailClient code, we're ready to write a quick test for the James server on localhost. The JamesConfigTest class in Listing 5 does this by creating three MailClient instances, each associated with one of our users (red, green, and blue). Before you run this code, make sure that those users are valid on the e-mail server.

Listing 5. JamesConfigTest: A quick test for the James server
public class JamesConfigTest
{
  public static void main(String[] args)
    throws Exception
  {
    // CREATE CLIENT INSTANCES
    MailClient redClient = new MailClient("red", "localhost");
    MailClient greenClient = new MailClient("green", "localhost");
    MailClient blueClient = new MailClient("blue", "localhost");
    
    // CLEAR EVERYBODY'S INBOX
    redClient.checkInbox(MailClient.CLEAR_MESSAGES);
    greenClient.checkInbox(MailClient.CLEAR_MESSAGES);
    blueClient.checkInbox(MailClient.CLEAR_MESSAGES);
    Thread.sleep(500); // Let the server catch up
    
    // SEND A COUPLE OF MESSAGES TO BLUE (FROM RED AND GREEN)
    redClient.sendMessage(
      "blue@localhost",
      "Testing blue from red",
      "This is a test message");
    greenClient.sendMessage(
      "blue@localhost",
      "Testing blue from green",
      "This is a test message");
    Thread.sleep(500); // Let the server catch up
    
    // LIST MESSAGES FOR BLUE (EXPECT MESSAGES FROM RED AND GREEN)
    blueClient.checkInbox(MailClient.SHOW_AND_CLEAR);
  }
}

After creating the three MailClient instances, the JamesConfigTest code simply clears each mailbox using the checkInbox() method with the CLEAR_MESSAGES mode, and waits a half second to make sure that the server has processed the deletions. We next send a message to blue from both red and green, and then check for messages to the blue account. When you run JamesConfigTest, you should see output that looks like Listing 6:

Listing 6. Output from running JamesConfigTest
Clear INBOX for red@localhost

Clear INBOX for green@localhost

Clear INBOX for blue@localhost

SENDING message from red@localhost to blue@localhost

SENDING message from green@localhost to blue@localhost

Show and Clear INBOX for blue@localhost
    From: green@localhost
 Subject: Testing blue from green
 Content: This is a test message

    From: red@localhost
 Subject: Testing blue from red
 Content: This is a test message

This proves that our James setup is functional; you'll need to have your system set up in this way before proceeding to development. We'll hold off on that until the second part of this series, however. In the remainder of this article, we'll examine the Matcher and Mailet APIs and the prewritten matchers and mailets provided as part of the James distribution. We'll also take a quick look at some additional features supported by James.


Matchers

James comes with a number of standard matchers. Each of these implements the Matcher API, illustrated in Listing 7, and provides functionality that is common to existing MTAs, as well as other useful extensions. The interface is fairly simple; it includes a couple of life-cycle methods, init() and destroy(), along with a pair of bookkeeping methods, getMatcherInfo() and getMatcherConfig(), and the main method, match(), which operates on a Mail object. The Mail reference provides access to container state, the mail message, and metadata for processing.

Listing 7. The Matcher interface
public interface Matcher
{
  void init(MatcherConfig config);
  void destroy();
  String getMatcherInfo();
  MatcherConfig getMatcherConfig();
  Collection match(Mail mail);
}

A matcher is responsible for recognizing a group of recipients and returns a collection of String objects that represent the recipients to be processed by a mailet. By combining matcher recognition and mailet processing, you can develop complex applications that operate on e-mail messages.

The matchers provided as part of the James distribution enable you to do several things without having to develop your own matcher implementations. It's important to know what these are before deciding to develop your own matchers; in many cases, the job you're contemplating may already be done for you. You can see a list of these prewritten matchers in Table 1:

Table 1. Prewritten James matchers

MatcherDescription
AllMatches all e-mails being processed and returns all recipients
HasHeaderMatches a specified header, if present
HasAttachmentMatches if the message is a multipart message
SubjectStartsWithMatches messages whose subjects start with the specified subject text
SubjectIsMatches messages that have a specific subject
HostIsMatches messages with a specified host
HostIsLocalMatches messages originating from localhost
UserIsMatches a specified user
SenderIsMatches a specified sender
SenderInFakeDomainMatches senders whose host address cannot be resolved
SizeGreaterThanMatches messages whose sizes are greater than a specified limit
RecipientsMatches messages with recipients from a specified list
RecipientsLocalMatches messages with local recipients
IsSingleRecipientMatches messages with only one recipient
RemoteAddrInNetworkMatches messages from a specified list of IP addresses, domains, and so on
RemoteAddrNotInNetworkMatches messages not from a specified list of IP addresses, domains, and so on
RelayLimitMatches messages relayed through more than a specified number of servers
InSpammerBlackListMatches addresses to a list provided by mail-abuse.org
NESSpamCheckMatches spam using a method derived from a Netscape Mail Server
HasHabeasWarrantMarkMatches mail with a Habeas Warrant
FetchedFromMatches the X-fetched-from header used by FetchPOP
CommandForListservMatches commands for the list server

As you can see from this list, you can accomplish many tasks without writing any new code, including granular operations like matching headers, subjects, or recipients, as well as high-level operations like detecting spam and processing list server commands.


Mailets

Many of James' features are implemented through the Mailet API illustrated in Listing 8, which will seem oddly familiar to developers accustomed to using the Servlet API. Like the Matcher API, the Mailet interface supports two lifecycle methods to provide initialization (the init() method) and shutdown (the destroy() method). Two additional methods return information. The first, getMailetInfo(), returns a String object that should contain information like the author, version, and copyright associated with the mailet. The second, getMailetConfig(), is useful for returning the current mailet configuration information. The init() method takes a MailetConfig object as an argument, so this is normally the object returned by the getMailetConfig() method, though it may have been modified.

Listing 8. The Mailet interface
public interface Mailet
{
  void init(MailetConfig config);
  void destroy();
  String getMailetInfo();
  MailetConfig getMailetConfig(); 
  void service(Mail mail);
}

Main processing is done in the services() method, which takes a Mail object argument. This object provides additional access to container state, the mail message, and metadata for processing.

To give you an idea of the features that are supported by James and the kinds of mailet applications that already exist, Table 2 provides a list of the mailet implementations available in James right out of the box:

Table 2. Prewritten James mailets

MailetDescription
NullEnds processing for the e-mail message
AddHeaderAdds a text header to the message content
AddFooterAdds a text footer to the message content
ForwardForwards the message to a list of recipients
RedirectProvides configurable redirection services
ToProcessorRedirects e-mail processing to a specified processor
ToRepositoryPuts a copy of the message in the specified directory
NotifySenderForwards the message as an attachment to the original sender
NotifyPostmasterForwards the message as an attachment to the postmaster
RemoteDeliveryManages SMTP host deliveries
LocalDeliveryDelivers messages to local mailboxes
JDBCAliasDoes alias translation using a JDBC data source
JDBCVirtualUserTableDoes more complex alias translation using a JDBC data source
UseHeaderRecipientRegenerates the mail recipients from the message header
ServerTimeSends a server time-stamp message
PostmasterAliasRedirects messages for postmaster@<domain> to an individual's address
AddHabeasWarrantMarkAdds a Habeas Warrant mark to the message
AvalonListservProvides basic list server functionality
AvalonListservManagerProcesses list server management commands

As you can see from this list, there are several features already available in James thanks to the Mailet API, including complex list server support, aliasing, and storage and routing capabilities.


Additional features

James supports many other capabilities that are beyond the scope of this series; however, we'll briefly mention them here so that you can have a better idea of what James is actually capable of. The first of these is NNTP support, which allows James to act as a Usenet server. James also implements the FetchPOP protocol to support mail-based remote management features. The RemoteManager and SpoolManager provide abstractions that allow multiple types of storage and management support to exist. For development purposes, it's sufficient to rely on the filesystem-based SpoolManager, for example, though both partially and fully database-centric solutions are also provided.

James provides interfaces and services to allow users to be managed effectively, and mailing list support is available out of the box. In fact, the mailing list feature is one of the most used services provided by James and is often one of the primary reasons administrators choose James as an e-mail solution.


What's next?

The James infrastructure is designed for flexibility and easy application development. E-mail application possibilities are limited only by your imagination. In the follow-up installment of this series, we'll develop a simple application that allows users to send e-mail messages to a specific James address to enable vacation message-type features. Users can draft an e-mail that will be sent to all incoming senders until the user sends a cancellation message to another specified James address to turn it off. This solution mimics a mechanism that is often implemented by e-mail client software, but that is generally limited to a single geographical location because of it: if your mail client is turned off, the feature does not work. By implementing this functionality on the e-mail server, we can still check our mail from any location without restraint, and we can easily change our "away" message to a more appropriate response if our plans change.


Download

DescriptionNameSize
Code samplej-james1.zip2KB

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=10821
ArticleTitle=Working with James, Part 1: An introduction to Apache's James enterprise e-mail server
publish-date=06102003