Chapter 11

Creating a MAPI Email Agent


CONTENTS


In this chapter you'll learn how to use the OLE Messaging library to create a stand-alone e-mail agent. This agent can scan your incoming mail and, based on rules you establish, automatically handle messages for you. All actions are based on rules you establish in a control file.

Features of the MAPI Email Agent are:

The first part of the chapter will discuss the concept of e-mail agents and cover the overall design of the program. The next sections will detail the coding of the forms and support routines needed to complete the project. In the final section, you'll install and test the MAPI Email Agent program.

Note
The Microsoft Exchange Server clients allow users to establish and code their own mail agent (called the Inbox Assistant). If you have the Microsoft Exchange Server client, now is a good time to review the Inbox Assistant to get an idea of its features. The programming project covered in this chapter works independently of the Inbox Assistant and does not require that users have Microsoft Exchange Server installed on their system.

When you complete the programming project in this chapter, you'll have a fully functional MAPI Email Agent that can be installed on any workstation that has access to a MAPI-compliant e-mail system. Also, the techniques used to build this project can be incorporated into other Windows programming projects to add message processing capabilities.

Designing the Email Agent

Before starting to code the project, it's a good idea to discuss the general features and functions of an e-mail agent. Once you have a good idea of what e-mail agents can do, then you can lay out the basic design features of the MAPI Email Agent programming project covered in this chapter.

Basically, an e-mail agent is a program that "acts for you." It is a program that reviews your messages and, based on information you have supplied, processes the messages for you. Typically, e-mail agents process messages in the user's inbox. Users can set up rules that tell the agent how to handle each new message. These rules can tell the e-mail agent to check various parts of the incoming message and then take a specific action.

For example, the agent can be instructed to look for all messages that are received from your boss and then place those messages in a special folder called "Urgent." Or the agent could be told that any message with the word "SALES" in the subject line should be immediately forwarded to another user's inbox and then erased from your inbox without any comment. You might also tell the agent to automatically reply to all senders that you are out on vacation and will return next week.

Features of the MAPI Email Agent

For this chapter, you'll use Visual Basic 4.0 and the OLE Messaging library to build a stand-alone MAPI Email Agent that has the following features:

Note
The features described here are just the start of what can be accomplished with an e-mail agent. The number of options has been limited to keep this chapter focused on design and coding issues instead of content. As you build your own agent, you can add many other capabilities.

To accomplish this, the MAPI Email Agent will keep track of rules created by the user. These rules will have three parts: tests, comparisons, and actions. The test portion of the rule performs a simple scan of the designated portion of the message, searching for requested content. The MAPI Email Agent described in this chapter is capable of inspecting three message parts:

For example, the test SUBJECT MAPI tells the agent to check the message subject for the word "MAPI." The phrase SENDER Boss tells the agent to check for messages sent to the user from the e-mail ID "boss."

All tests must use a logical condition as part of the processing. The MAPI Email Agent uses comparisons to do this. The program can check for the following four logical conditions:

You'll notice that the last value is able to check the selected message part for the occurrence of a word or phrase. Note that all the comparisons are case-insensitive. It is important to note that the LT and GT can be used with character data, too.

The last of the three portions of a rule is the action. This is the part of the rule that tells the agent what action to take if the test criteria have been met. The MAPI Email Agent can perform the following actions on a message:

The agent allows users to determine whether the forwarded and reply messages are retained or removed once the forward/reply is generated.

Storing the Rules in a Control File

The MAPI Email Agent allows users to build tests and actions, and then use them to create rules. All this information is stored in a text file similar to an INI file. This file also contains general control information, such as the scan interval, whether the agent should create a log file, the default log on profiles, and so on. Listing 11.1 shows a sample rule file.


Listing 11.1. Sample rule file for the MAPI Email Agent.
; ********************************************************
; MAPI Email Agent Control File
; ********************************************************
;
[General]
Editor=notepad.exe
ScanInterval=2
LogFile=mea.log
LogFlag=1
RuleCount=3
ActionCount=4
TestCount=4
Profile=MCA
DeleteForwardFlag=0
NotifyDialog=1
DeleteReplyFlag=0
MinimzeOnStart=0
AutoStart=0
LastUpdated=04/29/96 9:27:30 PM

[Actions]
Action0=MOVE MAPI
Action1=MOVE Urgent
Action2=FORWARD mamund@iac.net
Action3=COPY SavedMail

[Tests]
Test0=SENDER MCA
Test1=SENDER Boss
Test2=SUBJECT SAPI
Test3=SUBJECT MAPI

[Rules]
RuleName0=Boss's Mail
RuleTest0=SENDER Boss
RuleAction0=Move Urgent
RuleCompare0=EQ
RuleName1=Send To ISP
RuleTest1=SENDER MCA
RuleAction1=FORWARD mamund@iac.net
RuleCompare1=EQ
RuleName2=MAPI Mail
RuleTest2=SUBJECT MAPI
RuleAction2=MOVE MAPI
RuleCompare2=CI

The next sections show you how to code the MAPI Email Agent forms and support routines that will create and process the rules described here.

Coding the MAPI Email Agent Forms

You'll use Visual Basic 4.0 to create the three forms of the MAPI Email Agent. These forms will allow you to start and stop message processing; add or delete new tests, actions, and rules; and modify default configuration settings for the MAPI Email Agent.

The next three sections of this chapter outline the steps needed to layout and code these three forms. If you haven't done so yet, start Visual Basic 4.0 now and create a new project.

The Main Form

The Main form of the MAPI Email Agent shows the current list of rules, tests, and actions. You can also launch the message scan routine from this screen, access the setup dialog box, and inspect the MAPI Email Agent log file. Figure 11.1 shows an example of the Main form in run-time mode.

Figure 11.1 : The MAPI Email Agent Main form.

The Main form has a set of command button control arrays to handle the user selections. The first control array covers the top row of buttons. These buttons handle the main processing steps:

The second command button control array handles the adding and deleting of tests, actions, and rules. To keep things simple for this project, the system is capable only of adding or deleting rules. Existing rules cannot be edited and saved again. Also, only basic input editing is performed by this program. In a production environment, this program should be enhanced to add an improved user interface with additional input checking and recovery.

Table 11.1 contains a list of all the controls used on the MAPI Email Agent main form along with their property settings. Use this table along with Figure 11.1 to build the MAPI Email Agent Main form.

Table 11.1. Controls for the MAPI Email Agent Main form.
ControlProperty Setting
VB.Form Name frmMEA
  Caption "MAPI Email Agent"
  ClientHeight 6585
  ClientLeft 975
  ClientTop 1575
  ClientWidth 8175
  Height 6990
  Left 915
  LinkTopic "Form1"
  MaxButton 0 'False
  ScaleHeight 6585
  ScaleWidth 8175
  Top 1230
  Width 8295
VB.CommandButton Name Command1
  Caption "E&xit Program"
  Height 495
  Index 5
  Left 6780
  TabIndex 18
  Top 120
  Width 1200
VB.CommandButton Name Command1
  Caption "Re&fresh"
  Height 495
  Index 4
  Left 5460
  TabIndex 16
  Top 120
  Width 1200
VB.CommandButton Name Command2
  Caption "Delete R&ule"
  Height 495
  Index 5
  Left 6900
  TabIndex 15
  Top 3000
  Width 1100
VB.CommandButton Name Command2
  Caption "New &Rule"
  Height 495
  Index 4
  Left 5700
  TabIndex 14
  Top 3000
  Width 1100
VB.CommandButton Name Command1
  Caption "View L&og"
  Height 495
  Index 3
  Left 4140
  TabIndex 13
  Top 120
  Width 1200
VB.CommandButton Name Command2
  Caption "De&lete Action"
  Height 495
  Index 3
  Left 6960
  TabIndex 12
  Top 5700
  Width 1100
VB.CommandButton Name Command2
  Caption "Ne&w Action"
  Height 495
  Index 2
  Left 5760
  TabIndex 11
  Top 5700
  Width 1100
VB.CommandButton Name Command1
  Caption "SetU&p"
  Height 495
  Index 2
  Left 2820
  TabIndex 10
  Top 120
  Width 1200
VB.CommandButton Name Command1
  Caption "E&nd Timer"
  Height 495
  Index 1
  Left 1500
  TabIndex 9
  Top 120
  Width 1200
VB.CommandButton Name Command2
  Caption "&Delete Test"
  Height 495
  Index 1
  Left 2820
  TabIndex 8
  Top 5700
  Width 1100
VB.CommandButton Name Command2
  Caption "&New Test"
  Height 495
  Index 0
  Left 1620
  TabIndex 7
  Top 5700
  Width 1100
VB.ListBox Name lstActions
  Font  
    name="Courier"
    charset=0
    weight=400
    size=9.75
    underline=0 'False
    italic=0 'False
    strikethrough=0 'False
  Height 1815
  Left 4200
  TabIndex 3
  Top 3720
  Width 3855
VB.ListBox Name lstTests
  Font  
    name="Courier"
    charset=0
    weight=400
    size=9.75
    underline=0 'False
    italic=0 'False
    strikethrough=0 'False
  Height 1815
  Left 120
  TabIndex 2
  Top 3720
  Width 3795
VB.ListBox Name lstRules
  Font  
    name="Courier"
    charset=0
    weight=400
    size=9.75
    underline=0 'False
    italic=0 'False
    strikethrough=0 'False
  Height 1815
  Left 120
  TabIndex 1
  Top 1020
  Width 7875
VB.CommandButton Name Command1
  Caption "&Start Timer"
  Height 495
  Index 0
  Left 180
  TabIndex 0
  Top 120
  Width 1200
VB.Timer Name Timer1
  Left 4860
  Top 3000
VB.Label Name lblStatus
  BorderStyle 1 'Fixed Single
  Caption "Label5"
  Height 255
  Left 0
  TabIndex 17
  Top 6300
  Width 8115
