A WAP-based E-mail Application
Web mail accounts such as the ubiquitous
'Hotmail', or 'Yahoo! Mail' are fast becoming the most popular kind of e-mail –
there are almost 170 million subscribers to this type of account.
Web-based mail allows users convenient web
access to their mail account without any messy machine configuration issues.
Our second sample application implements a simple WAP mail system, allowing
access to an SMTP/POP3-based e-mail account.
This example uses Java servlets and
JavaMail to:
Compose, send and reply to mail via an
SMTP server
View an inbox and read mail using a
POP3 service
Determine the number of messages
waiting in the inbox
Delete mail
Diagram of WAPMail's Functionality
Below is a diagram illustrating
the functionality of the basic web mail system we will create in WAPMail.java. The user can logon to the system, view their POP3 inbox, read
mail, reply to, compose, send, and delete e-mail.
WAPMail.java
WAPMail provides all the basic
functionality described above, but the code has been designed for illustration
rather than deployment in a production environment. It should be noted that the
code is limited in the following ways:
It has minimal error checking.
There is no guarantee that a WML deck
of more that 1400 bytes will be accepted by the WAP browser. Large emails or a
long list of emails in the inbox will cause problems.
It is not implemented for maximum speed
and efficiency (for example, use of String
concatenation rather than StringBuffers)
It can only be used by a single user –
session information is held in a static class variable. We would need to use
some session tracking code to make this code multi-user and scaleable.
It has some problems if the browser
does not correctly respond to Cache-Control response headers.
WAPMail.java is
quite a long piece of code. Below, we'll step through it one piece at a time so
we might clearly understand what the code is doing.
The first piece of code shows the servlet's
main decision code. When the servlet receives a HTTP POST or GET request, it
executes the doPost() or doGet() respectively. If the user has not logged in, and had the necessary
session information created, they are shown the splash screen, which requires
them to login to proceed:
import
java.io.*;
import
java.util.*;
import
javax.mail.*;
import
javax.mail.internet.*;
import
javax.servlet.*;
import
javax.servlet.http.*;
import
com.wrox.util.WML;
public
class WAPMail extends HttpServlet implements SingleThreadModel
{
private static String inboxString =
"INBOX";
private static UserSessionData _userSessionData;
public void doGet (HttpServletRequest
request,
HttpServletResponse
response)
throws ServletException, IOException
{
this.doPost(request,response);
}
public void doPost (HttpServletRequest
request,
HttpServletResponse
response)
throws ServletException, IOException
{
UserSessionData userSessionData =
getUserSessionData();
String action =
request.getParameter("action");
//Main decision making
"loop"
try {
//SHOW SPLASH SCREEN
if ((action == null) ||
((!action.equalsIgnoreCase("login")) &&
(userSessionData == null))) {
this.splashScreen(request,
response);
}
//LOGIN
else if
(action.equalsIgnoreCase("login")) {
this.login(request, response);
}
//LOGOUT
else if
(action.equalsIgnoreCase("logout")) {
this.logout(request, response);
}
//SHOW MAIN MENU
else if (action.equalsIgnoreCase("mainmenu"))
{
this.mainMenu(request,
response, this.getUserSessionData());
}
//COMPOSE
else if
(action.equalsIgnoreCase("compose")) {
this.compose(request,
response);
}
//SEND
else if
(action.equalsIgnoreCase("send")) {
this.send(request, response,
this.getUserSessionData());
}
//VIEW IN BOX
else if
(action.equalsIgnoreCase("viewinbox")) {
this.viewInbox(request,
response, this.getUserSessionData());
}
//READ
else if
(action.equalsIgnoreCase("read")) {
this.read(request, response,
this.getUserSessionData());
}
//DELETE
else if
(action.equalsIgnoreCase("delete")) {
this.deleteMessage(request,
response, this.getUserSessionData());
}
//REPLY
else if
(action.equalsIgnoreCase("reply")) {
this.replyToMessage(request, response,
this.getUserSessionData());
}
} catch (MessagingException me) {
me.printStackTrace();
}
}
public void splashScreen(HttpServletRequest
request,
HttpServletResponse response)
throws ServletException, IOException,
MessagingException
{
this.splashScreen(request, response,
"");
}
}
public void splashScreen(HttpServletRequest
request,
HttpServletResponse response, String message)
throws ServletException, IOException,
MessagingException {
WML wml = new WML();
wml.addCard("WAPMailSplash");
wml.println("<do
type=\"accept\" label=\"Login\">" +
"<go href=\"" +
request.getRequestURI() + "\" method=\"post\">" +
"<postfield
name=\"action\" value=\"login\"/>" +
"<postfield
name=\"uid\" value=\"$uid\"/>" +
"<postfield
name=\"pwd\" value=\"$pwd\"/>" +
"</go>" +
"</do>" +
"<p
align=\"center\">" +
"WAP E-mail" +
"</p>");
if (message != "") {
wml.println("<p
align=\"left\">" +
"<i>" +
message + "</i>" +
"</p>");
}
wml.println("<p>" +
"Username:" +
"<input
name=\"uid\" title=\"user name\"/><br/>" +
"Password:" +
"<input
name=\"pwd\" type=\"password\"
title=\"password\"/><br/>" +
"</p>");
wml.endCard();
wml.outputWML(response, true);
}
The user is required to log in by
specifying their POP3 username (e-mail address) and password. Here is a screen
shot sequence of the login process using the Phone.com browser simulator:
|
If there are any problems in the login
process, for instance an incorrect password, the user is presented with this
error message, and an opportunity to re-try the login:
|

