Creating a WebSnap Email Application
by Corbin Dunn
We have all seen applications that allow you to check your email on a web page. This document gives step-by-step instructions on how to create a WebSnap application that allows you to check your email from a web page.
First, select File->New->Other... and from the WebSnap tab select WebSnap Application.
For ease of debugging, select a Web App Debugger executable, and give it a CoClass Name of WebMail. Press the Components... button and check the following:
Select File->New->Other... and from the WebSnap tab select WebSnap Page Module to create another page. Name it LoginModule and uncheck Published. Create another page, naming it ViewMailModule, but leave Published checked and also check Login Required. Finally, create one more page named EmailMessageModule, unchecking the Published option.
Select File->Save All and save the project into a new folder of your choice. Save Unit5.pas (EmailMessageModule) as EmailMessageMod.pas, Unit4.pas (ViewMailModule) as ViewMailMod.pas, Unit3.pas (LoginModule) as LoginMod.pas, Unit2.pas (MainModule) as MainMod.pas, Unit1.pas (Form1) as MainFrm.pas and Project1.dpr (Project1) as WebMailApp.dpr.
The first thing to do is to create the Login page. On the LoginModule, place a TLoginFormAdapter. We want to replace the PageProducer with a TAdapterPageProducer, so select PageProducer and delete it. Place a TAdapterPageProducer on the LoginModule. Click anywhere on the LoginModule make sure that the PageProducer property in the Object Inspector now says AdapterPageProducer1. Double-click on the LoginFormAdapter1 to bring up the Fields editor. Right click in the right-hand pane and select Add all fields.
Double-click on the AdapterPageProducer1 to bring up the web page editor, and then right-click on the AdapterPageProducer1 and select New Component.... Add an AdapterForm. Select the AdapterForm1, right-click on it, and select New Component... to add an AdapterFieldGroup. Select the AdapterFieldGroup1 and in the Object Inspector set the Adapter property to be LoginFormAdapter1. Now, right click on the AdapterForm1 and select New Component... to add an AdapterCommandGroup. Select the AdapterCommandGroup1 and set the DisplayComponent property to be AdapterFieldGroup1. Next, right click on the AdapterForm1 and from New Component... select AdapterErrorList. Then, select the AdapterErrorList1 and set the Adapter to be the LoginFormAdapter1. If you want errors to appear on top of everything else, drag the AdapterErrorList1 to the top of the list.
We now have to make the associated HTML page display the information on the LoginFormAdapter1. To do this, select the LoginMod.html file from the tabs at the bottom of the Code Editor. Before the closing BODY tag, add:
<#SERVERSCRIPT> If you take a look at the preview, you should see the edit boxes to login with.
Now, add a TIdPop3 component from the Indy Clients tab to the ViewMailModule and rename it to be popEmail. Set the Host property of the popEmail component to be your email server's POP3 hostname (such as, mail.your-host.com). Under the function ViewMailModule in the interface, add the following constants:
const
cUserName = 'UserName';
cPassword = 'Password';
Then, in the LoginModule's LoginFormAdapter1, double-click on the OnLogin event and add the code in the event handler:
procedure TLoginModule.LoginFormAdapter1Login(Sender: TObject;
UserID: Variant);
begin
// Try to log them in and verify the username/pass are correct
with ViewMailModule do
begin
if popEmail.Connected then
begin
try
popEmail.Disconnect;
except
end;
end;
if AdaptUserName.ActionValue <> nil then
popEmail.UserId := AdaptUserName.ActionValue.Values[0]
else
raise Exception.Create('No user name passed from web page!');
if AdaptPassword.ActionValue <> nil then
popEmail.Password := AdaptPassword.ActionValue.Values[0]
else
raise Exception.Create('No password passed from web page!');
// Try to connect, which will test the username and password
// against the mail server
try
popEmail.Connect;
finally
try
popEmail.Disconnect;
except
end;
end;
// On a successful login, save the users information in a session.
WebContext.Session.Values[cUserName] := popEmail.UserId;
WebContext.Session.Values[cPassword] := popEmail.Password;
end;
end;
You will have to add ViewMailMod to the uses list to make it compile.
Now that the login page is completed, we need to tell the application to use this login page. Select the EndUserSessionAdapter from the MainModule and in the Object Inspector set the LoginPage property to be LoginModule (you will have to type in the string).
Next, open back up the ViewMailModule. Add a TIdMessage (from the Indy Misc tab) to the ViewMailModule and name it msgEmail. We want to expose the email headers to the web page and use Javascript to iterate through all the messages. First, lets tackle the iteration part. Add the following to the private section of the TViewMailModule:
private
{ Private declarations }
FMessageId: Integer;
FMessageCount: Integer;
FMessageSubject: string;
FMessageFromName: string;
FMessageFromEmail: string;
FMessageDate: string;
FMessageSize: Integer;
These private data fields will allow us to keep track of what message we are on, and all the associated items in that message.
procedure TViewMailModule.EmailAdapterIterateRecords(Sender: TObject;
Action: TIteratorMethod; var EOF: Boolean);
procedure FillInMessageInfo;
begin
msgEmail.Clear;
popEmail.RetrieveHeader(FMessageId, msgEmail);
FMessageSubject := msgEmail.Subject;
FMessageFromName := msgEmail.From.Name;
FMessageFromEmail := msgEmail.From.Address;
FMessageDate := DateTimeToStr(msgEmail.Date);
end;
begin
if Action = itStart then
begin
// Connect to the server, using the user name and password stored
// in the session.
if popEmail.Connected then
begin
try
popEmail.Disconnect
except
end;
end;
popEmail.UserId := WebContext.Session.Values[cUserName];
popEmail.Password := WebContext.Session.Values[cPassword];
popEmail.Connect;
// Find out the message count
FMessageCount := popEmail.CheckMessages;
FMessageId := 0;
EOF := FMessageCount = 0;
end
else if Action = itNext then
begin
Inc(FMessageId);
EOF := FMessageId >= FMessageCount;
end
else
EOF := True;
if not EOF then
FillInMessageInfo;
end;
Notice how a different code is done depending on what Action the iterator is going through.
procedure TViewMailModule.MessageIdGetValue(Sender: TObject;
var Value: Variant);
begin
Value := FMessageId;
end;
Repeat for the other AdapterFields, with using the respective private data member:
procedure TViewMailModule.MessageSubjectGetValue(Sender: TObject;
var Value: Variant);
begin
Value := FMessageSubject;
end;
procedure TViewMailModule.MessageFromNameGetValue(Sender: TObject;
var Value: Variant);
begin
Value := FMessageFromName;
end;
procedure TViewMailModule.MessageDateGetValue(Sender: TObject;
var Value: Variant);
begin
Value := FMessageDate;
end;
procedure TViewMailModule.MessageFromEmailGetValue(Sender: TObject;
var Value: Variant);
begin
Value := FMessageFromEmail;
end;
procedure TViewMailModule.MessageSizeGetValue(Sender: TObject;
var Value: Variant);
begin
Value := FMessageSize;
end;
procedure TViewMailModule.MessageCountGetValue(Sender: TObject;
var Value: Variant);
begin
Value := FMessageCount;
end;
The AdapterFields are what allows you to access your data with Javascript from an HTML page.
Now, we need the ability to allow the user to view a particular email message. We will create an Action to do this. Select the EmailAdapter and click on the Actions property in the Object Inspector to bring up the Actions list editor. Click the New Item button and add a new AdapterAction. Rename it to GetEmail. We need to add a parameter of what email message that the GetEmail action should be associated with. In the Object Inspector's Events for the GetEmail AdapterAction, double-click on the OnGetParams event and add the following code:
procedure TViewMailModule.GetEmailGetParams(Sender: TObject;
Params: TStrings);
begin
Params.Values['id'] := IntToStr(FMessageId);
end;
Now, we need to specify what happens when this action is invoked. Double-click on the OnExecute event and add the following code:
procedure TViewMailModule.GetEmailExecute(Sender: TObject;
Params: TStrings);
begin
if (Params.Values['id'] <> '') and (WebContext.EndUser.LoggedIn) then
DispatchPageName(EmailMessageModule.Name, Response, []);
end;
The DispatchPageName will return a Response with the EmailMessageModule page. Since the EmailMessageModule is not Published, this will be the only way to access this page. To make this compile, you will have to add EmailMessageMod and WebDisp to your uses list for the ViewMailMod unit.
Now, we want to add Javascript that interacts with the EmailAdapter. Open the ViewMailMod.html page by clicking on the tab at the bottom of the Code Editor. We can write javascript accessing any TAdapter's Data or Actions with things such as: AdapterName.AdapterFieldName.Value or AdapterName.AdapterFieldName.DisplayText. Select the ViewMailMod.html tab at the bottom of the code editor and add the following HTML just above the closing BODY tag:
<% // Display server-side errors that may have occured. errors = new Enumerator(EmailAdapter.Errors) if (!errors.atEnd()) Response.Write('<p>The following error(s) happened:'); for (; !errors.atEnd(); errors.moveNext()) Response.Write("<li>" + errors.item().Message) // The call to the EmailAdapter's records starts the iteration, // and fills in the MessageCount emails = new Enumerator(EmailAdapter.Records); if (EmailAdapter.MessageCount.Value > 0) { var i = 0; %> <p>You have <%=EmailAdapter.MessageCount.DisplayText%> messages.<br> <center> <table width="90%"> <tr> <td bgcolor="#DCDCDC"><font face="Arial, Helvetica" size="2"><b>Id</b></font></td> <td bgcolor="#DCDCDC"><font face="Arial, Helvetica" size="2"><b>Message</b></font></td> <td bgcolor="#DCDCDC"><font face="Arial, Helvetica" size="2"><b>Date</b></font></td> <td bgcolor="#DCDCDC"><font face="Arial, Helvetica" size="2"><b>Size</b></font></td> </tr> <% for (;!emails.atEnd(); emails.moveNext()) { Response.Write('<tr>'); var color; // Color every other row if (i % 2 == 0) color = 'bgcolor="#DCDCDC"'; else color = ''; Response.Write('<td ' + color + '>' + EmailAdapter.MessageId.DisplayText + '</td>\n'); // For the message subject, create a link to the EmailMessageModule // to display that message. Response.Write('<td ' + color + '><b>Subject:</b> ' + '<a href="' + EmailAdapter.GetEmail.AsHREF + '">' + EmailAdapter.MessageSubject.DisplayText + '</a><br>'); Response.Write( '<b>From:</b> ' + EmailAdapter.MessageFromName.DisplayText + ' <<a href="mailto:' + EmailAdapter.MessageFromEmail.DisplayText + '">' + EmailAdapter.MessageFromEmail.DisplayText + '</a>>' + '</td>\n'); Response.Write('<td ' + color + '>' + EmailAdapter.MessageDate.DisplayText + '</td>\n'); Response.Write('<td ' + color + '>' + EmailAdapter.MessageSize.DisplayText + '</td>\n'); Response.Write('</tr>'); i++; } %> </table> </center> <% } else Response.Write('<P>You have no messages waiting.<br>'); %>Finally, we need to work on the EmailMessageModule to allow the user to display a particular message. Add a TAdapter to the EmailMessageModule and name it EmailMessage. Double-click on the EmailMessage Adapter and add the following new AdapterFields: StartMessage, MessageId, MessageFromName, MessageFromEmail, MessageSubject, MessageDate and MessageBody. Add the following to the private section of the EmailMessageModule:
{ Private declarations }
FMessageId: Integer;
FMessageSubject: string;
FMessageFromName: string;
FMessageFromEmail: string;
FMessageDate: string;
FMessageBody: string;
Select the StartMessage AdapterField and in the OnGetValue Event add the following code:
procedure TEmailMessageModule.StartMessageGetValue(Sender: TObject;
var Value: Variant);
begin
Value := False; // Failure
if Request.QueryFields.Values['id'] <> '' then
begin
FMessageId := StrToInt(Request.QueryFields.Values['id']);
with ViewMailModule do
begin
popEmail.UserId := WebContext.Session.Values[cUserName];
popEmail.Password := WebContext.Session.Values[cPassword];
popEmail.Connect;
try
msgEmail.Clear;
msgEMail.NoDecode := True;
popEmail.Retrieve(FMessageId, msgEmail);
FMessageSubject := msgEmail.Subject;
FMessageFromName := msgEmail.From.Name;
FMessageFromEmail := msgEmail.From.Address;
FMessageDate := DateTimeToStr(msgEmail.Date);
FMessageBody := msgEmail.Body.Text;
Value := True;
finally
try
popEmail.Disconnect;
except
end
end;
end;
end
else
raise Exception.Create('No valid message id passed to the EmailMessageModule');
end;
Be sure to add ViewMailMod to your lower uses list (in the implementation section) of the EmailMessageMod to make the code compile. Adding it to the upper uses list (the interface section) may give you a "circular reference" error.
procedure TEmailMessageModule.MessageIdGetValue(Sender: TObject;
var Value: Variant);
begin
Value := FMessageId;
end;
procedure TEmailMessageModule.MessageFromNameGetValue(Sender: TObject;
var Value: Variant);
begin
Value := FMessageFromName;
end;
procedure TEmailMessageModule.MessageFromEmailGetValue(Sender: TObject;
var Value: Variant);
begin
Value := FMessageFromEmail;
end;
procedure TEmailMessageModule.MessageSubjectGetValue(Sender: TObject;
var Value: Variant);
begin
Value := FMessageSubject;
end;
procedure TEmailMessageModule.MessageDateGetValue(Sender: TObject;
var Value: Variant);
begin
Value := FMessageDate;
end;
procedure TEmailMessageModule.MessageBodyGetValue(Sender: TObject;
var Value: Variant);
begin
Value := FMessageBody;
end;
Finally, we have to add HTML to the EmailMessageMod.html page, above the closing BODY tag:<P> <% // First, see if we can view the message if (EmailMessage.StartMessage.Value) { %> <table width="100%"> <tr> <td bgcolor="#DCDCDC"><b>From:</b> <%=EmailMessage.MessageFromName.DisplayText%> <a href="mailto:<%=EmailMessage.MessageFromEmail.DisplayText%>" ><%=EmailMessage.MessageFromEmail.DisplayText%></a> </td> <td bgcolor="#DCDCDC"><b>Date:</b> <%=EmailMessage.MessageDate.DisplayText%></td> </tr> <tr> <td bgcolor="#DCDCDC" colspan="2"><b>Subject:</b> <%=EmailMessage.MessageSubject.DisplayText%></td> </tr> <tr> <td colspan="2"> <pre> <%=EmailMessage.MessageBody.DisplayText%> </pre> </td> </tr> </table> <% } else Response.Write('Can not display the given email message id<br>'); errors = new Enumerator(EmailMessage.Errors) if (!errors.atEnd()) Response.Write('<p>The following error(s) happened:'); for (; !errors.atEnd(); errors.moveNext()) Response.Write("<li>" + errors.item().Message) %>
Run the application by pressing F9. Start the Web App Debugger and click on the default URL. Select WebMailApp.WebMail from the list. You should now be able to login to your email account and check your messages. Note that the WebMailApp.exe must stay running to keep the session active.