Create email-based apps with JAMES

The open source JAMES mail server offers the tools to develop email-based applications in Java

When it’s not ideal to send users to a Website or to launch a GUI-based application, email-based applications can offer an innovative way for users to communicate with server software. Developers have long recognized the value of sending generated emails to users, but until now they have been unable to listen in response. Traditionally, the developer’s best approach for a response from generated email was to have the user click a link in the email, sending him or her to a Website URL.

With an email-based application, a user can send an email to a server; the server can then process that email, making the communication two-way. Email-based interaction offers advantages over sending a user to a Website. First, interaction proceeds even if the user is offline. A user can read her email and, in turn, reply with a series of emails that create an instruction queue for a remote system. Second, under some circumstances a user can respond quicker via an email. If the needed response is simple, an email proves faster than Website interactions because the user stays within her email software.

The Java Apache Mail Enterprise Server (JAMES), an open source effort from the Jakarta Project, allows for custom email processing. With JAMES, developers can process messages from users via email. JAMES offers POP3 (Post Office Protocol) and NNTP (Network News Transport Protocol) support, and may support IMAP (Internet Message Access Protocol) in a future release.

In this article, I present an online scheduling system that uses JAMES to capture users’ availability. In the future, the scheduling system may use JAMES to track the validity of email addresses, track unsuccessfully delivered scheduling requests, and otherwise further integrate the Web and email sides of the system.

This article assumes that you know how to send an email using JavaMail. (For a JavaWorld article on JavaMail, read “JavaMail Quick Start.”) I focus on how to design the communication flow for an email-based application, how to set up JAMES, and how to process user messages.

Note: You can download this article’s source code from Resources.

Plan the interaction

In our scheduling system, buyers look for sellers available to do a small piece of work. In the existing system, buyers use a Website to enter a request that is sent to a group of sellers, generating an email to each seller about the request. Sellers receive the email, click an HTTP link to launch a browser, and view the requests on the Website. Sellers then use the Website to enter their availability status and optional notes about why they are or are not available.

In the new approach, the seller composes an email in reply to the email the scheduling system generates. For the seller, there are only two possible responses: he is either available or unavailable. To make his decision known, the seller will send his reply to one of two special email addresses; depending on the address to which the seller responds, his availability will be apparent.

These special email addresses contain the seller’s state and a unique identifier for the request in question. The address’s structure looks like this:

request-[availability]-[request ID]@mydomain.com

For instance, a seller reading about request 79834 who wants to indicate his availability would send a message to:

[email protected]

When the server receives the seller’s email, it can identify the request and availability status from the target address, and identify the seller by way of his email address. If this information is sensitive, you could include a hash parameter based on the seller’s account and the request ID instead of the plain request number. The encoded address would then look like this:

[email protected]

The hash parameter a87bc8e0d0ea8 confirms that the message was not from a seller indicating availability for someone else.

Note that the seller need not type the special address; the email inserts it for both available and unavailable responses in the header and message body. The email contains the special email address that indicates availability in the Reply-To header line. Usually, when the seller hits the Reply button on his email client, that client sends a message addressed to the special email address. The unavailability-indication address resides in the CC header line. When the seller hits the Reply to All button on his email client, the unavailability-indication address appears in the list of recipients. The seller can then delete the other address that indicates availability.

The email’s body contains the special email addresses in the instructions; most email software will make the address clickable. Also, if you send HTML email, you can use mailto: links easily generate messages to the appropriate address.

The Mailet API

Now that you know what you are listening for, you need to write code that will do the listening. The Mailet API makes the new approach possible. The API builds on the JavaMail API and uses an approach similar to the servlet specification’s lifecycle methods and object hierarchy.

The Mailet API comprises two core classes: Matcher and Mailet. Both are interfaces with abstract implementations available as GenericMatcher and GenericMailet, respectively. A matcher determines whether a message should be processed, while a mailet processes the message. A mailet container, which is a mailet-compliant SMTP (Simple Mail Transfer Protocol) mail server, instantiates and manages both.

The matcher determines the routing inside a mailet container. The match(Mail mail) method returns a Collection of recipients that meet the matcher’s criteria. (The result is a Collection because a message can contain multiple recipient addresses during SMTP transport.)