|
This login process creates an SMTP and a
POP3 session, storing them in an instance of UserSessionData, which
models this information throughout the rest of the WAPMail code. We will see
the code for this class at the end of the WAPMail listing.
It is the use of a static variable to store
the UserSessionData instance that limits the use of the system to a single user. To
allow multiple access to the system, WAPMail could be re-modeled to track the
user's HttpSession, making use of this to provide an indexed list of these UserSessionData
objects. Here this is left as an exercise for the reader.
The next piece
of code shows the login process:
public void login(HttpServletRequest
request, HttpServletResponse response)
throws ServletException, IOException,
MessagingException {
try {
String pop3host =
"pop.yourISP.net";
String smtphost =
"smtp.yourISP.net";
String username =
request.getParameter("uid");
String password =
request.getParameter("pwd");
//Get SMTP Session
Properties props = System.getProperties();
props.put("mail.smtp.host",
smtphost);
//Get SMTP session
Session smtpSession =
Session.getInstance(props, null);
smtpSession.setDebug(false);
//Get POP3 Session
Session pop3Session =
Session.getInstance(System.getProperties(), null);
pop3Session.setDebug(false);
//Get POP3 Store
Store pop3Store =
pop3Session.getStore("pop3");
pop3Store.connect(pop3host, username,
password);
//Create a new UserSessionData object
UserSessionData usd = new
UserSessionData(smtpSession, pop3Session,
pop3Store,
username);
this.setUserSessionData(usd);
//if all okay show main menu
this.mainMenu(request, response,
usd);
} catch (Exception e) {
e.printStackTrace();
this.splashScreen(request, response,
"Error logging in!");
}
}
public void logout(HttpServletRequest
request, HttpServletResponse response)
throws ServletException, IOException,
MessagingException {
try {
this.getUserSessionData().destroy();
this._userSessionData
= null;
WML wml = new WML();
wml.addCard("WAPMailLogout");
wml.println("<p
align=\"left\">" +
"Thank you for
using WAP Mail<br/>" +
"<anchor>Restart E-mail" +
"<go
href=\"" + request.getRequestURI() + "\"/>" +
"</anchor>" +
"</p>");
wml.endCard();
wml.outputWML(response, true);
} catch (Exception e) {
e.printStackTrace();
}
}
public void mainMenu(HttpServletRequest
request,
HttpServletResponse response,
UserSessionData userSessionData)
throws ServletException, IOException,
MessagingException {
try {
WML wml = new WML();
wml.addCard("WAPMailMainMenu", "Main Menu");
wml.println("<p
align=\"left\">" +
"<anchor>1. Read Mail " +
this.getInboxCount(userSessionData) +
"<go href=\""
+ request.getRequestURI() +
"?action=viewinbox\"/>" +
"</anchor><br/>" +
"<anchor>2.
Compose" +
"<go href=\""
+ request.getRequestURI() + "?action=compose\"/>" +
"</anchor><br/>" +
"<anchor>3.
Logout" +
"<go href=\""
+ request.getRequestURI() + "?action=logout\"/>" +
"</anchor><br/>" +
"</p>");
wml.endCard();
wml.outputWML(response, true);
} catch (Exception e) {
e.printStackTrace();
}
}
This code takes
the user's username and password as supplied, and attempts to connect to his
POP3 inbox, to enable the user to read his mail. It also creates an SMTP
session in order to send mail. We can see the logout method below, and the
method that constructs the main menu for the WAP mail system.
|
This produces the mainmenu of WAPMail:
|

