Mega Code Archive

 
Categories / Delphi / Ide Indy
 

Hotmail Delphi Style!

Title: Hotmail---Delphi Style! Question: This is the second of a three-part series showing how to access Hotmail through the HTTPMail "protocol" that OE uses using Delphi. This part builds an application that allows the user to get and send messages via Hotmail. Answer: Previously published in Hardcore Delphi (Pinnacle Publishing-Feb/Mar 2004) In last month's issue, we took a look at how Outlook Express communicates with Hotmail using HTTP and WebDAV. In this article, we are going to build a sample project to do this in Delphi. As stated in the first part of this series, Microsoft continues to say that its WebDAV functionality is a beta product and it has never been publicly documented nor added this functionality to other products such as Outlook. Overview In my searches for information on Microsoft's implementation of WebDAV for Hotmail, I came upon several Hotmail WebDAV projects but most of them were written in java or C++. I did, however, find one that was written in Visual Basic and the application in this article is based upon that project. This sample application is a port of the Visual Basic project provided by Julian Benjamin that was included in the HTTPMail Sourceforge project by Dave Dunkin. However, I am adding much functionality that does not exist in the VB project. The sample project uses Microsoft's XML and XMLHTTP controls to accomplish communicating to Hotmail via HTTP and WebDAV. An effort to port this functionality to a Delphi custom component is underway. Ian Boyd has already done a conversion of the VB Control to Delphi but his is a straight port and does not include much of the functionality that I am adding in this article. Beginnings The first thing we do is import the XML Type Library. This TypeLib includes both the XML and XMLHTTP interfaces that we will be using. In the Delphi IDE, we select Project-Import Type Library from the menu and select Microsoft XML in the list, select the package we want to install into and click Install. Note that you may not see version 3.0 of MSXML in your list unless you have IE5 or later installed. Version 3 of the MSXML DLL was used in the making of this sample application and other versions may be incompatible. The IXMLHTTPRequest and IXMLDOMDocument objects are created in the FormCreate and destroyed in the FormDestroy, as they are used throughout the life of the app. See listing 1. We are also creating the FolderList and MailList TList objects to hold Folder and Mail Message information. Listing 1. FormCreate procedure TfmHotmailForm.FormCreate ( Sender: TObject ); begin // Create the XML and XMLHTTP Objects we are going to use oXMLDoc := CreateOleObject('MSXML2.DOMDocument') as IXMLDOMDocument; oXMLHTTP := CreateOleObject('MSXML2.XMLHTTP.3.0') as IXMLHTTPRequest; FFolderList := TList.Create; FMailList := TList.Create; // Initialize the FolderURLs to blank InitProperties; end; Once we have the Type Library installed, we create a new project. Drop a TTreeview, TListView, TToolbar, TActionList, TMainMenu and TWebBrowser on your form (See Figure 1). The Treeview will hold the Folder List, the Listview, the messages list and the TWebBrowser is to show the message contents. We are adding menu items and Actions to Mark messages Read and Unread, Move and Delete messages, Empty the Trash Folder and to Create and Delete Folders. We are also adding a menu item to add the capability of composing and sending messages via Hotmail, "New Message.... Figure 1. Connecting The actConnect Action calls the Connect function and if successful, calls the RetrieveFolderURLs, ParseFolderInfo, DisplayMail and ParseFolders procedures. The Connect function in Listing 2 connects to Hotmail's servers and the IXMLHTTPRequest object does the authentication internally. Because of security patches that Microsoft has issued, the services.msn.com domain needs to be added to the list of Trusted sites for IE. If this is not done, XMLHTTP hangs on the send and then will timeout with an OLE error 0x800C0008. There is a function to check to see if the site is, in fact, in the users IE Trusted Zone. If it is not, the user is prompted to add it to run the demo properly. Even with this in mind, we still have to call oXMLHTTP.Send twice to get it to work correctly. Listing 2. function TfmHotmailForm.Connect: Boolean; var UserPassDlg: TfmUserPassDlg; begin Result := False; Result := CheckTrustedDomain; if not Result then begin FHotmailError := 'The site ' + HMTrustedHost + ' is not in the IE Trusted Zone'+ #13 + 'Please correct this for '+ 'this demo to work properly'; Result := False; Exit; end; if FUserName = '' then begin UserPassDlg := TfmUserPassDlg.Create(Self); try if UserPassDlg.Showmodal = mrOK then begin FUsername := Trim(UserPassDlg.UserName); FPassword := Trim(UserPassDlg.Password); end else begin Result := False; Exit; end; finally UserPassDlg.Free; end; if FUserName = '' then Exit; end; Screen.Cursor := crHourglass; try oXMLHTTP.open('PROPFIND', HotmailURL, False, FUserName, FPassword); oXMLHTTP.setRequestHeader('PROPFIND', QUERY_FOLDERS); oXMLHTTP.setRequestHeader('Content-Type', 'text/xml'); oXMLHTTP.setRequestHeader('Depth', '0'); oXMLHTTP.setRequestHeader('User-Agent', USER_AGENT); oXMLHTTP.send(EmptyParam); // // This nees to be done twice to get the // required result. // oXMLHTTP.open('PROPFIND', HotmailURL, False, FUserName, FPassword); oXMLHTTP.setRequestHeader('PROPFIND', QUERY_FOLDERS); oXMLHTTP.setRequestHeader('Content-Type', 'text/xml'); oXMLHTTP.setRequestHeader('Depth', '0'); oXMLHTTP.setRequestHeader('User-Agent', USER_AGENT); oXMLHTTP.send(EmptyParam); // The ResponseText will be parsed for Folder // Info, etc. FResponseText := Trim(oXMLHTTP.ResponseText); FResponseHdrs := oXMLHTTP.getAllResponseHeaders; FHotmailError := oXMLHTTP.getResponseHeader('X-Dav-Error'); // We actually got something back from the server. Result := FResponseText ''; if Result then fmHotmailForm.Caption := fmHotmailForm.Caption + ' {connected as: '+ FUserName + '}'; finally Screen.Cursor := crDefault; end; end; Retrieving Folder and Message Info The RetrieveFolderURLs procedure takes the ResponseText that is returned from the IXMLHTTPRequest object and loads it into the XMLDocument then parses it to obtain the FolderURL variables. In ParseFolderInfo, we are loading the XML returned by GetAllFolderInfo, which takes the RootFolderURL, does a PROPFIND to get the sub-folder URLs, into the XMLDocument. We then iterate through the nodes of the XMLDocument and create a TFolderInfo object for each folder and load it into the FolderList object. The Inbox is then selected in the Treeview. Listing 3. function TfmHotmailForm.RetrieveFolderURLs: Boolean; var oResponseNode : IXMLDOMNode; oHREFNode : IXMLDOMNode; oPropStatNode : IXMLDOMNode; oPropNode : IXMLDOMNode; oElement : IXMLDOMElement; oNodeList : IXMLDOMNodeList; begin oXMLDoc.LoadXML(FResponseText); Result := oXMLDoc.childNodes.length 0; if Result then begin oElement := oXMLDoc.documentElement; oNodeList := oElement.childNodes; oResponseNode := oNodeList.Item[0]; oHREFNode := oResponseNode.childNodes.Item[0]; FBaseRefURL := oHREFNode.text; oPropStatNode := oResponseNode.childNodes.Item[1]; oPropNode := oPropStatNode.childNodes.Item[0]; FContactsURL := oPropNode.childNodes.Item[0].text; FInboxURL := oPropNode.childNodes.Item[1].text; FOutboxURL := oPropNode.childNodes.Item[2].text; FSentItemsURL := oPropNode.childNodes.Item[3].text; FTrashURL := oPropNode.childNodes.Item[4].text; FRootFolderURL := oPropNode.childNodes.Item[5].text; end else begin FHotmailError := 'Invalid response from server'; ShowMessage(FHotmailError); end; end; procedure TfmHotmailForm.ParseFolderInfo; var fInfo : TFolderInfo; oNodelist : IXMLDOMNodeList; i : Integer; oFolderNode : IXMLDOMNode; oNode : IXMLDOMNode; begin FFolderList.Clear; oXMLDoc.loadXML(GetAllFolderInfo); oNodelist := oXMLDoc.selectNodes('//D:response'); for i := 0 to oNodelist.length -1 do begin fInfo := TFolderInfo.Create; oFolderNode := oNodelist.Item[i]; oNode := oFolderNode.childNodes[0]; fInfo.Url := oNode.text; oNode := oFolderNode.childNodes[1].childNodes[0]; fInfo.Name := oNode.childNodes[1].text; // Because the nodes for these folders are // different, we need to get the msgcount and // unreadcount from different child nodes. if (fInfo.Name = 'msnpromo') or (fInfo.Name = 'inbox') or (fInfo.Name = 'sentitems') or (fInfo.Name = 'deleteditems') then begin fInfo.UnreadCount := StrToInt(oNode.childNodes[4].text); fInfo.MsgCount := StrToInt(oNode.childNodes[5].text); end else begin fInfo.UnreadCount := StrToInt(oNode.childNodes[5].text); fInfo.MsgCount := StrToInt(oNode.childNodes[6].text); end; // These folders do not have a displayname element // so we modify them as they are added. // In the newer version of Hotmail, this folder // no longer exists. { if Pos('msnpromo', fInfo.Name) 0 then fInfo.Name := StringReplace(fInfo.Name, 'msnpromo', 'MSN Announcements', [rfReplaceAll]); } if Pos('inbox', fInfo.Name) 0 then fInfo.Name := StringReplace(fInfo.Name, 'inbox', 'Inbox', [rfReplaceAll]); if Pos('sentitems', fInfo.Name) 0 then fInfo.Name := StringReplace(fInfo.Name, 'sentitems', 'Sent Items', [rfReplaceAll]); if Pos('deleteditems', fInfo.Name) 0 then fInfo.Name := StringReplace(fInfo.Name, 'deleteditems', 'Deleted Items', [rfReplaceAll]); // if you want to leave it as Junk Mail .vs Bulk // Mail, remove these two lines if Pos('Junk E-Mail', fInfo.Name) 0 then fInfo.Name := StringReplace(fInfo.Name, 'Junk E-Mail', 'Bulk Mail', [rfReplaceAll]); if (Pos('Junk E-Mail', FInfo.Name) 0) then FJunkURL := fInfo.Url; FFolderList.Add(fInfo); end; end; procedure TfmHotmailForm.DisplayMail; var i, x : Integer; lItem : TListItem; sentitems: Boolean; MailInfoItem: TMailInfo; begin MessageView.Items.Clear; if FMailList.Count 0 then begin if FolderView.Selected nil then begin i := FolderView.Selected.AbsoluteIndex; Mailview.Navigate('about:blank'); lblSubject.Caption := 'Subject: '; lblFrom.Caption := 'From: '; if Lowercase(FolderView.Items[i].Text) = 'sentitems' then begin MessageView.Columns[1].Caption := 'To'; sentitems := True; end else begin MessageView.Columns[1].Caption := 'From'; sentitems := False; end; for x := 0 to FMailList.Count-1 do begin MailInfoItem := TMailInfo(FMailList.Items[x]); lItem := MessageView.Items.Add; lItem.Checked := False; lItem.Data := MailInfoItem lItem.Caption := MailInfoItem.Subject; if sentitems then lItem.SubItems.Add(MailInfoItem.Recipient) else lItem.SubItems.Add(MailInfoItem.From); lItem.SubItems.Add(MailInfoItem.Date); lItem.SubItems.Add(MailInfoItem.Length); end; MessageView.RowSelect := True; end; end; end; When a Folder node is clicked in the treeview, the Folder URL is retrieved from the TFolderInfo object and ParseMailboxInfo gets the Messages List from the selected folder, creates a TMailInfo Object for each message, adds it to the MailList, DisplayMail iterates through the MailList and fills the Messages Listview with the info. When an item in the Messages Listview is clicked, we obtain the URL for the message from the MailInfo object and Navigate to it in the TWebBrowser message viewer. Listing 4. function TfmHotmailForm.RetrieveFolderURLs: Boolean; var oResponseNode : IXMLDOMNode; oHREFNode : IXMLDOMNode; oPropStatNode : IXMLDOMNode; oPropNode : IXMLDOMNode; oElement : IXMLDOMElement; oNodeList : IXMLDOMNodeList; begin oXMLDoc.LoadXML(FResponseText); Result := oXMLDoc.childNodes.length 0; if Result then begin oElement := oXMLDoc.documentElement; oNodeList := oElement.childNodes; oResponseNode := oNodeList.Item[0]; oHREFNode := oResponseNode.childNodes.Item[0]; FBaseRefURL := oHREFNode.text; oPropStatNode := oResponseNode.childNodes.Item[1]; oPropNode := oPropStatNode.childNodes.Item[0]; FContactsURL := oPropNode.childNodes.Item[0].text; FInboxURL := oPropNode.childNodes.Item[1].text; FOutboxURL := oPropNode.childNodes.Item[2].text; FSentItemsURL := oPropNode.childNodes.Item[3].text; FTrashURL := oPropNode.childNodes.Item[4].text; FRootFolderURL := oPropNode.childNodes.Item[5].text; end else begin FHotmailError := 'Invalid response from server'; ShowMessage(FHotmailError); end; end; procedure TfmHotmailForm.ParseFolderInfo; var fInfo : TFolderInfo; oNodelist : IXMLDOMNodeList; i : Integer; oFolderNode : IXMLDOMNode; oNode : IXMLDOMNode; begin FFolderList.Clear; oXMLDoc.loadXML(GetAllFolderInfo); oNodelist := oXMLDoc.selectNodes('//D:response'); for i := 0 to oNodelist.length -1 do begin fInfo := TFolderInfo.Create; oFolderNode := oNodelist.Item[i]; oNode := oFolderNode.childNodes[0]; fInfo.Url := oNode.text; oNode := oFolderNode.childNodes[1].childNodes[0]; fInfo.Name := oNode.childNodes[1].text; // Because the nodes for these folders are // different, we need to get the msgcount and // unreadcount from different child nodes. if (fInfo.Name = 'msnpromo') or (fInfo.Name = 'inbox') or (fInfo.Name = 'sentitems') or (fInfo.Name = 'deleteditems') then begin fInfo.UnreadCount := StrToInt(oNode.childNodes[4].text); fInfo.MsgCount := StrToInt(oNode.childNodes[5].text); end else begin fInfo.UnreadCount := StrToInt(oNode.childNodes[5].text); fInfo.MsgCount := StrToInt(oNode.childNodes[6].text); end; // These folders do not have a displayname element // so we modify them as they are added. // In the newer version of Hotmail, this folder // no longer exists. { if Pos('msnpromo', fInfo.Name) 0 then fInfo.Name := StringReplace(fInfo.Name, 'msnpromo', 'MSN Announcements', [rfReplaceAll]); } if Pos('inbox', fInfo.Name) 0 then fInfo.Name := StringReplace(fInfo.Name, 'inbox', 'Inbox', [rfReplaceAll]); if Pos('sentitems', fInfo.Name) 0 then fInfo.Name := StringReplace(fInfo.Name, 'sentitems', 'Sent Items', [rfReplaceAll]); if Pos('deleteditems', fInfo.Name) 0 then fInfo.Name := StringReplace(fInfo.Name, 'deleteditems', 'Deleted Items', [rfReplaceAll]); // if you want to leave it as Junk Mail .vs Bulk // Mail, remove these two lines if Pos('Junk E-Mail', fInfo.Name) 0 then fInfo.Name := StringReplace(fInfo.Name, 'Junk E-Mail', 'Bulk Mail', [rfReplaceAll]); if (Pos('Junk E-Mail', FInfo.Name) 0) then FJunkURL := fInfo.Url; FFolderList.Add(fInfo); end; end; procedure TfmHotmailForm.DisplayMail; var i, x : Integer; lItem : TListItem; sentitems: Boolean; MailInfoItem: TMailInfo; begin MessageView.Items.Clear; if FMailList.Count 0 then begin if FolderView.Selected nil then begin i := FolderView.Selected.AbsoluteIndex; Mailview.Navigate('about:blank'); lblSubject.Caption := 'Subject: '; lblFrom.Caption := 'From: '; if Lowercase(FolderView.Items[i].Text) = 'sentitems' then begin MessageView.Columns[1].Caption := 'To'; sentitems := True; end else begin MessageView.Columns[1].Caption := 'From'; sentitems := False; end; for x := 0 to FMailList.Count-1 do begin MailInfoItem := TMailInfo(FMailList.Items[x]); lItem := MessageView.Items.Add; lItem.Checked := False; lItem.Data := MailInfoItem lItem.Caption := MailInfoItem.Subject; if sentitems then lItem.SubItems.Add(MailInfoItem.Recipient) else lItem.SubItems.Add(MailInfoItem.From); lItem.SubItems.Add(MailInfoItem.Date); lItem.SubItems.Add(MailInfoItem.Length); end; MessageView.RowSelect := True; end; end; end; Deleting/Moving Messages and Emptying the Trash Folder The process for deleting and moving message(s) is essentially the same, there is just a different destination URL for the selected message(s). {Listing 5} When a message is moved, a folder must be selected to move it to and a folder selection dialog has been provided in the project. When deleting messages, the Trash folder is the destination URL. In moving deleting or moving multiple messages, the program builds a list of messages to delete/move and the list of messages are sent in the XML in a BMOVE (bulk move) XMLHTTP call. The original VB project did not have the capability to empty the Trash Folder. This functionality was added based upon study of the HTTPMail log file and the code that was passed between Outlook Express and the Hotmail servers. When emptying the Trash folder, a list of messages in the Trash Folder is built and passed in a BDELETE (bulk delete) XMLHTTP call. Note that BDELETE cannot be used on any other folder except the Trash folder. Listing 5. procedure TfmHotmailForm.BuildMailList ( var AMailList: TStringList ); var i, iLastSlash : Integer; sKey : String; begin // Adds messageids to the stringlist only for i := 0 to MessageView.Items.Count-1 do begin if (MessageView.Items[i].Checked) or (MessageView.Items[i].Selected)then begin sKey := TMailInfo(MessageView.Items[i].Data).URL; iLastSlash := RatChar(sKey, '/'); sKey := Copy(sKey, iLastSlash + 1, Length(sKey)); AMailList.Add(sKey); end; end; end; procedure fmHotmailForm.actDeleteMessagesExecute ( Sender: TObject ); var i : Integer; sCurrentFolder : String; slMailList : TStringList; FolderListItem: TFolderInfo; begin // Delete the checked messages // Get the currently selected folder i := FolderView.Selected.AbsoluteIndex-1; FolderListItem := TFolderInfo(FFolderList.Items[i]); sCurrentFolder := FolderListItem.URL; slMailList := TStringList.Create; try // Adds all checked messageids (URLS) to the // slMailList stringlist BuildMailList(slMailList); // move checked messages to the Trash Folder. MoveMail(slMailList, sCurrentFolder, FTrashURL); finally slMailList.Free; end; end; function TfmHotmailForm.MoveMail ( uMail: TStringList ; mSrcFolder , mDestFolder: String ): String; var oElement: IXMLDOMElement; oRoot : IXMLDOMElement; sMail : String; i : Integer; begin // Moves mail from mSrcFolder to mDestFolder oXMLDoc.loadXML(MOVE_MAIL); oRoot := oXMLDoc.documentElement; // Add each checked message URl to the XML Document for i := 0 to uMail.Count -1 do begin oElement := oXMLDoc.createElement('D:href'); oElement.Text := uMail.Strings[i]; oRoot.childNodes[0].appendChild(oElement); end; sMail := oXMLDoc.xml; oXMLHTTP.open('BMOVE', mSrcFolder, False, FUsername, FPassword); oXMLHTTP.setRequestHeader('User-Agent', USER_AGENT); oXMLHTTP.setRequestHeader('Destination', mDestFolder); oXMLHTTP.setRequestHeader('Content-Type', CONTENT_TYPE); oXMLHTTP.send(sMail); Result := oXMLHTTP.getAllResponseHeaders; FResponseText := oXMLHTTP.ResponseText; FHotmailError := oXMLHTTP.getResponseHeader('X-Dav-Error'); ParseFolderInfo; ParseMailboxInfo(mSrcFolder); DisplayMail; end; procedure TfmHotmailForm.actMoveMessagesExecute ( Sender : TObject ); var sCurrentFolder: String; sDestFolder : String; slMailList : TStringList; FolderSelect : TfmFolderSelect; i: Integer; begin // Move checked messages to the selected folder FolderSelect := TfmFolderSelect.Create(Self); try FolderSelect.sDestFolder := sDestFolder; for i := 0 to FFolderList.Count-1 do begin FolderSelect.ListBox1.Items.Add(TFolderInfo(FFolderList.Items[i]).Name); end; FolderSelect.ShowModal; finally sDestFolder := FolderSelect.sDestFolder; FolderSelect.Free; if sDestFolder '' then begin for i := 0 to FFolderList.Count-1 do begin if Pos(sDestFolder, TFolderInfo(FFolderList.Items[i]).Name) 0 then begin sDestFolder := TFolderInfo(FFolderList.Items[i]).URL; Break; end; end; i := FolderView.Selected.AbsoluteIndex-1; sCurrentFolder := TFolderInfo(FFolderList.Items[i]).URL; slMailList := TStringList.Create; try BuildMailList(slMailList); MoveMail(slMailList, sCurrentFolder, sDestFolder); finally slMailList.Free; end; end; end; end; Marking Message Read/Unread When marking messages read or unread, a PROPPATCH method is called to modify the Read property of the message in question. If more than one message is checked in the listview, a list is made of the checked messages and if there are more than one message in the list, the BPROPPATCH method is used else the PROPPATCH method is called for the lone message in the list as shown in Listing 6. Listing 6. procedure TfmHotmailForm.FlagMailRead ( AMessages: TStrings ; currentfolder: String ); var oElement : IXMLDOMElement; oRoot : IXMLDOMElement; sMessages : String; i : Integer; begin if AMessages.Count 1 then begin // Bulk Marks messages read oXMLDoc.loadXML(FLAG_MAIL_READ); oRoot := oXMLDoc.documentElement; for i := 0 to AMessages.Count -1 do begin oElement := oXMLDoc.createElement('D:href'); oElement.Text := AMessages[i]; oRoot.childNodes[0].appendChild(oElement); end; sMessages := oXMLDoc.xml; oXMLHTTP.open('BPROPPATCH', currentfolder, False, FUsername, FPassword); oXMLHTTP.setRequestHeader('User-Agent', USER_AGENT); oXMLHTTP.send(sMessages); end else begin oXMLHTTP.open('PROPPATCH', AMessages[0], False, FUsername, FPassword); oXMLHTTP.setRequestHeader('User-Agent', USER_AGENT); oXMLHTTP.send(FLAG_MAIL_READ); end; FResponseHdrs := oXMLHTTP.getAllResponseHeaders; FResponseText := oXMLHTTP.ResponseText; FHotmailError := oXMLHTTP.getResponseHeader('X-Dav-Error'); // Now refresh the current folder ParseMailboxInfo(currentfolder); DisplayMail; end; procedure TfmHotmailForm.FlagMailUnread ( AMessages: TStrings ; currentfolder: String ); var oElement : IXMLDOMElement; oRoot : IXMLDOMElement; sMessages : String; i : Integer; begin if AMessages.Count 1 then begin // Bulk Marks messages unread oXMLDoc.loadXML(FLAG_MAIL_UNREAD); oRoot := oXMLDoc.documentElement; for i := 0 to AMessages.Count -1 do begin oElement := oXMLDoc.createElement('D:href'); oElement.Text := AMessages[i]; oRoot.childNodes[0].appendChild(oElement); end; sMessages := oXMLDoc.xml; oXMLHTTP.open('BPROPPATCH', currentfolder, False, FUsername, FPassword); oXMLHTTP.setRequestHeader('User-Agent', USER_AGENT); oXMLHTTP.send(sMessages); end else begin oXMLHTTP.open('PROPPATCH', AMessages[0], False, FUsername, FPassword); oXMLHTTP.setRequestHeader('User-Agent', USER_AGENT); oXMLHTTP.send(FLAG_MAIL_UNREAD); end; FResponseHdrs := oXMLHTTP.getAllResponseHeaders; FResponseText := oXMLHTTP.ResponseText; FHotmailError := oXMLHTTP.getResponseHeader('X-Dav-Error'); ParseFolderInfo; // Now refresh the current folder ParseMailboxInfo(currentfolder); DisplayMail; end; procedure TfmHotmailForm.actMarkReadExecute ( Sender : TObject ); var i : Integer; sMsgCount : String; sUnReadCount : String; sFolderInfo : String; sCurrentFolder: String; slMailList : TStringList; FolderListItem: TFolderInfo; begin // Mark checked items Read MessageView.Items.BeginUpdate; i := FolderView.Selected.AbsoluteIndex-1; FolderListItem := TFolderInfo(FFolderList.Items[i]); sCurrentFolder := FolderListItem.URL; slMailList := TStringList.Create; try BuildMailURLList(slMailList); sFolderInfo := FolderListItem.fName; sMsgCount := IntToStr(FolderListItem.MsgCount); sUnReadCount := IntToStr(FolderListItem.UnreadCount); sFolderInfo := sFolderInfo + '(' + sMsgCount + ':' + sUnreadCount + ')'; FolderView.Selected.Text := sFolderInfo; for i := 0 to slMailList.Count-1 do begin FlagMailRead(slMailList.Strings[i], sCurrentFolder); end; finally slMailList.Free; end; ParseFolderInfo; // Now refresh the current folder ParseMailboxInfo(sCurrentFolder); Mailview.Navigate('about:blank'); lblSubject.Caption := 'Subject: '; lblFrom.Caption := 'From: '; DisplayMail; MessageView.Items.EndUpdate; end; procedure TfmHotmailForm.actMarkUnreadExecute ( Sender : TObject ); var i : Integer; sMsgCount : String; sUnReadCount : String; sFolderInfo : String; sCurrentFolder: String; slMailList : TStringList; FolderListItem: TFolderInfo; begin // Mark checked items Unread MessageView.Items.BeginUpdate; i := FolderView.Selected.AbsoluteIndex-1; FolderListItem := TFolderInfo(FFolderList.Items[i]); sCurrentFolder := FolderListItem.URL; slMailList := TStringList.Create; try BuildMailURLList(slMailList); sFolderInfo := FolderListItem.fName; sMsgCount := IntToStr(FolderListItem.MsgCount); sUnReadCount := IntToStr(FolderListItem.UnreadCount); sFolderInfo := sFolderInfo + '(' + sMsgCount + ':' + sUnreadCount + ')'; FolderView.Selected.Text := sFolderInfo; for i := 0 to slMailList.Count-1 do begin FlagMailUnRead(slMailList.Strings[i], sCurrentFolder); end; finally slMailList.Free; end; ParseFolderInfo; // Now refresh the current folder ParseMailboxInfo(sCurrentFolder); Mailview.Navigate('about:blank'); lblSubject.Caption := 'Subject: '; lblFrom.Caption := 'From: '; DisplayMail; MessageView.Items.EndUpdate; end; Creating/Deleting Folders This functionality was also not present in the VB project. In WebDAV, folders are considered collections, hence the MKCOL method to create a folder. And to remove a folder, we use the standard DELETE method. Listing 7. function TfmHotmailForm.CreateNewFolder ( mNewFolderName: String ): String; var sKey: String; begin sKey := FRootFolderURL + mNewFolderName; oXMLHTTP.open('MKCOL', sKey, False, FUserName, FPassword); oXMLHTTP.setRequestHeader('User-Agent', USER_AGENT); oXMLHTTP.setRequestHeader('Content-Type', CONTENT_TYPE); oXMLHTTP.send(CREATE_FOLDER); oXMLHTTP.open('PROPFIND', HotmailURL, False, '', ''); GetFolderInfo(sKey); ParseFolderInfo; ParseMailboxInfo(sKey); Mailview.Navigate('about:blank'); lblSubject.Caption := 'Subject: '; lblFrom.Caption := 'From: '; DisplayMail; end; function TfmHotmailForm.DeleteFolder ( mDestFolder: String ): String; begin // Delete the folder using the DELETE method oXMLHTTP.open('DELETE', mDestFolder, False, FUserName, FPassword); ParseFolderInfo; ParseFolders; oXMLHTTP.open('PROPFIND', HotmailURL, False, '', ''); GetFolderInfo(FInBoxURL); ParseFolderInfo; ParseMailboxInfo(FInBoxURL); Mailview.Navigate('about:blank'); lblSubject.Caption := 'Subject: '; lblFrom.Caption := 'From: '; DisplayMail; end; procedure TfmHotmailForm.actNewFolderExecute ( Sender : TObject ); begin CreateNewFolder(InputBox('New Folder', 'Enter Name for New Folder', 'New Folder')); end; procedure TfmHotmailForm.actDeleteFolderExecute ( Sender : TObject ); var i : Integer; sDestFolder : String; FolderSelect: TfmFolderSelect; begin sDestFolder := ''; FolderSelect := TfmFolderSelect.Create(Self); try FolderSelect.sDestFolder := sDestFolder; for i := 0 to FFolderList.Count-1 do begin FolderSelect.ListBox1.Items.Add(TFolderInfo(FFolderList.Items[i]).Name); end; FolderSelect.Caption := 'Select Folder to Delete'; FolderSelect.ShowModal; finally sDestFolder := FolderSelect.sDestFolder; FolderSelect.Free; if sDestFolder '' then begin for i := 0 to FFolderList.Count-1 do begin if Pos(sDestFolder, TFolderInfo(FFolderList.Items[i]).Name) 0 then begin sDestFolder := TFolderInfo(FFolderList.Items[i]).URL; Break; end; end; DeleteFolder(sDestFolder); end; end; end; Extras There are a lot of little extras in the program that help to make this a presentable application. In ParseFolderInfo, I modify the folder names for the deleted items, msnpromo, sentitems, inbox, and Junk Mail to the Outlook Express default folder titles. When the folder Sent Items is selected, the From: column header is changed to To: and unread messages are in bold text in the DisplayMail procedure. Sending Messages via Hotmail WebDAV Messages can also be sent via Hotmails servers without using Outlook Express or any other email client using the WebDAV POST method. The SendMail procedure only allows plaintext messages but this could be enhanced to include HTML messages as well as attachments. Be sure to read RFC821 for more info on the format of multipart messages. You can also save a copy of the message in your Hotmail sent folder by setting the request header value SAVEINSENT: to t. Listing 8 procedure TfmHotmailForm.actNewMessageExecute ( Sender : TObject ); var frmNewMessage: TfrmNewMessage; begin frmNewMessage := TfrmNewMessage.Create(Self); try if frmNewMessage.ShowModal = mrOK then SendMail(FuserName + '@hotmail.com', FUserName, frmNewMessage.edtToAddress.Text, frmNewMessage.edtSubject.Text, frmNewMessage.chkSaveCopy.Checked, frmNewMessage.memMsgBody.Lines); finally frmNewMessage.Free; end; end; procedure TfmHotmailForm.SendMail ( AFromAddress , AFromName , AToAddress , ASubject: String ; ASaveCopy: Boolean ; ABody: TStrings ); var sTimeStamp, sHeaders, sSaveCopy: String; slPostBody: TStringList; i: Integer; begin slPostBody := TStringList.Create; try // Generate the time stamp. sTimeStamp := FormatDateTime('ddd, dd MMM yyyy '+ 'hh:mm:ss', Now); // Dump mail headers. slPostBody.Add('MAIL FROM:+ AFromAddress + ''); slPostBody.Add('RCPT TO:+ AToAddress + ''); slPostBody.Add(''); slPostBody.Add('From: "' + AFromName + '" ' + AFromAddress + ''); slPostBody.Add('To: + AToAddress + ''); slPostBody.Add('Subject: ' + ASubject +''); // // Change the GMT setting based upon your timezone, // please. // slPostBody.Add('Date: ' + sTimeStamp + ' -6000'); slPostBody.Add(''); // Dump mail body. for i := 0 to ABody.Count-1 do slPostBody.Add(ABody.Strings[i]); // Open the connection. oXMLHTTP.open('POST', FOutBoxURL, False, FUsername, FPassword); // Send the request. if ASaveCopy then sSaveCopy := 't' else sSaveCopy := 'f'; oXMLHTTP.setRequestHeader('SAVEINSENT:', sSaveCopy); oXMLHTTP.setRequestHeader('User-Agent:', USER_AGENT); oXMLHTTP.setRequestHeader('Content-Type', 'message/rfc821'); oXMLHTTP.send(slPostBody.Text); sHeaders := oXMLHTTP.getAllResponseHeaders; FResponseText := oXMLHTTP.ResponseText; FHotmailError := oXMLHTTP.getResponseHeader('X-Dav-Error'); // Show the error if it doesn't go through. if Pos('200', FHotmailError) = 0 then ShowMessage(FHotmailError); finally slPostBody.Free; end; end; Conclusion In this issue, we built an application that can communicate directly with Hotmail. You can now make this functionality available in your own programs by following the example. Hopefully, this will satisfy the many users that have asked for such a feature in their applications. I am also working on a conversion to Delphi for .Net, look for it in a future issue.