VB.Label Name Label3
  AutoSize -1 'True
  Caption "Actions:"
  Font  
    name="Courier"
    charset=0
    weight=400
    size=9.75
    underline=0 'False
    italic=0 'False
    strikethrough=0 'False
  Height 195
  Left 4260
  TabIndex 6
  Top 3480
  Width 960
VB.Label Name Label2
  AutoSize -1 'True
  Caption "Tests:"
  Font  
    name="Courier"
    charset=0
    weight=400
    size=9.75
    underline=0 'False
    italic=0 'False
    strikethrough=0 'False
  Height 195
  Left 180
  TabIndex 5
  Top 3420
  Width 720
VB.Label Name Label1
  AutoSize -1 'True
  Caption "Current Rules:"
  Font  
    name="Courier"
    charset=0
    weight=400
    size=9.75
    underline=0 'False
    italic=0 'False
    strikethrough=0 'False
  Height 195
  Left 180
  TabIndex 4
  Top 780
  Width 1680

Notice that the font size and type used for this form is slightly different. By using a fixed-width font for the text boxes, it is very easy to get consistent alignment. This is a quick way to present a grid-like look to your list boxes. After completing the form layout, save the form as MEA.FRM and save the project as MEA.VBP.

Next you need to add some code to the form. This code covers several key events for the form including the command button selections. The first thing to do is add two form-level variables (see Listing 11.2).


Listing 11.2. Adding the form-level variables to the Main form.
Option Explicit

Dim iLocalHeight As Integer
Dim iLocalWidth As Integer

These variables are used to retain the original size and shape of the form.

Next add code to the Form_Load event. This code executes some one-time initializations and prepares the form for the user (see Listing 11.3).