You’ll also find an abstract GenericRecipientMatcher implementation that helps the matching based on the recipient’s address. It iterates through the recipient in the Collection and generates the resulting Collection object so the developer need only implement the matchRecipient(MailAddress recipient) method.

The mailet defines methods to initialize a mailet, service a message, and remove a mailet from the server. The mailet container calls these life-cycle methods in the following sequence:

  1. The mailet is constructed, then initialized with the init(MailetConfig config) method
  2. Any messages for the service(Mail mail) method are handled
  3. The mailet is taken out of service, destroyed with the destroy() method, then garbage collected and finalized

MailetConfig, analogous to the Servlet API’s ServletConfig, allows the mailet container to pass initialization parameters to the mailet.

Other useful Mailet API classes and interfaces include Mail, MailAddress, and MailetContext. The Mail interface wraps a MimeMessage object with SMTP delivery information, including the sender’s address, a recipient’s Collection, the connecting machine’s remote IP address, and the message state in the mailet container. MailAddress, a more rigorous implementation of JavaMail’s InternetAddress, provides helper methods to find an email address’s domain and user part. The MailetContext is analogous to the ServletContext as it provides access to the mailet container.

For the scheduling system, you will build a simple matcher that checks for addresses matching the address specification discussed above, and a mailet to process the responses and determine the sellers’ availability status.

Install and configure JAMES

You can download the latest version of JAMES from the Jakarta Project Website. At press time, the most recent version was 2.0 alpha 1 — a stable release with significant speed and feature improvements over version 1.2.1. It comprises near-production quality code, but the project elected to release an alpha version to allow for the option to change the configuration file’s structure before the final release.

After downloading and extracting the distribution to your hard drive, start JAMES by running run.bat or run.sh in the bin directory. JAMES will extract itself in the apps directory, and the basic service will begin running.

To configure JAMES, you need to modify the configuration file (apps/james/conf/config.xml). If you run JAMES on a machine without a running local DNS server, you need to identify one in the config file; at line 278, add another line with an available DNS server’s IP address. You need to specify the domain names JAMES should handle. At line 37, add any additional domain names your machine should handle, according to your host settings.

You also need to configure a database connection. In the database connection section at line 602, either uncomment the appropriate data source or create your own by specifying the appropriate <driver>, <dburl> (the JDBC [Java Database Connectivity] URL), <user>, and <password>. Keep the name of the data source as maildb.

The custom matcher

The matcher, dubbed SchedulingRequestMatcher, extends GenericRecipientMatcher to match based on the recipient’s address. The matcher will not attempt to validate the address’s structure, only determine whether the address starts with request-available- or request-unavailable-. The single method looks like this:

public boolean matchRecipient(MailAddress recipient) {
    if (recipient.getUser().startsWith("request-available-")) {
        return true;
    }
    if (recipient.getUser().startsWith("request-unavailable-")) {
        return true;
    }
    return false;
}

As mentioned above, you could validate the address in the matcher; instead, we’ll leave it to the mailet to determine the validity. While it is unlikely that this kind of address would have errors, it’s better to have it match and then let the mailet catch any errors and reply with error messages.

The custom mailet

The mailet’s service() method comprises two major sections. First, the mailet must parse the recipient address, and determine the availability and request ID. Second, the mailet must make a JDBC call to update the seller’s availability for this request. I’ve wrapped both sections in a try/catch block, so if errors occur and the action cannot complete, you can notify the seller of the problem. Name the mailet SchedulingRequestMailet and have it extend GenericMailet.

Start by parsing the recipient address to determine the availability and request ID. Check that only a single recipient has been specified, because each message can indicate availability or unavailability for only a single request. The service method starts:

public void service(Mail mail) throws MessagingException {
    Collection recipients = mail.getRecipients();
    if (recipients.size() > 1) {
        //In final implementation, send error message to sender
        return;
    }
    MailAddress recipient = (MailAddress)recipients.iterator().next();
    //Determine whether the seller is available
    boolean sellerAvailable = recipient.getUser().
       startsWith("request-available-");
    //Determine the request ID in question
    String requestID = recipient.getUser();
    int pos = requestID.lastIndexOf("-");
    requestID = requestID.substring(pos + 1);
    //Get the seller's email address
    String sellerAddress = mail.getSender().toString();

The second section, taking these settings, uses JDBC to store the result. However, before it can store anything via JDBC, you need a handle to a database connection pool. The mailet will get a reference to a pool during the init() method. Because much of this code is specific to Avalon, the underlying application framework upon which JAMES is built, it is not necessary to understand the specifics. Nonetheless, note that it gives a reference to a database connection pool:

protected DataSourceComponent datasource;
public void init() throws MessagingException {
    try {
        //Get a reference to the Avalon component manager
        ComponentManager componentManager = (ComponentManager)getMailetContext()
          .getAttribute(Constants.AVALON_COMPONENT_MANAGER);
        // Get the list of possible data sources
        DataSourceSelector datasources = (DataSourceSelector)
           componentManager.lookup(DataSourceSelector.ROLE);
        // Get the data source we need
        datasource = (DataSourceComponent)datasources.select("maildb");
    } catch (Exception e) {
        throw new MessagingException("Error getting datasource", e);
    }
}

With your database connection pool, you can now write the service() method’s second half:

    Connection conn = datasource.getConnection();
    String sql = "UPDATE SellerRequest SET available =
        ? WHERE seller_email = ? AND request_id = ?";
    PreparedStatement stmt = conn.prepareStatement(sql);
    stmt.setBoolean(1, sellerAvailable);
    stmt.setString(2, sellerAddress);
    stmt.setInt(3, Integer.parseInt(requestID));
    stmt.execute();
    stmt.close();
    conn.close();
    mail.setState(Mail.GHOST);
}

The second-to-last line above sets the message’s state to Mail.GHOST and indicates to the mailet container that this message should not be processed further. Mailets that filter a message and want let it continue processing should leave the state as is.

Again, in the final code version, you’ll wrap everything in the service() method inside a try/catch block so that if any errors occur parsing the address or the message, or if there is a problem with the database connection, you can bounce a message back to the sender. You may also want to notify the seller that his response was successfully received and processed.

Install the matcher and mailet

To compile the code, you will need mailet.jar, cornerstone.jar, and excalibur.jar, all of which come bundled with JAMES. Once you have compiled both classes, create a jar file in the lib directory with the other included jar files. You should also put a jar file for your JDBC driver here if you are not using the bundled MySQL driver.

Next, open the config.xml file you edited earlier. You want to insert your matcher and mailet in the root processor, which is the first set of matchers and mailets that the mailet container sends messages through. Around line 158, add the following setting:

<mailet match="SchedulingRequestMatcher" class="SchedulingRequestMailet">
</mailet>

The above configuration block can appear anywhere in the root processor, but should be before the final matcher/mailet setting that uses ToProcessor to send messages to the transport processor.

With this in place, restart JAMES and the new matcher and mailet should be initialized and begin processing. The log files should report whether or not they successfully loaded and if they encountered any problems.

Other ideas for email-based applications

In this article, I provided a simple example showing how you can use matchers and mailets. I hope my example sparks others to support email-based interaction in their applications. Other examples employing JAMES include:

  • Attaching footers to messages
  • Spam detection
  • Traffic logging
  • Message-delivery problem tracking
  • SMS (Short Message Service) notification
  • Easy file uploading with attachments
  • Mailing list management

Other more exotic ideas include integrating JAMES with a fax server for email-fax-email integration. Or perhaps an application could scan for repeatedly forwarded messages to break chain messages before they clog up a mail server. Or, for the truly pious mail server administrator, a matcher could check the percentage of skin tone in attached images to spot email containing explicit images.

Email is indeed the Internet’s killer app, and JAMES gives Java developers the tools they need to build email-based applications.

Serge Knystautas is the
lead technical guru and Java developer at Loki Technologies. He donated
the code that started the JAMES project nearly 3
years ago, and he continues as an active contributor. He’s been
using Java since 1.0 beta (1995) and contributes to JSR 052, which aims to
codify a standard tag library for JavaServer Pages.

Source: www.infoworld.com