|
The following code shows the compose() method, which constructs a WML page that enables the user of our mail system to enter the details of an email: who it is to, what the subject is, and the text of the e-mail:
public void compose(HttpServletRequest request, HttpServletResponse
response)
throws ServletException, IOException, MessagingException {
this.compose(request, response, "", "");
}
public void compose(HttpServletRequest request,
HttpServletResponse response, String to, String
subject)
throws ServletException, IOException, MessagingException {
WML wml = new WML();
wml.addCard("WAPMailCompose");
wml.println("<p>" +
"<fieldset title=\"Compose\">" +
"To:<input name=\"to\" maxlength=\"100\"
emptyok=\"false\"" +
" value=\"" + to +
"\"/><br/>" +
"cc:<input name=\"cc\"
maxlength=\"100\" emptyok=\"true\"/><br/>" +
"Subject:<input name=\"subject\"
value=\"" + subject +
"\"
emptyok=\"true\"/><br/>" +
"Text:<input name=\"text\"
maxlength=\"500\" emptyok=\"true\"/><br/>" +
"</fieldset>" +
"<anchor> Send " +
"<go href=\"" + request.getRequestURI() +
"\" method=\"post\">" +
"<postfield name=\"action\"
value=\"send\"/>" +
"<postfield
name=\"to\" value=\"$to\"/>" +
"<postfield name=\"cc\"
value=\"$cc\"/>" +
"<postfield name=\"subject\"
value=\"$subject\"/>" +
"<postfield name=\"text\"
value=\"$text\"/>" +
"</go>" +
"</anchor>" +
"</p>");
wml.endCard();
wml.outputWML(response, true);
}
To compose an e-mail, the user must enter:
A recipient
A circulation list (optional – leave
blank if not required)
A subject for the message
Some text for the body of the message
The example of constructing a message above
relies on the fact that the String 'to' is a single Internet address in the form of john_doe@wapbook.org:
message.addRecipient(Message.RecipientType.TO,
new InternetAddress(to));
If you wish to give your users the ability
to type in a comma delimited list of e-mail addresses then you can use the parse() method
of the InternetAddress class to return an array of InternetAddress objects and
use the setRecipients()of the message object:
message.setRecipients(Message.RecipientType.TO,
InternetAddress.parse(to));
If an invalid string is encountered when
trying to construct an InternetAddress object, an AddressException is thrown.
Now, on with the code: The next section
shows the method that sends e-mail using the same JavaMail process we
encountered in our first code example. We can also see listed below the viewInbox() method that shows the e-mails waiting in our inbox:
public void send(HttpServletRequest request,
HttpServletResponse response, UserSessionData
userSessionData)
throws ServletException, IOException, MessagingException {
try {
String from = userSessionData.getEmailAddress();
String to = request.getParameter("to");
String cc = request.getParameter("cc");
String subject =
request.getParameter("subject");
String text = request.getParameter("text");
//Define message
MimeMessage message = new
MimeMessage(userSessionData.getSmtpSession());
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO,
new InternetAddress(to));
try {
message.addRecipient(Message.RecipientType.CC,
new InternetAddress(cc));
} catch
(AddressException ae) {
//Bad cc address
}
message.setSubject(subject);
message.setText(text);
//send message
Transport.send(message);
this.mainMenu(request, response, userSessionData);
}catch (Exception e) {
e.printStackTrace();
}
}
public void viewInbox(HttpServletRequest request,
HttpServletResponse response, UserSessionData
userSessionData)
throws ServletException, IOException, MessagingException {
try {
//Get Folder
Folder folder =
userSessionData.getPop3Store().getFolder(inboxString);
folder.open(Folder.READ_ONLY);
//Get Directory
Message message[] = folder.getMessages();
int n = message.length;
WML wml = new WML();
wml.addCard("WAPMailViewInbox");
wml.println("<p
align=\"left\">");
wml.println("Inbox:<br/>");
for (int i=0; i < n; i++) {
String emailAddress =
((InternetAddress)message[i].getFrom()[0]).getAddress();
wml.println((i + 1) + ": " + emailAddress +
"<br/>" +
message[i].getSubject() + "<br/>" +
"<anchor>Read" +
"<go href=\"" + request.getRequestURI()
+ "\" method=\"post\">" +
"<postfield name=\"action\"
value=\"read\"/>" +
"<postfield name=\"index\"
value=\"" + (i + 1) + "\"/>" +
"</go>" +
"</anchor><br/>" +
"<anchor>Delete" +
"<go href=\"" +
request.getRequestURI() + "\" method=\"post\">" +
"<postfield name=\"action\"
value=\"delete\"/>" +
"<postfield name=\"index\"
value=\"" + (i + 1) + "\"/>" +
"</go>" +
"</anchor><br/>" +
"------------<br/>");
}
wml.println("</p>" +
"<p>" +
"<anchor>Main Menu" +
"<go href=\"" +
request.getRequestURI() + "?action=mainmenu\"/>" +
"</anchor>" +
"</p>");
wml.endCard();
wml.outputWML(response, true);
//Close connection
folder.close(false);
} catch (Exception e) {
e.printStackTrace();
}
}
|
We can view the inbox as follows:
|