Listing 11.3. Adding the Form_Load code.
Private Sub Form_Load()
    '
    Left = (Screen.Width - Me.Width) / 2
    Top = (Screen.Height - Me.Height) / 2
    '
    Timer1.Enabled = False ' start as OFF
    Timer1.Interval = 60000 ' tick off the minutes
    lCounter = 0 ' start at zero
    '
    lblstatus = ""
    Me.Icon = LoadPicture(App.Path & "\" & "mail14.ico")
    '
    ' if user set flag, minimize
    If cMinOnStartValue = "1" Then
        Me.WindowState = vbMinimized
    End If
    '
    ' if user set flag, go!
    If cAutoStartValue = "1" Then
        Timer1_Timer ' fire event
        Timer1.Enabled = True
    End If
    '
    ' remember your size
    iLocalHeight = Me.Height
    iLocalWidth = Me.Width
    '
End Sub

Note the code that initializes the form-level variables. These variables are used in the next routine: the Form_Resize event, which will prevent users from resizing the form. Add the code shown in Listing 11.4.


Listing 11.4. Adding code to the Form_Resize event.
Private Sub Form_Resize()
    '
    ' don't go changin!
    '
    If WindowState = vbNormal Then
        Me.Height = iLocalHeight
        Me.Width = iLocalWidth
    End If
    '
End Sub

The next section of code goes in the Timer1_Timer event (see Listing 11.5).


Listing 11.5. Adding code to the Timer1_Timer event.
Private Sub Timer1_Timer()
    '
    ' fire off message scan
    '
    lblstatus = "Minutes to next scan: " & CStr(lCounter)
    lblstatus.Refresh
    Me.Caption = "MAPI Email Agent [" & CStr(lCounter) & " min]"
    DoEvents
    '
    If lCounter = 0 Then
        Timer1.Enabled = False
        StartProcess ' run scan loop
        lCounter = Val(cScanIntervalValue)
        Timer1.Enabled = True
    Else
        lCounter = lCounter - 1
    End If
    '
End Sub

This event fires off every minute (see Listing 11.3 where the interval was set to 60000). Each time the event fires, the lCounter variable is decremented. When it hits zero, the StartProcess routine is called. This is the routine that actually scans the messages. Notice also that the routine is designed to report timer progress on the form title bar. This same information appears as part of the task bar when the form is minimized.

Now it's time to add the code behind the first set of command buttons. This first set of buttons calls all the main operations of the program. Add the code in Listing 11.6 to the Command1_Click event.


Listing 11.6. Adding the code for the Command1_Click event.
Private Sub Command1_Click(Index As Integer)
    '
    ' handle button selection
    '
    Dim x As Long
    '
    Select Case Index
        Case 0 ' start scan countdown
            LogWrite "Message Scan Started"
            lCounter = 0 ' clear counter
            Timer1_Timer ' fire it off right away
            Timer1.Enabled = True ' start counting
        Case 1 ' end scan countdown
            Timer1.Enabled = False
            LogWrite "Message Scan Stopped"
Case 2 ' view control file
            Timer1.Enabled = False
            frmMEASetUp.Show vbModal
            InitStuff
        Case 3 ' view log file
            x = Shell(cEditorValue & " " & cLogFileValue, 1)
        Case 4 ' refresh lists
            Timer1.Enabled = False
            InitStuff
        Case 5 ' exit program
            Unload Me
    End Select
    '
End Sub

Warning
Most of the code in Listings 11.6 and 11.7 refer to routines and forms that have not been built yet. If you attempt to run the project before all your routines are complete, you'll get error messages from Visual Basic. If you want to test your code by running the project, you'll need to build subroutines to cover the ones that are missing or comment out the lines that call for other routines.

The last code you need to add to the MAPI Email Agent main form is the code for the Command2_Click event. This code handles the calls for adding and deleting rules, actions, and tests. Add the code in Listing 11.7 to the Command2_Click event.


Listing 11.7. Adding code to the Command2_Click event.
Private Sub Command2_Click(Index As Integer)
    '
    ' get edit selections
    '
    Dim cInput As String
    Dim cCommand As String
    '
    Select Case Index
        Case 0 ' new test
            cInput = InputBox("Enter Test String [SENDER|SUBJECT|PRIORITY Value]:", "New Test")
            If Trim(cInput) <> "" Then
                cCommand = ParseWord(cInput)
                If InStr(UCase(cTestCommands), UCase(cCommand)) = 0 Then
                    MsgBox "Invalid Test Command - use " & cTestCommands, vbCritical, "Invalid Command"
                Else
                    MousePointer = vbHourglass
                    AddTest cInput
                    MousePointer = vbNormal
                End If
            End If
        Case 1 ' delete test
            If lsttests.ListIndex = -1 Then
                MsgBox "Must select a Test item to delete", vbCritical, "Delete Test"
            Else
                MousePointer = vbHourglass
                DeleteTest lsttests.ListIndex
                MousePointer = vbNormal
            End If
        Case 2 ' new action
            cInput = InputBox("Enter Test String [MsgPart Value]:", "New Test")
            cCommand = ParseWord(cInput)
            If InStr(UCase(cActionCommands), UCase(cCommand)) = 0 Then
                MsgBox "Invalid Action Command - use " & cActionCommands, vbCritical, "Invalid Command"
            Else
                MousePointer = vbHourglass
                AddAction cInput
                MousePointer = vbNormal
            End If
        Case 3 ' delete action
            If lstactions.ListIndex = -1 Then
                MsgBox "Must select a Action item to delete", vbCritical, "Delete Test"
            Else
                MousePointer = vbHourglass
                DeleteAction lstactions.ListIndex
                MousePointer = vbNormal
            End If
        Case 4 ' new rule
            AddRule ' call add routine
        Case 5 ' delete rule
            If lstrules.ListIndex = -1 Then
                MsgBox "Must select a Rule to delete", vbCritical, "Delete Test"
            Else
                MousePointer = vbHourglass
                DeleteRule lstrules.ListIndex
                MousePointer = vbNormal
            End If
    End Select
    '
End Sub

That's all the coding for the MAPI Email Agent main form. Save this form (MEA.FRM) and this project (MEA.VBP) before continuing.

The Add Rule Form

The rule form is used to compose new rules for the MAPI Email Agent. This form is actually quite simple. It has three list boxes that allow the user to select a test, a compare value, and an action. By combining these three items, the user creates a valid MAPI e-mail agent rule. Once the rule is given a name, it can be saved. All saved rules are acted upon each time MAPI Email Agent scans the incoming messages.

Note
This version of MAPI Email Agent does not let you turn rules on or off. If a rule is in the database, it will be processed each time the messages are scanned. The only way to tell the MAPI Email Agent to not process a rule is to permanently remove the rule from the database. Toggling rules on and off would make a nice enhancement for future versions of MAPI Email Agent.

Refer to Figure 11.2 and Table 11.2 when laying out the new MAPI Email Agent Rule form.

Figure 11.2 : Laying out the MAPI Email Agent Rule form.

Table 11.2. Controls for the MAPI Email Agent Rule form.
ControlProperty Setting
VB.Form Name frmMEARule
  BorderStyle 3 'Fixed Dialog
  Caption "Create Rule"
  ClientHeight 3990
   ClientLeft 1140
  ClientTop 1515
  ClientWidth 6165
  Height 4395
  Left 1080
  LinkTopic "Form1"
  MaxButton 0 'False
  MinButton 0 'False
  ScaleHeight 3990
  ScaleWidth 6165
  ShowInTaskbar 0 'False
  Top 1170
  Width 6285
VB.TextBox Name Text1
  Height 315
  Left 1260
  TabIndex 6
  Text "Text1"
  Top 120
  Width 4755
VB.CommandButton Name Command1
  Caption "&OK"
  Height 495
  Index 1
  Left 4800
  TabIndex 5
  Top 3360
  Width 1215
VB.CommandButton Name Command1
  Caption "&Cancel"
  Height 495
  Index 0
  Left 3420
  TabIndex 4
  Top 3360
  Width 1215
VB.ListBox Name List3
  Height 1815
  Left 2820
  TabIndex 2
  Top 1380
  Width 495
VB.ListBox Name List2
  Height 1815
  Left 3420
  TabIndex 1
  Top 1380
  Width 2595
VB.ListBox Name List1
  Height 1815
  Left 120
  TabIndex 0
  Top 1380
  Width 2595
VB.Label Name Label2
  Caption "Rule Name:"
  Height 315
  Left 180
  TabIndex 7
  Top 120
  Width 975
VB.Label Name Label1
  BorderStyle 1 'Fixed Single
  Caption "Label1"
  Height 675
  Left 120
  TabIndex 3
  Top 540
  Width 5895

There are only a few events in the MAPI Email Agent Rule form that need coding. Listing 11.8 shows the code for the Form_Load event.


Listing 11.8. Adding the code for the MAPI Email Agent Rule form Form_Load event.
Private Sub Form_Load()
    '
    ' fill local lists
    '
    FillList "tests", Me.List1
    FillList "actions", Me.List2
    FillList "compares", Me.List3
    '
    Label1 = "" ' clear rule display
    Text1 = "" ' clear rule name
    '
    Me.Left = (Screen.Width - Me.Width) / 2
    Me.Top = (Screen.Height - Me.Height) / 2
    Me.Icon = LoadPicture(App.Path & "\" & "mail14.ico")
    Me.Caption = "MAPI Email Agent - Create Rule"
    '
End Sub

The Form_Load event first fills the local list boxes, then initializes the other local controls, centers the form, and sets the form's icon and caption.

The code in Listing 11.9 handles the user selections on the command button control array. Add this code to the Command1_Click event.


Listing 11.9. Adding code to the Command1_Click event.
Private Sub Command1_Click(Index As Integer)
    '
    ' handle button clicks
    '
    Select Case Index
        Case 0 ' cancel
            Unload Me
        Case 1 ' OK
            If Trim(Text1) <> "" Then
                If Trim(Label1) <> "" Then
                    MakeRule Text1.Text, Label1.Caption
                    Text1 = ""
                    Label1 = ""
                Else
                    MsgBox "Must Construct Named Rule", vbCritical, "Rule Error"
                End If
            Else
                MsgBox "Must Name the Rule", vbCritical, "Rule Error"
            End If
    End Select
End Sub

Finally, you need to add code to the DblClick events of the three list boxes. By double-clicking each box, the user can create a new rule to add to the database. Refer to Listing 11.10 for the code lines to add to each list box.


Listing 11.10. Adding code to the DblClick_event of each list box.
Private Sub List1_DblClick()
    Label1 = List1.List(List1.ListIndex) & " | "
End Sub

Private Sub List2_DblClick()
    Label1 = Label1 & List2.List(List2.ListIndex)
End Sub

Private Sub List3_DblClick()
    Label1 = Label1 & List3.List(List3.ListIndex) & " | "
End Sub

That is all you need to do for the MAPI Email Agent Rule form. Save this form as MEARULE.FRM and save the project (MEA.VBP) before continuing.

The Setup Form

The last form you need to add to the project is the MAPI Email Agent Setup form. This form allows users to modify the default configuration settings for the MAPI Email Agent. Add a new form to your project called MEASETUP.FRM. Refer to Figure 11.3 and Table 11.3 as you lay out the form.

Figure 11.3 : Laying out the MAPI Email Agent Setup form.

Table 11.3. Controls for the MAPI Email Agent Setup form.
ControlProperty Setting
VB.Form Name frmMEASetUp
  BorderStyle 3 'Fixed Dialog
  Caption "Form2"
  ClientHeight 5940
  ClientLeft 2175
  ClientTop 1605
  ClientWidth 4005
  Height 6345
  Left 2115
  LinkTopic "Form2"
  MaxButton 0 'False
  MinButton 0 'False
  ScaleHeight 5940
  ScaleWidth 4005
  ShowInTaskbar 0 'False
  Top 1260
  Width 4125
VB.CommandButton Name Command1
  Caption "&OK"
  Height 495
  Index 1
  Left 2640
  TabIndex 23
  Top 5280
  Width 1215
VB.CommandButton Name Command1
  Caption "&Cancel"
  Height 495
  Index 0
  Left 1320
  TabIndex 22
  Top 5280
  Width 1215
VB.CheckBox Name Check6
  Alignment 1 'Right Justify
  Caption "Log Activity to File:"
  Height 495
  Left 2040
  TabIndex 13
  Top 2940
  Width 1800
VB.CheckBox Name Check5
  Alignment 1 'Right Justify
  Caption "Use PopUp Dialog on Notify:"
  Height 495
  Left 2040
  TabIndex 12
  Top 2400
  Width 1800
VB.CheckBox Name Check4
  Alignment 1 'Right Justify
  Caption "Start Scan at Load:"
  Height 495
  Left 2040
  TabIndex 11
  Top 1860
  Width 1800
VB.CheckBox Name Check3
  Alignment 1 'Right Justify
  Caption "Minimize On Startup"
  Height 495
  Left 120
  TabIndex 10
  Top 2940
  Width 1800
VB.CheckBox Name Check2
  Alignment 1 'Right Justify
  Caption "Delete Replied Messages:"
  Height 495
  Left 120
  TabIndex 9
  Top 2400
  Width 1800
VB.CheckBox Name Check1
  Alignment 1 'Right Justify
  Caption "Delete Forwarded Messages:"
  Height 495
  Left 120
  TabIndex 8
  Top 1860
  Width 1800
VB.TextBox Name Text4
  Height 315
  Left 1440
  TabIndex 7
  Text "Text1"
  Top 1380
  Width 2400
VB.TextBox Name Text3
  Height 315
  Left 1440
  TabIndex 6
  Text "Text1"
  Top 960
  Width 2400
VB.TextBox Name Text2
  Height 315
  Left 1440
  TabIndex 5
  Text "Text1"
  Top 540
  Width 2400
VB.TextBox Name Text1
  Height 315
  Left 1440
  TabIndex 1
  Text "Text1"
  Top 120
  Width 2400
VB.Label Name Label12
  BorderStyle 1 'Fixed Single
  Height 300
  Left 1440
  TabIndex 21
  Top 3600
  Width 1200
VB.Label Name Label11
  BorderStyle 1 'Fixed Single
  Height 300
  Left 1440
  TabIndex 20
  Top 4860
  Width 2415
VB.Label Name Label10
  BorderStyle 1 'Fixed Single
  Height 300
  Left 1440
  TabIndex 19
  Top 4440
  Width 1200
VB.Label Name Label9
  BorderStyle 1 'Fixed Single
  Height 300
  Left 1440
  TabIndex 18
  Top 4080
  Width 1200
VB.Label Name Label5
  BorderStyle 1 'Fixed Single
  Caption "Rule Count:"
  Height 300
  Left 120
  TabIndex 17
  Top 3600
  Width 1200
VB.Label Name Label8
  BorderStyle 1 'Fixed Single
  Caption "Last Updated:"
  Height 300
  Left 120
  TabIndex 16
  Top 4860
  Width 1200
VB.Label Name Label7
  BorderStyle 1 'Fixed Single
  Caption "Action Count:"
  Height 300
  Left 120
  TabIndex 15
  Top 4440
  Width 1200
VB.Label Name Label6
  BorderStyle 1 'Fixed Single
  Caption "Test Count:"
  Height 300
  Left 120
  TabIndex 14
  Top 4020
  Width 1200
VB.Label Name Label4
  BorderStyle 1 'Fixed Single
  Caption "Profile:"
  Height 300
  Left 120
  TabIndex 4
  Top 1380
  Width 1200
VB.Label Name Label3
  BorderStyle 1 'Fixed Single
  Caption "Log File:"
  Height 300
  Left 120
  TabIndex 3
  Top 960
  Width 1200
VB.Label Name Label2
  AutoSize -1 'True
  BorderStyle 1 'Fixed Single
  Caption "Scan Interval:"
  Height 300
  Left 120
  TabIndex 2
  Top 540
  Width 1200
VB.Label Name Label1
  AutoSize -1 'True
  BorderStyle 1 'Fixed Single
  Caption "Editor:"
  Height 300
  Left 120
  TabIndex 0
  Top 120
  Width 1200

The MAPI Email Agent Setup form needs code for two events and two custom routines. The Custom routines are needed to load the forms controls with the configuration values for editing and then to save them after the values have been modified.

Add a new subroutine called SetupPageLoad to the MAPI Email Agent Setup form and enter the code shown in Listing 11.11.


Listing 11.11. Adding the SetupPageLoad routine.
Public Sub SetupPageLoad()
    '
    Text1 = cEditorValue
    Text2 = cScanIntervalValue
    Text3 = cLogFileValue
    Text4 = cProfileValue
    '
    Label9 = cTestCountValue
    Label10 = cActionCountValue
    Label11 = cLastUpdateValue
    Label12 = cRuleCountValue
    '
    Check1.Value = Val(cDelFwdFlagValue)
    Check2.Value = Val(cDelReplyFlagValue)
    Check3.Value = Val(cMinOnStartValue)
    Check4.Value = Val(cAutoStartValue)
    Check5.Value = Val(cNotifyDialogValue)
    Check6.Value = Val(cLogFlagValue)
    '
End Sub

Now add another new subroutine called SetupPageSave and enter the code shown in Listing 11.12.


Listing 11.12. Adding the SetupPageSave routine.
Public Sub SetupPageSave()
    '
    ' save updated config data
    ' to vars for later
    '
    cEditorValue = Text1
    cScanIntervalValue = Text2
    cLogFileValue = Text3
    cProfileValue = Text4
    '
    cDelFwdFlagValue = CStr(Check1)
    cDelReplyFlagValue = CStr(Check2)
    cMinOnStartValue = CStr(Check3)
    cAutoStartValue = CStr(Check4)
    cNotifyDialogValue = CStr(Check5)
    cLogFlagValue = CStr(Check6)
    '
    ' now save to file
    SaveValues
    '
End Sub

Only two events need coding-the Form_Load event and the Command1_Click event. Listing 11.13 shows the code for the Form_Load event.


Listing 11.13. Adding code to the MAPI Email Agent Setup Form_Load event.
Private Sub Form_Load()
    '
    ' load controls
    '
    SetupPageLoad
    '
    Me.Left = (Screen.Width - Me.Width) / 2
    Me.Top = (Screen.Height - Me.Height) / 2
    Me.Icon = LoadPicture(App.Path & "\" & "mail14.ico")
    Me.Caption = "MAPI Email Agent - Setup Page"
    '
End Sub

Warning
In the second-to-last line, this code refers to an icon in the local folder. The icon can be found on the CD-ROM that ships with this book, and is copied to your machine when you install the source code from the CD-ROM. If you get an error message on this line, you may need to locate the icon or use another icon for your project.

The last event you need to code for the form is the Command1_Click event. This event handles the saving (or canceling) of a new rule. Add the code shown in Listing 11.14 to the Command1_Click event.


Listing 11.14. Adding code to the Command1_Click event.
Private Sub Command1_Click(Index As Integer)
    '
    ' handle key clicks
    '
    Select Case Index
        Case 0 ' cancel
            ' na
        Case 1 ' ok
            SetupPageSave
    End Select
    '
    Unload Me
    '
End Sub

That is the end of the coding for the MAPI Email Agent Setup form. Save this form as MEASETUP.FRM and save the project (MEA.VBP) before you continue to the next section.

In the next section, you'll add the support routines that are needed to make the MAPI Email Agent really work.

Coding the Support Routines

The real heart of the MAPI Email Agent program is the support routines. There are three main sets of routines in the program:

The next three sections of this chapter walk you through the process of building the support routines for the MAPI Email Agent program.

All the code for the support routines will be added to a BAS module. Before going on to the next sections, add a BAS module called LIBMEA.BAS to the MAPI Email Agent project.

The Initialization Routines

The initialization routines declare the global variables and set them to their initial values. There are also routines to handle the reading and writing of configuration values and the storing and retrieving of the test, action, and rule records.

Note
All of the control information is kept in a single ASCII text file (MEA.RUL) in the same folder as the program. This format was chosen for simplicity. In a production environment, you will want to consider a more sophisticated storage system including database formats.

Since we will be using an ASCII control file similar to the 16-bit INI files, we need to declare two API calls to handle the reading and writing of those values. Listing 11.15 shows the two API calls needed for this project. Add this code to the general declarations section of the BAS module.


Listing 11.15. Adding the API calls.
Option Explicit
'
' APIs for read/write of shared INI settings
Declare Function GetPrivateProfileString Lib "kernel32" Alias "GetPrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpDefault As String, ByVal lpReturnedString As String, ByVal nSize As Long, ByVal lpFileName As String) As Long
Declare Function WritePrivateProfileString Lib "kernel32" Alias "WritePrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpString As Any, ByVal lpFileName As String) As Long

The next code to add declares the global variables. Listing 11.16 shows all the declarations needed for the project. Add these values to the general declarations section of the module.


Listing 11.16. Declaring the global variables.
Global cRuleFile As String
Global cIsEqualTo As String
Global cIsContainedIn As String
Global cIsGreaterThan As String
Global cIsLessThan As String
Global cForwardMsg1 As String
Global cForwardMsg2 As String
Global lCounter As Long
'
' general section
Global cGeneralSection As String
Global cGeneralScanInterval As String
Global cScanIntervalValue As String
Global cGeneralAutoStart As String
Global cAutoStartValue As String
Global cGeneralEditor As String
Global cEditorValue As String
Global cGeneralLogFile As String
Global cLogFileValue As String
Global cGeneralLogFlag As String
Global cLogFlagValue As String
Global cGeneralRuleCount As String
Global cRuleCountValue As String
Global cGeneralTestCount As String
Global cTestCountValue As String
Global cGeneralActionCount As String
Global cActionCountValue As String
Global cGeneralProfile As String
Global cProfileValue As String
Global cGeneralDelFwdFlag As String
Global cDelFwdFlagValue As String
Global cGeneralDelReplyFlag As String
Global cDelReplyFlagValue As String
Global cGeneralNotifyDialog As String
Global cNotifyDialogValue As String
Global cGeneralMinOnStart As String
Global cMinOnStartValue As String
Global cGeneralLastUpdate As String
Global cLastUpdateValue As String
'
' test section
Global cTestSection As String
Global cTestPriority As String
Global cTestSubject As String
Global cTestSender As String
Global cTestCommands As String
'
' action section
Global cActionSection As String
Global cActionCopy As String ' COPY (folder)
Global cActionMove As String ' MOVE (folder)
Global cActionForward As String ' FORWARD (address)
Global cActionNotify As String ' NOTIFY
Global cActionReply As String ' REPLY (textfile)
Global cActionCommands As String
'
' rules section
Global cRulesSection As String
Global cRuleName() As String
Global cRuleTest() As String
Global cRuleAction() As String
Global cRuleCompare() As String
Global cTest() As String
Global cAction() As String
Global iTestCount As Integer
Global iActionCount As Integer
Global iRuleCount As Integer
'
' standard mapi objects
Global objSession As Object
Global objMsgColl As Object
Global objMessage As Object
Global objOriginator As Object
Global objRecipColl As Object
Global objRecipient As Object
Global objFolderColl As Object
Global objFolder As Object
Global objAddrEntry As Object
Global objInfoStoreColl As Object
Global objInfoStore As Object
Global objAttachColl As Object
Global objAttachments As Object

Next add the Main subroutine to the project. This Visual Basic project launches from a Main() subroutine instead of a form. The Main routine first calls a routine that performs the initialization routines, then calls the main form. Once the form is closed by the user, a short cleanup routine is called. Add the code in Listing 11.17 to the project.


Listing 11.17. Adding the Main routine.
Public Sub Main()
    '
    ' main start and end
    '
    InitStuff
    frmMEA.Show vbModal
    CloseDown
    '
End Sub

Now add the InitStuff subroutine to the project and enter the code in Listing 11.18.


Listing 11.18. Adding the InitStuff routine.
Public Sub InitStuff()
    '
    ' it all starts here
    '
    ReDim cAction(0)
    ReDim cTest(0)
    ReDim cRuleName(0)
    ReDim cRuleTest(0)
    ReDim cRuleAction(0)
    ReDim cRuleCompare(0)
    '
    LoadStrings
    LoadValues
    StartMAPI
    LoadLists
    '
End Sub

The routine in Listing 11.18 calls four other routines. The first one is LoadStrings. This routine initializes the local variables at startup. Add the LoadStrings subroutine and enter the code in Listing 11.19.


Listing 11.19. Adding the LoadStrings routine.
Public Sub LoadStrings()
    '
    ' init all internals
    '
    cRuleFile = App.Path & "\" & App.EXEName & ".RUL"
    cIsEqualTo = "EQ"
    cIsContainedIn = "CI"
    cIsGreaterThan = "GT"
    cIsLessThan = "LT"
    '
    cGeneralSection = "General"
    cGeneralEditor = "Editor"
    cEditorValue = "notepad.exe"
    cGeneralLogFile = "LogFile"
    cLogFileValue = App.Path & "\" & App.EXEName & ".LOG"
    cGeneralLogFlag = "LogFlag"
    cLogFlagValue = "1"
    cGeneralScanInterval = "ScanInterval"
    cScanIntervalValue = "15"
    cGeneralRuleCount = "RuleCount"
    cRuleCountValue = "0"
    cGeneralTestCount = "TestCount"
    cTestCountValue = "0"
    cGeneralActionCount = "ActionCount"
    cActionCountValue = "0"
    cGeneralProfile = "Profile"
    cProfileValue = "MCA"
    cGeneralDelFwdFlag = "DeleteForwardFlag"
    cDelFwdFlagValue = "0"
    cGeneralNotifyDialog = "NotifyDialog"
    cNotifyDialogValue = "0"
    cGeneralDelReplyFlag = "DeleteReplyFlag"
    cDelReplyFlagValue = "0"
    cGeneralMinOnStart = "MinimzeOnStart"
    cMinOnStartValue = "0"
    cGeneralAutoStart = "AutoStart"
    cAutoStartValue = "0"
    cGeneralLastUpdate = "LastUpdated"
    cLastUpdateValue = Now()
    '
    cTestSection = "Tests"
    cTestPriority = "Priority"
    cTestSubject = "Subject"
    cTestSender = "Sender"
    cTestCommands = cTestPriority & " " & cTestSubject & " " & cTestSender
    '
    cActionSection = "Actions"
    cActionCopy = "Copy"
    cActionMove = "Move"
    cActionForward = "Forward"
    cActionReply = "Reply"
    cActionNotify = "Notify"
    cActionCommands = cActionCopy & " " & cActionMove & " " & cActionForward
    cActionCommands = cActionCommands & " " & cActionReply & " " & cActionNotify
    '
    cRulesSection = "Rules"
    '
End Sub

Tip
Most of the strings in the LoadString routine are the names of control file keys ("Editor," "ScanInterval," "LogFile," and so on). By storing the names of the keys in this way, you can easily localize the project for other languages-because the MAPI Email Agent project checks only variable names, you can change the values in this section to match other languages without having to re-code most of the program.

The next few routines all deal with data transfers to and from the ASCII control file. Listing 11.20 shows two new routines-LoadValues and SaveValues. Add these routines to your module and enter the code shown in Listing 11.20.


Listing 11.20. Adding the LoadValues and SaveValues routines.
Public Sub LoadValues()
    '
    ' process rules file
    '
    INIGeneral "LOAD"
    INIRules "LOAD"
    INITests "LOAD"
    INIActions "LOAD"
    '
End Sub

Public Sub SaveValues()
    '
    ' save ini values for next time
    '
    INIGeneral "SAVE"
    INIRules "SAVE"
    INITests "SAVE"
    INIActions "SAVE"
    '
End Sub

You can see that both routines call the same subroutines using different parameters for the save and load events. Now add the INIGeneral subroutine to your module and enter the code shown in Listing 11.21.


Listing 11.21. Adding the INIGeneral routine.
Public Sub INIGeneral(cAction As String)
    '
    ' load general rule stuff
    '
    cLastUpdateValue = Format(Now(), "general date")
    '
    cEditorValue = INIRegSetting(cAction, cRuleFile, cGeneralSection, cGeneralEditor, cEditorValue)
    cScanIntervalValue = INIRegSetting(cAction, cRuleFile, cGeneralSection, cGeneralScanInterval, cScanIntervalValue)
    cLogFileValue = INIRegSetting(cAction, cRuleFile, cGeneralSection, cGeneralLogFile, cLogFileValue)
    cLogFlagValue = INIRegSetting(cAction, cRuleFile, cGeneralSection, cGeneralLogFlag, cLogFlagValue)
    cRuleCountValue = INIRegSetting(cAction, cRuleFile, cGeneralSection, cGeneralRuleCount, cRuleCountValue)
    cActionCountValue = INIRegSetting(cAction, cRuleFile, cGeneralSection, cGeneralActionCount, cActionCountValue)
    cTestCountValue = INIRegSetting(cAction, cRuleFile, cGeneralSection, cGeneralTestCount, cTestCountValue)
    cProfileValue = INIRegSetting(cAction, cRuleFile, cGeneralSection, cGeneralProfile, cProfileValue)
    cDelFwdFlagValue = INIRegSetting(cAction, cRuleFile, cGeneralSection, cGeneralDelFwdFlag, cDelFwdFlagValue)
    cNotifyDialogValue = INIRegSetting(cAction, cRuleFile, cGeneralSection, cGeneralNotifyDialog, cNotifyDialogValue)
    cDelReplyFlagValue = INIRegSetting(cAction, cRuleFile, cGeneralSection, cGeneralDelReplyFlag, cDelReplyFlagValue)
    cMinOnStartValue = INIRegSetting(cAction, cRuleFile, cGeneralSection, cGeneralMinOnStart, cMinOnStartValue)
    cAutoStartValue = INIRegSetting(cAction, cRuleFile, cGeneralSection, cGeneralAutoStart, cAutoStartValue)
    cLastUpdateValue = INIRegSetting(cAction, cRuleFile, cGeneralSection, cGeneralLastUpdate, cLastUpdateValue)
    '
End Sub

You'll notice that the INIRegSetting function called in each line of Listing 11.21 is very similar to the SaveSetting/GetSetting functions built into Visual Basic 4.0. However, unlike the built-in Visual Basic functions, this one routine can be used to both read and write values. Also, this custom version always writes to a disk file. The Visual Basic 4.0 SaveSetting function saves values to the Registry under Windows NT and Windows 95.

Add the INIRegSetting function to your project and enter the code shown in Listing 11.22.


Listing 11.22. Adding the INIRegSetting function.
Public Function INIRegSetting(cAction As String, cFile As String, cSection As String, cKey As String, cValue As String) As String
    '
    ' handle read/write of local ini settings
    ' for public use
    '
    INIRegSetting = ""
    '
    If UCase(cAction) = "LOAD" Then
        INIRegSetting = LocalGetSetting(cFile, cSection, cKey, cValue)
    End If
    '
    If UCase(cAction) = "SAVE" Then
 INIRegSetting = LocalSaveSetting(cFile, cSection, cKey, cValue)
    End If
    '
End Function

The INIRegSetting routine makes one last call down to a pair of custom routines. These custom routines (LocalGetSetting and LocalSaveSetting) are the wrapper functions for the API declarations you added at the start of this module. Add the two new functions (LocalGetSetting and LocalSaveSetting) and enter the code shown in Listing 11.23.


Listing 11.23. Adding the LocalGetSetting and LocalSaveSetting functions.
Public Function LocalGetSetting(cFile As String, cSection As String, cKey As String, cDefault As String) As String
    '
    ' mimic GetSetting/SaveSetting for 32-bit text files
    '
    Dim lRtn As Long
    Dim lSize As Long
    Dim cTemp As String * 1024
    '
    lSize = Len(cTemp)
    lRtn = GetPrivateProfileString(cSection, cKey, cDefault, cTemp, lSize, cFile)
    If Trim(cTemp) = "" Then
        cTemp = Trim(cDefault)
        lRtn = WritePrivateProfileString(cSection, cKey, cTemp, cFile)
    End If
    '
    LocalGetSetting = Left(cTemp, lRtn)
    '
End Function

Public Function LocalSaveSetting(cFile As String, cSection As String, cKey As String, cValue As String)
    '
    ' mimic INI/Registry stuff for public use
    '
    Dim lRtn As Long
    '
    lRtn = WritePrivateProfileString(cSection, cKey, cValue, cFile)
    LocalSaveSetting = cValue ' return what was saved
    '
End Function

Next you need to add the routines to load the rules, actions, and tests from the control file into memory variables. Listing 11.24 contains the code for the INIRules routine.


Listing 11.24. Adding the INIRules routine.
Public Sub INIRules(cAction As String)
    '
    ' load all rules
    '
    Dim x As Integer
    '
    iRuleCount = Val(cRuleCountValue)
    ReDim Preserve cRuleName(iRuleCount)
    ReDim Preserve cRuleTest(iRuleCount)
    ReDim Preserve cRuleAction(iRuleCount)
    ReDim Preserve cRuleCompare(iRuleCount)
    '
    If iRuleCount > 0 Then
        For x = 0 To iRuleCount - 1
            cRuleName(x) = INIRegSetting(cAction, cRuleFile, cRulesSection, "RuleName" & CStr(x), cRuleName(x))
            cRuleTest(x) = INIRegSetting(cAction, cRuleFile, cRulesSection, "RuleTest" & CStr(x), cRuleTest(x))
            cRuleAction(x) = INIRegSetting(cAction, cRuleFile, cRulesSection, "RuleAction" & CStr(x), cRuleAction(x))
            cRuleCompare(x) = INIRegSetting(cAction, cRuleFile, cRulesSection, "RuleCompare" & CStr(x), cRuleCompare(x))
        Next x
    End If
    '
End Sub

Listing 11.25 shows the code for the INITests and INIActions routines. Add these to your project.


Listing 11.25. Adding the INITests and INIActions routines.
Public Sub INITests(cAction As String)
    '
    ' load all tests
    '
    Dim x As Integer
    '
    iTestCount = Val(cTestCountValue)
    ReDim Preserve cTest(iTestCount)
    '
    If iTestCount > 0 Then
        For x = 0 To iTestCount - 1
            cTest(x) = INIRegSetting(cAction, cRuleFile, cTestSection, "Test" & CStr(x), cTest(x))
        Next x
    End If
    '
End Sub

Public Sub INIActions(cEvent As String)
    '
    ' load all actions
    '
    Dim x As Integer
    '
    iActionCount = Val(cActionCountValue)
    ReDim Preserve cAction(iActionCount)
    '
    If iActionCount > 0 Then
        For x = 0 To iActionCount - 1
            cAction(x) = INIRegSetting(cEvent, cRuleFile, cActionSection, "Action" & CStr(x), cAction(x))
        Next x
    End If
    '
End Sub

The last routines needed for the initialization section are the ones that start and end your MAPI sessions (you remember MAPI, right?), and the routine that handles program exit cleanup.

First add the CloseDown cleanup routine to your project and enter the code shown in Listing 11.26.


Listing 11.26. Adding the CloseDown routine.
Public Sub CloseDown()
    '
    ' close down system
    '
    SaveValues
    MAPIEnd
    '
End Sub

Now add the StartMAPI and MAPIEnd routines and enter the code from Listing 11.27.


Listing 11.27. Adding the StartMAPI and MAPIEnd routines.
Public Sub StartMAPI()
    '
    ' log into mapi system
    '
    Set objSession = CreateObject("MAPI.Session")
    objSession.Logon profilename:=cProfileValue, profilepassword:="", newsession:=True
    '
End Sub

Public Sub MAPIEnd()
    '
    ' log off mapi system
    objSession.Logoff
    Set objSession = Nothing
    '
End Sub

That is the end of the support routines for initialization. Save this module (MEA.BAS) and project (MEA.VBP) before continuing to the next section.

The List-Handling Routines

The next set of routines handle the addition and deletion of records from the rules, tests, and actions lists. There are also two routines that handle the populating of the list controls on the MAPI Email Agent forms.

First, add a new subroutine called LoadLists to the module and enter the code shown in Listing 11.28. This routine simply calls the low-level function that actually fills the list box control.


Listing 11.28. Adding the LoadLists routine.
Public Sub LoadLists()
    '
    ' load the main form lists
    '
    FillList "rules", frmMEA.lstrules
    FillList "tests", frmMEA.lsttests
    FillList "actions", frmMEA.lstactions
    '
End Sub

Next, add the low-level routine that is called by LoadLists. Add the subroutine FillList to the project and enter the code shown in Listing 11.29.


Listing 11.29. Adding the FillList routine.
Public Sub FillList(cListName As String, lstControl As Control)
    '
    ' fill form lists with data
    '
    Dim x As Integer
    Dim ln As Integer
    Dim cLine As String
    '
    Select Case UCase(cListName)
        Case "RULES"
            lstControl.Clear
            For x = 0 To iRuleCount - 1
                If Len(cRuleName(x)) > 18 Then
                    ln = 18
                Else
                    ln = Len(cRuleName(x))
                End If
                cLine = Left(cRuleName(x), 18) & Space(20 - ln)
                '
                If Len(cRuleTest(x)) > 13 Then
                    ln = 13
                Else
                    ln = Len(cRuleTest(x))
                End If
                '
                cLine = cLine & Left(cRuleTest(x), 13) & Space(15 - ln)
                cLine = cLine & cRuleCompare(x)
                cLine = cLine & Space(2)
                cLine = cLine & cRuleAction(x)
                lstControl.AddItem cLine
            Next x
        Case "TESTS"
            lstControl.Clear
            For x = 0 To iTestCount - 1
                lstControl.AddItem cTest(x)
            Next x
        Case "ACTIONS"
            lstControl.Clear
            For x = 0 To iActionCount - 1
                lstControl.AddItem cAction(x)
            Next x
        Case "COMPARES"
            lstControl.Clear
            lstControl.AddItem cIsEqualTo
            lstControl.AddItem cIsGreaterThan
            lstControl.AddItem cIsLessThan
            lstControl.AddItem cIsContainedIn
    End Select
    '
End Sub

Notice the use of column-like spacing for the rules list. By computing length and setting spacing evenly, you can give the effect of a grid format while still using a list box control.

Tip
This technique works well only if you set your list box control font type to a fixed-width font such as Courier or System.

The next two routines deal with the addition and deletion of tests. First, add the new routine AddTest to the project and enter the code shown in Listing 11.30.


Listing 11.30. Adding the AddTest routine.
Public Sub AddTest(cTestLine As String)
    '
    ' add a new test to system
    '
    Dim x As Integer
    Dim p As Integer
    '
    ' look for empty spot
    p = -1
    For x = 0 To iTestCount - 1
        If cTest(x) = "" Then
            p = x
            Exit For
        End If
    Next x
    '
    ' no empty spot, make a new one
    If p = -1 Then
        iTestCount = iTestCount + 1
        cTestCountValue = CStr(iTestCount)
        ReDim Preserve cTest(iTestCount)
        p = iTestCount - 1
    End If
    '
    cTest(p) = cTestLine ' save item
    '
    ' refresh lists
    SaveValues
    LoadValues
    LoadLists
    '
End Sub

This routine works by adding an item to the Test() array. First, the routine attempts to find an open slot in the existing list. If none is found, the routine will expand the list and add the new item at the bottom of the list. As a final step, AddTest saves the new data to the control file and then reloads the control data to refresh the local variables.

The DeleteTest function is quite simple. It just removes the selected item from the array by blanking it out. This is simple, but not the most desirable. When you run the program you'll see that each deletion leaves a hole in the list. As new items are added, these holes are filled. The holes do not adversely affect processing, but they are a bit unsightly in the list controls. In a production application, more time can be spent on the user interface. For now, just keep in mind you have a routine that works-you can add the bells and whistles later.

Add the DeleteTest subroutine to your project and enter the code in Listing 11.31.


Listing 11.31. Adding the DeleteTest routine.
Public Sub DeleteTest(itest As Integer)
    '
    ' remove test from list
    '
    cTest(itest) = ""
    '
    SaveValues
    LoadValues
    LoadLists
    '
End Sub

The AddAction and DeleteAction routines are almost identical to the AddTest and DeleteTest routines. Add these two new subroutines to your project and enter the code from Listings 11.32 and 11.33.


Listing 11.32. Adding the AddAction routine.
Public Sub AddAction(cInput As String)
    '
    ' add a new test to system
    '
    Dim x As Integer
    Dim p As Integer
    '
    ' look for empty spot
    p = -1
    For x = 0 To iActionCount - 1
        If cAction(x) = "" Then
            p = x
            Exit For
        End If
    Next x
    '
    ' no empty spot, make a new one
    If p = -1 Then
        iActionCount = iActionCount + 1
        cActionCountValue = CStr(iActionCount)
        ReDim Preserve cAction(iActionCount)
        p = iActionCount - 1
    End If
    '
    cAction(p) = cInput ' save item
    '
    ' refresh lists
    SaveValues
    LoadValues
    LoadLists
    '
End Sub


Listing 11.33. Adding the DeleteAction routine.
Public Sub DeleteAction(iAction As Integer)
    '
    ' remove action from list
    '
    cAction(iAction) = ""
    '
    SaveValues
    LoadValues
    LoadLists
    '
End Sub

The AddRule routine is a bit different. The AddRule routine calls the MAPI Email Agent Rule form to add new rules. This form then calls a function to actually add the new rule to the control file. So there are really three routines:

First, add the new AddRule subroutine and enter the code shown in Listing 11.34.


Listing 11.34. Adding the AddRule routine.
Public Sub AddRule()
    '
    ' handle adding new rule
    '
    frmMEARule.Show vbModal
    '
    frmMEA.MousePointer = vbHourglass
    '
    SaveValues
    LoadValues
    LoadLists
    '
    frmMEA.MousePointer = vbNormal
    '
End Sub

Now add the MakeRule subroutine to your project. This is the routine that actually saves the results of data entry on the MAPI Email Agent Rule form. Enter the code from Listing 11.35.


Listing 11.35. Adding the MakeRule routine.
Public Sub MakeRule(cName As String, cRule As String)
    '
    ' store new rule
    '
    Dim x As Integer
    Dim p As Integer
    Dim cTestPart As String
    Dim cComparePart As String
    Dim cActionPart As String
    Dim nPos1 As Integer
    Dim nPos2 As Integer
    '
    ' first get parts
    '
    nPos1 = 1
    nPos2 = InStr(nPos1, cRule, " | ")
    If nPos2 <> 0 Then
        cTestPart = Trim(Mid(cRule, nPos1, nPos2 - nPos1))
    End If
    '
    nPos1 = nPos2 + 3
    nPos2 = InStr(nPos1, cRule, " | ")
    If nPos2 <> 0 Then
        cComparePart = Trim(Mid(cRule, nPos1, nPos2 - nPos1))
    End If
    '
    nPos1 = nPos2 + 3
    cActionPart = Trim(Mid(cRule, nPos1, 255))
    '
    '
    ' look for empty spot
    p = -1
    For x = 0 To iRuleCount - 1
        If Trim(cRuleName(x)) = "" Then
            p = x
            Exit For
        End If
    Next x
    '
    ' no empty spot, make a new one
    If p = -1 Then
        iRuleCount = iRuleCount + 1
        cRuleCountValue = CStr(iRuleCount)
        ReDim Preserve cRuleName(iRuleCount)
        ReDim Preserve cRuleTest(iRuleCount)
        ReDim Preserve cRuleCompare(iRuleCount)
        ReDim Preserve cRuleAction(iRuleCount)
        p = iRuleCount - 1
    End If
    '
    ' save it
    cRuleName(p) = cName
    cRuleTest(p) = cTestPart
    cRuleCompare(p) = cComparePart
    cRuleAction(p) = cActionPart
    '
    ' refresh storage
    SaveValues
    LoadValues
    '
End Sub

The last list-handling routine you need to add is the DeleteRule subroutine. After adding the routine, enter the code you see in Listing 11.36.


Listing 11.36. Adding the DeleteRule routine.
Public Sub DeleteRule(iAction As Integer)
    '
    ' remove rule from list
    '
    cRuleName(iAction) = ""
    cRuleTest(iAction) = ""
    cRuleCompare(iAction) = ""
    cRuleAction(iAction) = ""
    '
    SaveValues
    LoadValues
    LoadLists
    '
End Sub

There is one more support routine needed before you are done with this section. You need a routine to write out log messages when requested. Add a new subroutine called LogWrite to your project and enter the code shown in Listing 11.37. This routine writes lines to a text file along with the date and time the line was written. This routine also sends the same message to the status line of the MAPI Email Agent main form.


Listing 11.37. Adding the LogWrite routine.
Public Sub LogWrite(cLine As String)
    '
    ' write an entry in the log
    '
    Dim nFile As Integer
    Dim cWriteLine As String
    '
    If cLogFlagValue <> "1" Then
        Exit Sub ' write is OFF
    End If
    '
    cWriteLine = Format(Now, "general date")
    cWriteLine = cWriteLine & Space(3)
    cWriteLine = cWriteLine & cLine
    '
    nFile = FreeFile
    Open cLogFileValue For append As nFile
    Print #nFile, cWriteLine
    Close #nFile
    '
    frmMEA.lblstatus = cWriteLine
    '
End Sub

You now have all the routines needed to add and delete items from the lists. Actually you have everything built except the message processing routines. This is a good time to test the forms and list-handling routines. First, you need to add a "stub" routine to your project. This routine does nothing on its own, but it allows you to test your forms without getting errors. Just create a new subroutine called StartProcess.

Public Sub StartProcess()
    '
End Sub

Now save the module (MEA.BAS) and the project (MEA.VBP) before doing any test runs. After you are satisfied the routines are working properly, you can go on to the next section for the last bit of coding-the message processing routines.

The Message Processing Routines

This last set of routines is where the MAPI services are finally used. The goal of the message processing routines is to inspect each message in the user's inbox and check the messages against the rules that have been established for the MAPI Email Agent.

The top-level routines are StartProcess and ScanMsgs. The StartProcess routine is called by the Timer1_Timer event or by pressing the Start button on the main form. StartProcess checks to see if there are any messages in the user's inbox. If there are, then the ScanMsg routine is called to process each message.

If you already added the StartProcess routine, locate it now. Otherwise, add the new routine and enter the code shown in Listing 11.38.


Listing 11.38. Adding the StartProcess code.
Public Sub StartProcess()
    '
    ' start main process loop
    '
    Set objMsgColl = objSession.Inbox.Messages
    If objMsgColl Is Nothing Then
        MsgBox "No Messages to scan"
    Else
        ScanMsgs
        Set objMsgColl = Nothing
    End If
    '
End Sub

The StartProcess routine attempts to create a message collection object based on the Session inbox. If that is successful, the ScanMsgs routine can be called. Now add the ScanMsgs subroutine and enter the code in Listing 11.39.


Listing 11.39. Adding the ScanMsgs routine.
Public Sub ScanMsgs()
    '
    ' check each message for hits
    '
    Dim x As Long
    '
    Set objMessage = objMsgColl.GetFirst
    If objMessage Is Nothing Then
        Exit Sub ' no messages!
    End If
    Do Until objMessage Is Nothing
        CheckRule
        Set objMessage = objMsgColl.GetNext
    Loop
    '
    LogWrite "Msg Scan Completed"
    '
End Sub

The ScanMsgs routine selects each message in the collection and submits it to the CheckRule routine for processing.

Now add the CheckRule subroutine and type in the code shown in Listing 11.40.


Listing 11.40. Adding the CheckRule routine.
Public Sub CheckRule()
    '
    ' check for rule hit
    '
    Dim x As Integer
    Dim cCmd As String
    Dim bRtn As Boolean
    Dim objAddrEntry As Object
    '
    On Error GoTo CheckRuleErr
    '
    For x = 0 To iRuleCount - 1
        cCmd = ParseWord(cRuleTest(x))
        '
        Select Case UCase(cCmd)
            Case UCase(cTestSender)
                Set objAddrEntry = objMessage.Sender
                If objAddrEntry Is Nothing Then
                    ' na
                Else
                    If CheckSender(x, objMessage.Sender.Name) = True Then
                        DoAction x
                    End If
                End If
            Case UCase(cTestSubject)
                If CheckSubject(x, objMessage.subject) = True Then
                    DoAction x
                End If
            Case UCase(cTestPriority)
                If CheckPriority(x, objMessage.importance) = True Then
                    DoAction x
                End If
        End Select
        '
    Next x
    '
    Exit Sub
    '
CheckRuleErr:
    MsgBox Error$, vbCritical, "CheckRuleErr [" & CStr(Err) & "]"
    '
End Sub

Several things are going on in this routine. First, the first "word" on the line is removed using the ParseWord() function. This word is then used to determine the type of test to perform on the message. The appropriate check subroutine is called (CheckSender, CheckSubject, CheckPriority) and, if the return is positive, the DoAction routine is called to handle the action portion of the rule.

Now add the ParseWord function and enter the code shown in Listing 11.41.


Listing 11.41. Adding the ParseWord function.
Public Function ParseWord(cLine As String) As String
    '
    ' pick a word off line
    '
    Dim nPos As Integer
    '
    nPos = InStr(cLine, " ")
    If nPos <> 0 Then
        ParseWord = Left(cLine, nPos - 1)
    Else
        ParseWord = ""
    End If
    '
End Function

The ParseWord() function accepts a string and returns the first full word found in the string. For example ParseWord("SENDER Smith") would return SENDER. This is used to pull the command portion of a test or action record.

The CheckRule routine you entered earlier (in Listing 11.40) uses ParseWord() to get the message portion command of a rule (SENDER, SUBJECT, PRIORITY). This value is then used to call the three message-part-specific check routines (CheckSender, CheckSubject, and CheckPriority). You need to add these routines next.

First add the CheckSender function, and enter the code shown in Listing 11.42.


Listing 11.42. Adding the CheckSender function.
Public Function CheckSender(nRule As Integer, cName As String)
    '
    ' check name against sender test
    '
    Dim nPos As Integer
    Dim cSender As String
    '
    nPos = InStr(cRuleTest(nRule), " ")
    If nPos <> 0 Then
        cSender = Trim(Mid(cRuleTest(nRule), nPos + 1, 255))
    End If
    '
    cSender = Trim(UCase(cSender))
    cName = Trim(UCase(cName))
    '
    Select Case UCase(cRuleCompare(nRule))
        Case UCase(cIsEqualTo)
            If cSender = cName Then
                CheckSender = True
            Else
                CheckSender = False
            End If
        Case UCase(cIsGreaterThan)
            If cSender > cName Then
                CheckSender = True
            Else
                CheckSender = False
            End If
        Case UCase(cIsLessThan)
            If cSender < cName Then
                CheckSender = True
            Else
                CheckSender = False
            End If
        Case UCase(cIsContainedIn)
             If InStr(cSender, cName) <> 0 Then
                CheckSender = True
            Else
                CheckSender = False
            End If
    End Select
   '
End Function

Note that this routine uses a SELECT CASE structure to handle the compare portion of the rule. After locating the correct compare operation, CheckSender tests the Sender portion against the Name in the rule and returns the result (TRUE or FALSE).

The CheckSubject and CheckPriority functions work the same way. The only difference is that the CheckPriority function does not test for CI ("is contained in"). Add the CheckSubject function and enter the code from Listing 11.43.


Listing 11.43. Adding the CheckSubject routine.
Public Function CheckSubject(nRule As Integer, cSubjMsg As String) As Boolean
    '
    ' check subject against message test
    '
Dim nPos As Integer
    Dim cSubjRule As String
    '
    nPos = InStr(cRuleTest(nRule), " ")
    If nPos <> 0 Then
        cSubjRule = Trim(Mid(cRuleTest(nRule), nPos + 1, 255))
    End If
    '
    cSubjRule = UCase(Trim(cSubjRule))
    cSubjMsg = UCase(Trim(cSubjMsg))
    '
    Select Case UCase(cRuleCompare(nRule))
        Case UCase(cIsEqualTo)
            If cSubjRule = cSubjMsg Then
                CheckSubject = True
            Else
                CheckSubject = False
            End If
        Case UCase(cIsLessThan)
            If cSubjRule < cSubjMsg Then
                CheckSubject = True
            Else
                CheckSubject = False
            End If
        Case UCase(cIsGreaterThan)
            If cSubjRule > cSubjMsg Then
                CheckSubject = True
            Else
                CheckSubject = False
            End If
        Case UCase(cIsContainedIn)
            If InStr(cSubjRule, cSubjMsg) <> 0 Then
                CheckSubject = True
            Else
                CheckSubject = False
            End If
    End Select
    '
End Function

Finally, add the CheckPriority function and enter the code from Listing 11.44.


Listing 11.44. Adding the CheckPriority routine.
Public Function CheckPriority(nRule As Integer, nImpMsg) As Boolean
    '
    ' check subject against message test
    '
    Dim nPos As Integer
    Dim cImpRule As String
    Dim nImpRule As Integer
    '
    nPos = InStr(cRuleTest(nRule), " ")
    If nPos <> 0 Then
        cImpRule = Trim(Mid(cRuleTest(nRule), nPos + 1, 255))
    End If
    '
    nImpRule = Val(cImpRule)
    '
    Select Case UCase(cRuleCompare(nRule))
        Case UCase(cIsEqualTo)
            If nImpRule = nImpMsg Then
                CheckPriority = True
            Else
                CheckPriority = False
            End If
        Case UCase(cIsLessThan)
            If nImpRule < nImpMsg Then
                CheckPriority = True
            Else
                CheckPriority = False
            End If
        Case UCase(cIsGreaterThan)
            If nImpRule > nImpMsg Then
                CheckPriority = True
            Else
                CheckPriority = False
            End If
    End Select
    '
End Function

If the Checknnn routine returns TRUE, an action must take place. The DoAction routine is used to execute the appropriate e-mail action. DoAction accepts the index to the rule as its only parameter. Like the CheckRule routine, DoAction uses a SELECT CASE structure to act on each command word (NOTIFY, COPY, MOVE, FORWARD, and REPLY).

Add the DoAction subroutine to the project and enter the code shown in Listing 11.45.


Listing 11.45. Adding the DoAction routine.
Public Sub DoAction(nRule As Integer)
    '
    ' handle valid action
    ' nRule points to rule in array
    ' use current objMessage
    '
    Dim cCmd As String ' action command
    Dim cTarget As String ' action target
    Dim nPos As Integer
    '
    ' get command and target
    cCmd = ParseWord(cRuleAction(nRule))
    nPos = InStr(cRuleAction(nRule), " ")
    If nPos <> 0 Then
        cTarget = Trim(Mid(cRuleAction(nRule), nPos + 1, 255))
    End If
    '
' now execute command
    Select Case UCase(cCmd)
        Case UCase(cActionMove)
            MsgMoveCopy "MOVE", cTarget, objMessage
        Case UCase(cActionCopy)
            MsgMoveCopy "COPY", cTarget, objMessage
        Case UCase(cActionForward)
            MsgFwdReply "FORWARD", cTarget, objMessage
        Case UCase(cActionReply)
            MsgFwdReply "REPLY", cTarget, objMessage
        Case UCase(cActionNotify)
            MsgNotify cTarget, objMessage
    End Select
    '
End Sub

There are only three routines used to act on all five commands. This is because the FORWARD and REPLY commands act on messages, and the COPY and MOVE commands act on folders. Only one routine is needed for each (with slight behavior changes within each routine).

The NOTIFY option is the easiest to handle. All that is needed is a pop-up dialog box when the message arrives. Add the MsgNotify routine and type in the code from Listing 11.46.


Listing 11.46. Adding the MsgNotify routine.
Public Sub MsgNotify(cNotify As String, objMsg As Object)
    '
    Dim cMsg As String
    '
    cMsg = "Message Notification for ["
    cMsg = cMsg & objMsg.subject
    cMsg = cMsg & "] from ["
    cMsg = cMsg & objMsg.Sender.Name & "]"
    '
    ' send out pop-up?
    If cNotifyDialogValue = "1" Then
        MsgBox cMsg, vbExclamation, "MAPI Email Agent Notification"
    End If
    LogWrite (cMsg)
    '
End Sub

The routine needed for forwarding and replying to messages involves making a new message (with the contents of the original) and sending it to a new address. Since this is actually a messaging operation, you'll use the .Send function to force the message into the transport for delivery. Add the MsgFwdReply subroutine and enter the code in Listing 11.47.


Listing 11.47. Adding the MsgFwdReply routine.
Public Sub MsgFwdReply(cEvent As String, cDestAddr As String, objMsg As Object)
    '
    Dim cMsg As String
    '
    Dim objLocalMsgColl As Object
    Dim objCopyMsg As Object
    Dim cHeader As String
    Dim cSubjPrefix As String
    '
    cTarget = UCase(cEvent)
    '
    cHeader = "<------ Message " & cEvent
    cHeader = cHeader & " from ["
    cHeader = cHeader & objSession.Name
    cHeader = cHeader & "] by MAPI Email Agent ------>"
    cHeader = cHeader & Chr(13) & Chr(10) & Chr(13) & Chr(10)
    '
    If cEvent = "REPLY" Then
        cSubjPrefix = "RE: "
    Else
        cSubjPrefix = "FW: "
    End If
    '
    Set objLocalMsgColl = objSession.Outbox.Messages
    Set objCopyMsg = objLocalMsgColl.Add
    With objCopyMsg
        .subject = cSubjPrefix & objMsg.subject
        .Text = cHeader & objMsg.Text
    End With
    '
    ' add recipient
    Set objRecipient = objCopyMsg.Recipients.Add
    objRecipient.Name = cDestAddr
    objRecipient.Type = mapiTo
    objCopyMsg.Recipients.Resolve showdialog:=False
    objCopyMsg.Update
    objCopyMsg.Send showdialog:=False
    '
    ' delete old message?
    If cDelFwdFlagValue = "1" And cEvent = "FORWARD" Then
        objMessage.Delete
    End If
    If cDelReplyFlagValue = "1" And cEvent = "REPLY" Then
        objMessage.Delete
    End If
    objSession.Outbox.Update
    '
    ' send out status
    cMsg = cEvent & " Message ["
    cMsg = cMsg & objMsg.subject
    cMsg = cMsg & "] to [" & cDestAddr & "]"
    '
    LogWrite (cMsg)
    '
End Sub

There are a few things to keep in mind about this routine. First, a good REPLY routine should allow users to attach, or append, a text message to the original. Here, that code is left out for brevity. You'll also notice that only one recipient is added to the note. In some cases, it is possible that more than one person should receive the forwarded message. This can be handled by using distribution lists. Finally, there is no code here to handle any attachments to the original note. This should be added in a production environment.

The other action to be handled by the MAPI Email Agent is copying or moving messages to other folders. This is accomplished using the .Update method. Moving messages is similar to posting them. For this reason you do not want to attempt to "send" the message. Add the MsgMoveCopy subroutine and enter the code shown in Listing 11.48.


Listing 11.48. Adding the MsgMoveCopy routine.
Public Sub MsgMoveCopy(cEvent As String, cFolder As String, objMsg As Object)
    '
    Dim objLocalFolder As Object
    Dim objLocalMsg As Object
    Dim objLocalRecipient As Object
    '
    Dim cMsg As String
    Dim cFldrID As String
    Dim cRecipName As String
    Dim cHeader As String
    Dim i As Integer
    '
    ' carry sender info with you
    cHeader = "<------ " & UCase(cEvent) & " from ["
    cHeader = cHeader & objMsg.Sender.Name
    cHeader = cHeader & "] by MAPI Email Agent ------>"
    cHeader = cHeader & Chr(13) & Chr(10) & Chr(13) & Chr(10)
    '
    ' look for folder
    cFldrID = FindFolder(cFolder)
    If cFldrID = "" Then
        Exit Sub
    End If
    '
    ' move to folder
    Set objLocalFolder = objSession.GetFolder(cFldrID)
    Set objLocalMsg = objLocalFolder.Messages.Add
    '
    ' copy from objmsg to objlocalmsg
    With objLocalMsg
        .DeliveryReceipt = objMsg.DeliveryReceipt
        .Encrypted = objMsg.Encrypted
        .importance = objMsg.importance
        .ReadReceipt = objMsg.ReadReceipt
        .Sent = objMsg.Sent
        .Signed = objMsg.Signed
        .subject = objMsg.subject
        .Submitted = objMsg.Submitted
        .Text = cHeader & objMsg.Text
        .TimeReceived = objMsg.TimeReceived
        .TimeSent = objMsg.TimeSent
        .Type = objMsg.Type
        .Unread = objMsg.Unread
    End With
    '
    ' add recipients
    For i = 1 To objMsg.Recipients.Count Step 1
        cRecipName = objMsg.Recipients.Item(i).Name
        If cRecipName <> "" Then
            Set objLocalRecipient = objLocalMsg.Recipients.Add
            objLocalRecipient.Name = cRecipName
        End If
    Next i
    '
    ' now save the update
    objLocalMsg.Update
    '
    ' if a move, dump the original message
    If UCase(cEvent) = "MOVE" Then
        objMsg.Delete
    End If
    '
    ' send out notices
    cMsg = UCase(cEvent) & " Message ["
    cMsg = cMsg & objMsg.subject
    cMsg = cMsg & "] to [" & cFolder & "]"
    LogWrite (cMsg)
    '
End Sub

Again, a few things worth pointing out here. First, moving or copying messages to other folders requires that you actually find the target folder first. This is not as simple as looking up a name in a list. MAPI message stores are recursively hierarchical. That means that folders can exist within folders. And MAPI does not publish a list of the folder tree-you must traverse it each time yourself. This means you need your own FindFolder routine (you'll add that in a minute).

Warning
Since traversing the folder tree can be time-consuming, you might be tempted to build a tree at startup and use that throughout your program. Be careful! Since the Microsoft Exchange message stores can be shared among users, the tree can change rather rapidly. Not only could you encounter new folders or discover that old folders have been deleted, it is also possible that your target folder has been moved. It is a good idea to traverse the folder tree each time you attempt to find a folder.

Notice that the process of moving a message really involves creating a copy in the new folder. The code here copies the most commonly used items, but does not copy any attachments. Keep in mind that some properties may not exist for some messages. You'll need error trapping to prevent program crashes.

The last routine you need for the MAPI Email Agent application is the FindFolder function. This routine accepts a folder name and returns its unique MAPI ID value. Add the FindFolder function and enter the code shown in Listing 11.49.


Listing 11.49. Adding the FindFolder function.
Public Function FindFolder(cFldrName As String) As String
    '
    ' see if you can locate the requested folder
    ' if  found, return the unqiue Folder ID
    '
    Dim cRtnID As String
    Dim cTempID As String
    Dim objInfoStore As Object
    Dim objTempFldr As Object
    Dim objTempColl As Object
    Dim x As Integer
    '
    cRtnID = "" ' assume not found
    '
    ' scan the folder collection
    Set objTempColl = objSession.Inbox.Folders
    Set objTempFldr = objTempColl.GetFirst
    Do
        cTempID = objTempFldr.ID
        If UCase(objTempFldr.Name) = UCase(cFldrName) Then
            cRtnID = objTempFldr.ID
            Exit Do
        End If
        Set objTempFldr = objTempColl.GetNext
    Loop While objTempFldr.Name <> ""
    '
    FindFolder = cRtnID ' return the result
    '
End Function

You'll notice that the MAPI Email Agent inspects only the top-level folder collection of the Session.InBox. This is done to keep the code brief. If you wanted to search all the folders, you'd need to start at the Session.RootFolder and, after collecting the folders, inspect each of them for the existence of folders, and so on and so forth. For now, the MAPI Email Agent program only works with folders in the inbox. You can add code to expand the search capabilities of the Findfolder routine in future MAPI projects.

Save this module (MEA.BAS) and the project (MEA.VBP) before you start testing the MAPI Email Agent.

Installing and Testing the MAPI Email Agent

After you have completed the MAPI Email Agent project, you can install it on any workstation that has access to MAPI services. You do not have to have Microsoft Exchange or Microsoft Mail clients running in order to use the MAPI Email Agent.

In order to set up MAPI Email Agent to process your incoming mail you need to build test, action, and rule records. You also need to use your Microsoft Exchange or Microsoft Mail client to build any new target folders you want to include as parts of COPY or MOVE actions.

Once your folders are built and your rules are entered, you can launch the MAPI Email Agent at startup each day and let the program do its thing!

The next few sections talk you through a simple example of running the MAPI Email Agent on your workstation.

Setting Up Your Email Folders

First, start up your MAPI e-mail client and make sure you have the following folders within your inbox:

After adding these folders to your inbox, it should look something like the one in Figure 11.4.

Figure 11.4 : Adding folders to your inbox.

You'll use these folders as destinations when you add rules to your MAPI Email Agent database.

Building MAPI Email Agent Actions, Tests, and Rules

Next you need to add test, action, and rule records to the MAPI Email Agent database. Start the MAPI Email Agent program and enter the tests and actions shown in Table 11.4.

Table 11.4. MAPI Email Agent tests and actions.
Record TypeContents
TestSENDER Boss
 SUBJECT MAPI
 SUBJECT SALES
 PRIORITY 0
 SENDER Assistant
ActionFORWARD Boss
  MOVE Urgent
  COPY MAPI
  REPLY Sales
  NOTIFY Me

Figure 11.5 shows how the MAPI Email Agent looks after the tests and actions have been entered.

Figure 11.5 : Adding the test and action records.

Now you're ready to create some rules for the MAPI Email Agent. Enter the rules shown in Table 11.5.

Table 11.5. Adding rules to the MAPI Email Agent.
Rule NameTest CompareAction
Bosses Mail SENDER Boss EQ COPY Urgent
Save MAPI SUBJECT MAPI CI COPY MAPI
Assistant Routing SENDER Assistant EQ FORWARD Boss
Wake Me Up PRIORITY 0 EQ NOTIFY Me
Sales Handler SUBJECT SALES CI REPLY Sales

Figure 11.6 shows what your screen should look like as you build your MAPI Email Agent rules.

Figure 11.6 : Building MAPI Agent rules.

You're now ready to test your MAPI Email Agent!

Running the MAPI Email Agent

You can easily test the MAPI Email Agent by sending yourself some messages that meet the entered criteria. Send yourself a note that has MAPI in the subject or send a note that is marked high priority. Once the messages are sent, click the Start Timer button on the MAPI Email Agent to scan your inbox. You'll see messages along the status bar as the program scans; when the program finishes, check the log file by clicking the View Log button.

Summary

In this chapter you learned how to use the OLE Messaging library to create a stand-alone
e-mail agent. This agent can scan your incoming mail and, based on rules you establish, automatically handle messages for you. All actions are based on rules you establish in a control file.

Features of the MAPI Email Agent include:

You also learned that the process of sending messages is handled differently than posting messages (.Send versus .Update), and you learned how to traverse the folder tree to locate a desired MAPI storage folder.