|
The following code shows the method that
reads a particular e-mail:
public void read(HttpServletRequest request,
HttpServletResponse response, UserSessionData
userSessionData)
throws ServletException, IOException, MessagingException {
try {
//Get Folder
Folder folder =
userSessionData.getPop3Store().getFolder(inboxString);
folder.open(Folder.READ_ONLY);
//Get Message
String indexString = request.getParameter("index");
int messageIndex = Integer.parseInt(indexString);
Message message = folder.getMessage(messageIndex);
Object messageContent = message.getContent();
WML wml = new WML();
wml.addCard("WAPMailReadMail");
String emailAddress =
((InternetAddress)message.getFrom()[0]).getAddress();
wml.println("<p align=\"left\">"
+
"From: " + emailAddress +
"<br/>" +
message.getSubject() + "<br/>" +
"------------<br/>");
if (message.isMimeType("text/plain") &&
messageContent instanceof String) {
wml.println((String)messageContent);
} else {
wml.println("Error! WAP Mail can only read plaintext
e-mails");
}
wml.println("------------<br/>" +
"<anchor>Reply" +
"<go href=\"" +
request.getRequestURI() + "\" method=\"post\">" +
"<postfield name=\"action\"
value=\"reply\"/>" +
"<postfield name=\"to\"
value=\"" + emailAddress + "\"/>" +
"<postfield name=\"subject\"
value=\"Re: " +
message.getSubject() + "\"/>" +
"</go>" +
"</anchor><br/>" +
"<anchor>Delete" +
"<go href=\"" +
request.getRequestURI() + "\" method=\"post\">" +
"<postfield name=\"action\"
value=\"delete\"/>" +
"<postfield name=\"index\"
value=\"" + messageIndex + "\"/>" +
"</go>" +
"</anchor><br/>" +
"------------<br/>" +
"</p>" +
"<p>" +
"<anchor>Main Menu" +
"<go href=\"" +
request.getRequestURI() + "?action=mainmenu\"/>" +
"</anchor><br/>" +
"<anchor>Back to Inbox" +
"<go href=\"" +
request.getRequestURI() + "?action=viewinbox\"/>" +
"</anchor>" +
"</p>");
wml.endCard();
wml.outputWML(response, true);
//Close connection
folder.close(false);
} catch (Exception e) {
e.printStackTrace();
}
}
|
It is then possible to read an e-mail:
|

|
The final section of the WAPMail code shows
the method that deletes emails from our inbox, and also some utility methods
including a method that facilitates replying to a mail, and a method that
determines the number of emails waiting in the inbox:
public void deleteMessage (HttpServletRequest request,
HttpServletResponse response, UserSessionData
userSessionData)
throws ServletException, IOException, MessagingException {
try {
//Get Folder
Folder folder =
userSessionData.getPop3Store().getFolder(inboxString);
folder.open(Folder.READ_WRITE);
//Get Directory
int messageIndex =
Integer.parseInt(request.getParameter("index"));
Message message = folder.getMessage(messageIndex);
message.setFlag(Flags.Flag.DELETED, true);
//close connection
folder.close(true);
this.mainMenu(request, response, userSessionData);
} catch (Exception e) {
e.printStackTrace();
}
}
public void replyToMessage(HttpServletRequest request,
HttpServletResponse response, UserSessionData
userSessionData)
throws ServletException, IOException, MessagingException {
//Not using the reply mechanism of the Message object
String to = request.getParameter("to");
String subject = request.getParameter("subject");
this.compose(request, response, to, subject);
}
public String getInboxCount (UserSessionData userSessionData)
throws Exception {
//Get Folder
Folder folder =
userSessionData.getPop3Store().getFolder(inboxString);
folder.open(Folder.READ_ONLY);
//Get Count
int totalCount = folder.getMessageCount();
//Close Connection
folder.close(false);
return "(total: " + totalCount + ")";
}
private static UserSessionData getUserSessionData() {
return _userSessionData;
}
private static void setUserSessionData(UserSessionData usd) {
_userSessionData = usd;
}
public String getServletInfo() {
return "A simple WAP e-mail application servlet";
}
}
This class is used by WAPMail as a storage
mechanism for the SMTP and POP3 sessions that are created by the login process.
By storing the user's session information
in a separate class (UserSessionData) we
remove some of the session logic from the rest of the servlet, which should
make it easier to extend the functionality of WAPMail as the need arises:
// Storage class for user's
session information
class UserSessionData {
private Session _smtpSession;
private Session _pop3Session;
private Store _pop3store;
private String _userEmailAddress;
public UserSessionData(Session newSmtpSession,
Session newPop3Session,
Store newPop3Store,
String newUserEmailAddress) {
setSmtpSession(newSmtpSession);
setPop3Session(newPop3Session);
setPop3Store(newPop3Store);
setEmailAddress(newUserEmailAddress);
}
public void destroy() {
try {
if (getPop3Store() != null) {
getPop3Store().close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void setEmailAddress(String
newUserEmailAddress) {
_userEmailAddress
= newUserEmailAddress;
}
public String getEmailAddress() {
return _userEmailAddress;
}
public Session getSmtpSession() {
return _smtpSession;
}
public void setSmtpSession(Session
newSmtpSession) {
_smtpSession
= newSmtpSession;
}
public Session getPop3Session() {
return _pop3Session;
}
public void setPop3Session(Session
newPop3Session) {
_pop3Session
= newPop3Session;
}
public Store getPop3Store() {
return _pop3store;
}
public void setPop3Store(Store
newPop3Store) {
_pop3store
= newPop3Store;
}
}
|
This specifies the logout screen:
|

|
Enhancements to WAPMail
The implementation of WAPMail is designed
to illustrate basic e-mail functionality and is not a very elegant example of
Java servlet programming. Many things could be done to improve the design of
WAPMail, for example:
Use reflection to eliminate the huge if statement in the doPost() method.
Use a separate JavaBean to model the
mail system moving the logic out of the Java servlet, with which the Servlet
would then interface.
Use HttpSession tracking to allow
multi-user access to WAPMail.
Allow the user to specify the POP3 and
SMTP servers; perhaps allow for multiple accounts to be read.
If the e-mail contains illegal WML
characters, then the e-mail text needs to be parsed to ensure that we encounter
no problems. For example, the following use of '<' and '>' in the e-mail
address field seen in some e-mails is an illegal character sequence in WML, and
causes the generated WML response to be rejected by the browser: for example, John
Doe <John_Doe@wapbook.org>
Sending and Receiving E-Mail using ASP
So far in this chapter, we've concentrated
on using Java for incorporating e-mail into WAP applications. If you're in the
Microsoft camp, however, you will most likely be using ASP to generate your WAP
(and web) content. In this section we'll show how to use ASP with VBScript to
generate WML pages that access email functionality.
Due to space constraints we're going to
keep the examples simple – if you've got this far in the book then you must
already be familiar with WML, and if you're interested enough to read this
section then presumably that means you're already familiar with ASP itself too!
So, we're not going to stop to explain anything about ASP itself, and we're not
going to waste time writing a sophisticated set of WML pages – instead we'll
just demonstrate a simple page that sends an email based on a couple of
options, as well as another page that lets you check for incoming email
messages, all from your mobile phone.
In order to keep things simple, we'll also
use CDONTS rather than CDO The examples here were tested with CDONTS running on
Windows 2000 Professional, with IIS 5 installed. CDONTS will be default use the
SMTP service that comes with IIS as its mail service. However, if you prefer,
you can run CDONTS using Exchange Server.
Sending an E-Mail
Sending an email with CDONTS could hardly
be easier. For example, here's the VBScript code that sends an email addressed
to my own local account, simon@biggybiggy.lt.local.
dim objSendMail
set objSendMail =
Server.CreateObject("CDONTS.NewMail")
objSendMail.Send "editors@wrox.com",
"simon@biggybiggy.lt.local", _
"Meeting!", _
"Meeting to go over final adjustments to Pro WAP is
today at 2pm."
set objSendMail = nothing
In other words all we need to do is create
an object of type CDONTS.NewMail and
call its Send method. The
four parameters passed are (in order):
The address of the sender (Note that
no validation is performed on this: You can put in any address you want – it
doesn't have to be your own address).
The address the mail should be
delivered to
The title of the message
The text of the message body
You can also optionally add a fifth
parameter, the message importance, which can take any of the values 0 (low),
1(normal, the default value) and 2(high).