Chapter 9

Creating a MAPI Mailing List Manager with the OLE Messaging Library


CONTENTS


Introduction

After reviewing the OLE Messaging Library objects in Chapter 8, "The OLE Messaging Library," you're now ready to build a MAPI application for Win95 and Visual Basic 4.0 that uses these objects.

The Mailing List Manager application lets users define and manage automated mailing lists from the client desktop. Messages can be distributed within a single server or across the Internet (depending on the available transports at the desktop). All access to MAPI services will be performed through the OLE Message objects.

The key features of the Mailing List Manager (MLM) are:

The MLM application allows individuals to create a set of text files to be distributed to a
controlled list of users at specified times. This project has only one simple form and several support routines. All application rules are stored in a set of ASCII control files similar to INI/registry settings. These control files can be changed by the list manager to determine how the mailing list operates and what features are available to subscribers.

Once you complete this application, you'll be able to establish and manage one or more one-way mailing lists from your own desktop. These mailing lists can be limited to your current attached server or cross over any transport out onto the Internet (depending on the transports installed on your desktop).

Laying Out the MLM Form

The MLM application has only one form. Since the primary purpose of the application is to manage automated lists, there is very little needed in the way of a GUI interface. MLM has a set of command buttons to initiate specific tasks and a single scrollable text box to show progress as the application processes incoming and outgoing mail.

Start a new Visual Basic project and lay out the MLM form. Refer to Table 9.1 and Figure 9.1 for details on the size and position of the controls on the form.

Figure 9.1 : Laying out the MLM form.

Table 9.1. MLM form controls.
ControlProperty Setting
FormName frmMLM
 Height 5325
 Left 1140
 Top 1230
 Width 7380
Text BoxName Text1
 Font 8pt MS Sans Serif, Bold
 Height 4635
 Left 120
 MultiLine -1 ' True
 Scrollbars 2 ' Vertical
 Top 120
 Width 5115
Command ButtonName Command1
 Caption Edit &Controls
 Font 8pt MS Sans Serif, Bold
 Height 450
 Index 0
 Left 5400
 Top 120
 Width 1800
Command ButtonName Command1
 Caption Edit &Subs
 Font 8pt MS Sans Serif, Bold
 Height 450
 Index 1
 Left 5400
 Top 720
 Width 1800
Command ButtonName Command1
 Caption Edit &Sked
 Font 8pt MS Sans Serif, Bold
 Height 450
 Index 2
 Left 5400
 Top 1320
 Width 1800
Command ButtonName Command1
 Caption Edit &Archive
 Font 8pt MS Sans Serif, Bold
 Height 450
 Index 3
 Left 5400
 Top 1920
 Width 1800
Command ButtonName Command1
 Caption Read &Inbox
 Font 8pt MS Sans Serif, Bold
 Height 450
 Index 4
 Left 5400
 Top 2520
 Width 1800
Command ButtonName Command1
 Caption Send &Mail
 Font 8pt MS Sans Serif, Bold
 Height 450
 Index 5
 Left 5400
 Top 3120
 Width 1800
Command ButtonName Command1
 Caption &Read && Send
 Font 8pt MS Sans Serif, Bold
 Height 450
 Index 6
 Left 5400
 Top 3720
 Width 1800
Command ButtonName Command1
 Caption E&xit
 Font 8pt MS Sans Serif, Bold
 Height 450
 Index 7
 Left 5400
 Top 4320
 Width 1800

Note that the layout table calls for a control array of command buttons. Add a single button to the form, set its properties (including the Index property), and then use the Edit | Copy, Edit | Paste menu options to make the additional copies of the button. You can then edit the Caption properties as needed.

After you lay out the form, you need to add a handful of variables to the general declaration area, a few routines to handle the standard form events, and one routine to respond to the command-button actions. Listing 9.1 shows the code that declares the form-level variables for this project. Add this code to the general declaration area of the form.


Listing 9.1. Adding the form-level variables.
Option Explicit

Dim objMAPISession As Object    ' session object
Dim cMLMFile As String          ' control file
Dim cCtlName() As String        ' control names
Dim cCtlValue() As String       ' control values
Dim bErr As Boolean             ' error flag
Dim nWidth As Integer           ' form width
Dim nHeight As Integer          ' form height
Dim EOL As String               ' end of line

Next, add the code in Listing 9.2 to the Form_Load event. This code centers the form and then stores its current width and height. This information will be used to prevent users from resizing the form at run-time.


Listing 9.2. Adding code to the Form_Load event.
Private Sub Form_Load()
    '
    Me.Top = (Screen.Height - Me.Height) / 2
    Me.Left = (Screen.Width - Me.Width) / 2
    '
    nWidth = Me.Width
    nHeight = Me.Height
    EOL = (Chr(13) & Chr(10))
    Text1 = ""
    Me.Caption = "Mailing List Manager [" & cMLMFile & "]"
    '
    ' check for passed parm
    '
    If Len(Command$) <> 0 Then
        cMLMFile = Command$
    Else
        cMLMFile = "mlm.txt"
    End If
    '
End Sub

You'll also notice that the Form_Load event checks for a parameter passed on the command line at startup. This will be used to determine what set of control files will be used for each run of the MLM application (you'll see more about this later).

Next, add the code in Listing 9.3 to the Form_Resize event. This code uses the values established in the Form_Load event to keep forcing the form back to its original size whenever a user tries to adjust the form size. Note, however, that this routine will allow users to minimize the form.


Listing 9.3. Adding code to the Form_Resize event.
Private Sub Form_Resize()
    '
    If Me.WindowState <> vbMinimized Then
        Me.Width = nWidth
        Me.Height = nHeight
    End If
    '
End Sub

You also need to add code behind the command-button control array. Listing 9.4 contains the code that should be placed in the Command1_Click event. This routine just calls a set of custom subroutines that you'll add a bit later in the chapter.


Listing 9.4. Adding the code in the Command1_Click event.
Private Sub Command1_Click(Index As Integer)
    '
    ' handle user clicks
    '
    Select Case Index
        Case 0 ' edit controls
            ControlsEdit
        Case 1 ' edit subs
            SubEdit
        Case 2 ' edit schedule
            SkedEdit
        Case 3 ' edit archive
            ArchEdit
        Case 4 ' read inbox
            ReadInbox
        Case 5 ' send mail
            SendMail
        Case 6 ' read & send
            ReadInbox
            SendMail
        Case 7 ' exit
            Unload Me
    End Select
    '
End Sub

One more line of code is needed to complete this section. The text box control should be a read-only form object. By adding the following line of code to the Text1_KeyPress event, you can trick Visual Basic into ignoring any keyboard input performed within the text box control.

Private Sub Text1_KeyPress(KeyAscii As Integer)
    KeyAscii = 0
End Sub

That's the code needed to support form events and controls. Save this form as MLM.FRM and save the project as MLM.VBP. In the next section you'll add a series of simple support routines to the project.

Coding the Support Routines

Now you'll add a few support routines that are called frequently from other, high-level routines in the project. You'll add all these routines to the general declaration section of the form.

First, add a new subroutine called Status, and add the code shown in Listing 9.5.


Listing 9.5. Adding the Status routine to the project.
Public Sub Status(cInfo As String)
    '
    ' send info to status line
    '
    If cInfo = "" Then
        Text1 = ""
    Else
        Text1 = Text1 & cInfo & Chr(13) & Chr(10)
    End If
    '
End Sub

The code in the Status routine places a new line in the text box. This will be used to pass progress information to the text box control as the MLM is processing subscriber lists and the Microsoft Exchange inbox.

The MLM project gets its primary instructions from a set of ASCII text control files. The next routine you'll build in this section is the one that reads the master control file. Add a new subroutine called ControlsLoad to the project, and enter the code shown in Listing 9.6.


Listing 9.6. Adding the ControlsLoad routine.
Public Sub ControlsLoad()
    '
    ' load control values into variables
    '
    On Error GoTo ControlsLoadErr
    '
    Status ""
    Status "Loading Control Values..."
    '
    Dim nFile As Integer
    Dim nCount As Integer
    Dim cLine As String
    Dim nPos As Integer
    '
    bErr = False
    nCount = 0
    nFile = FreeFile
    '
    Open cMLMFile For Input As nFile
    While Not EOF(nFile)
        Line Input #nFile, cLine
        If Left(cLine, 1) <> ";" Then
            nPos = InStr(cLine, "=")
            If nPos <> 0 Then
                nCount = nCount + 1
                ReDim Preserve cCtlName(nCount)
                ReDim Preserve cCtlValue(nCount)
                cCtlName(nCount) = Left(cLine, nPos - 1)
                cCtlValue(nCount) = Mid(cLine, nPos + 1, 255)
            End If
        End If
    Wend
    Close #nFile
    Exit Sub
    '
ControlsLoadErr:
    MsgBox Error$, vbCritical, "ControlsLoad Error [" & CStr(Err) & "]"
    bErr = True
    '
End Sub

Notice that the ControlsLoad routine reads each line of the ASCII text file, and if it is not a comment line (that is, it starts with a ";"), it parses the line into a control name array and a control value array. You'll use these values throughout your project.

Now that the control values are stored in a local array, you need a routine to retrieve a particular control value. Add a new function (not a subroutine) to the project called ControlSetting, and add the code shown in Listing 9.7.


Listing 9.7. Adding the ControlSetting function.
Public Function ControlSetting(cName As String) As String
    '
    ' look up control setting
    '
    Dim cReturn As String
    Dim nCount As Integer
    Dim x As Integer
    '
    nCount = UBound(cCtlName)
    cName = UCase(cName)
    '
    For x = 1 To nCount
        If cName = UCase(cCtlName(x)) Then
            cReturn = cCtlValue(x)
            Exit For
        End If
    Next x
    '
    ControlSetting = cReturn
    '
End Function

The ControlSetting function accepts a single parameter (the name of the control value you are requesting) and returns a single value (the value of the control setting you named). This routine accomplishes its task by simply reading through the array of control names until the name is found.

That's all for the general support routines. Save this form and project again before continuing.

Coding the Edit Routines

This next set of routines allows users to edit the various control files required to manage the project. You'll use a call to the NOTEPAD.EXE applet to edit the control files. This is much easier than spending the time to write your own text file editor. Also, the first time you call these routines you'll be prompted to create the new files.

Add a new subroutine called ControlsEdit to the form, and enter the code shown in Listing 9.8.


Listing 9.8. Adding the ControlsEdit routine.
Public Sub ControlsEdit()
    '
    Dim rtn As Long
    Dim cEditor
    '
    ControlsLoad
    If bErr <> True Then
        cEditor = ControlSetting("Editor")
        '
        Status "Opening Control File [" & cMLMFile & "]..."
        rtn = Shell(cEditor & " " & cMLMFile, 1)
        Status "Closing Control File..."
    End If
    '
End Sub

This routine first attempts to load the master control values, then launches the default editor to allow users to modify those values. You can also see the use of the Status routine to update the form's text box. Go back to the Command1_Click routine (see Listing 9.4) and remove the comment from in front of the ControlsLoad command. Then save this project.

Before you can run this routine, you need to create the default control file. Start NOTEPAD.EXE and enter the information shown in Listing 9.9. Once you complete the entry, save the file in the same folder as the MLM project and call it MLM.TXT.

Tip
If you get errors attempting to launch the editor from these routines, you can include the drive and path qualifiers in the Editor control value.


Listing 9.9. Creating the default MLM.TXT control file.
; ===================================================
; Mailing List Control values for MLM
; ===================================================
;
; read by MLM.EXE
;
; ===================================================
;
MAPIUserName=MCA
MAPIPassword=
SearchKey=MLM
ListName=MLM Mailing List
NewSub=SUB
NewSubMsg=MLMHello.txt
UnSubMsg=MLMBye.txt
UnSub=UNSUB
GetArchive=GET
ListArchive=LIST
ArchiveFile=MLMArch.txt
ListSchedule=MLMSked.txt
ListSubs=MLMSubs.txt
Editor=notepad.exe

Tip
If you don't want to spend time entering this control file information, you can find it in the MLM folder that was created when you installed the source code from the CD-ROM.

There are several entries in this control file. For now, make sure that the control names and values are entered correctly. You'll learn more about how each one works as you go along. Once you get the hang of the control file, you can modify it to suit your own mailing-list needs.

Now add a new subroutine, called SubEdit, to allow the editing of the subscriber list. Enter the code in Listing 9.10 into the routine.


Listing 9.10. Adding the SubEdit routine.
Public Sub SubEdit()
    '
    Dim lReturn As Long
    Dim cEditor As String
    Dim cFile As String
    '
    ControlsLoad
    If bErr <> True Then
        cFile = ControlSetting("ListSubs")
        cEditor = ControlSetting("Editor")
        Status "Loading Subscriber List [" & cFile & "]..."
        lReturn = Shell(cEditor & " " & cFile, 1)
        Status "Closing Subscriber List..."
    End If
    '
End Sub

Normally you will not need to pre-build the subscriber file. It will be created as you add new subscribers to your mailing list via e-mail requests. However, for testing purposes, open up Notepad and enter the values shown in Listing 9.11. When you are done, save the file as MLMSUBS.TXT in the same folder as the Visual Basic project.


Listing 9.11. Creating the test MLMSUBS.TXT file.
; ====================================================
; Mailing List Subscriber File
; ====================================================
;
; Read by MLM.EXE
;
; format:name^address^transport
;
; where:name      = display name
;    address   = e-mail address
;    transport = MAPI transport
;
;    example:Mike Amundsen^mamund@iac.net^SMTP
;
; ====================================================
;
Michael C. Amundsen^mamund@iac.net^SMTP
Mike Amundsen^102461,1267^COMPUSERVE

The addresses in the file may not be valid e-mail addresses on your system, but they illustrate the format of the file. Each address entry has three parts:

As users request to be on your mailing list, their mailing information is added to this file. Later in the chapter, you'll add yourself to this list by sending yourself an e-mail request.

The next control file needed for the MLM application is the schedule file. This control file contains information on the display name, complete filename, and scheduled delivery date of messages to be sent by MLM. Create a new routine called SkedEdit, and add the code in Listing 9.12.


Listing 9.12. Adding the SkedEdit routine.
Public Sub SkedEdit()
    '
    Dim rtn As Long
    Dim cFile As String
    Dim cEditor As String
    '
    ControlsLoad
    If bErr <> True Then
        cFile = ControlSetting("ListSchedule")
        cEditor = ControlSetting("Editor")
        Status "Opening Schedule [" & cFile & "]..."
        rtn = Shell(cEditor & " " & cFile, 1)
        Status "Closing Schdule..."
    End If
    '
End Sub

You'll need to create a default schedule file for this project. Listing 9.13 shows the schedule file format. Use NOTEPAD.EXE to build this file and save it in the project directory as MLMSKED.TXT.


Listing 9.13. Building the MLMSKED.TXT file.
; ==================================================
; Mailing List Schedule file
; ==================================================
;
; read by MLM.EXE
;
; format:    YYMMDD,uafn,title
;
; where:    YYMMDD = Year, Month, Day
;       uafn   = unambiguous file name
;       title  = descriptive title
;
; example:    960225,MLMFAQ.txt,MLM FAQ Document
;
; ==================================================

960225,mlmhello.txt,Hello and Welcome to MLM!
960226,mlmbye.txt,Goodbye - We'll Miss You!

You can see from the sample file that there are three control values for each entry:

As you build your mailing list message base, you can add lines to this control file.

The last edit routine to add to the project is the one used to edit the archive list. Add a new subroutine called ArchEdit to the project, and enter the code shown in Listing 9.14.


Listing 9.14. Adding the ArchEdit routine.
Public Sub ArchEdit()
   '
    Dim rtn As Long
    Dim cEditor As String
    Dim cArchFile As String
    '
    ControlsLoad
    If bErr <> True Then
        cEditor = ControlSetting("Editor")
        cArchFile = ControlSetting("ArchiveFile")
        '
        Status "Opening Archive File [" & cArchFile & "]..."
        rtn = Shell(cEditor & " " & cArchFile, 1)
        Status "Closing Archive File..."
    End If
    '
End Sub

Again, you'll need to create an initial archive listing file before you first run your project. Use NOTEPAD.EXE to build a file called MLMARch.TXT and enter the data shown in Listing 9.15. Save this file in the project directory.


Listing 9.15. Creating the MLMARch.TXT file.
; ==================================================
; Mailing List Archive File
; ==================================================
;
; read by MLM.EXE
;
; format:    YYMMDD,uafn,title
;
; where:     YYMMDD = Year, Month, Day
;            uafn   = unambiguous file name
;            title  = descriptive name
;
; example:    960225,MLMFAQ.txt,MLM FAQ Document
;
; ==================================================

960225,mlmhello.txt,Hello and Welcome to MLM!
960226,mlmbye.txt,Goodbye - We'll Miss You!

This file format is identical to the one used in the MLMSKED.TXT file. The contents of this file can be requested by subscribers when they want to retrieve an old message in the database. By passing a GET YYMMDD line in the message subject, subscribers can get a copy of the archive file sent to them automatically.

This is the last of the edit routines for the project. Be sure to save this project before you continue.

Coding the MAPIStart and MAPIEnd routines

Before you can start processing messages, you need to build the routines that will start and end your MAPI sessions. Add a new subroutine called MAPIStart to the project, and enter the code that appears in Listing 9.16.


Listing 9.16. Adding the MAPIStart routine.
Public Sub MAPIStart()
    '
    On Error GoTo MAPIStartErr
    '
    Dim cProfile As String
    Dim cPassword As String
    Status "Starting MAPI Session..."
    '
    Set objMAPISession = CreateObject("MAPI.Session")
    cProfile = ControlSetting("MAPIUserName")
    cPassword = ControlSetting("MAPIPassword")
    objMAPISession.Logon profilename:=cProfile, profilePassword:=cPassword
    Exit Sub
    '
MAPIStartErr:
    MsgBox "Unable to Start a MAPI Session", vbCritical, "MAPIStart Error"
    bErr = True
    '
End Sub

Note the use of the OLE Messaging Library as the means of access into the MAPI system. Now add the MAPIEnd subroutine to your project and enter the code from Listing 9.17.


Listing 9.17. Adding the MAPIEnd routine.
Public Sub MAPIEnd()
    On Error Resume Next
    Status "Closing MAPI Session..."
    objMAPISession.Logoff
End Sub

These two routines are the start and end of the ReadMail and SendMail routines you'll add in the next two sections.

Coding the SendMail Routines

The next set of routines will check the schedule control file for the message of the day and automatically format and send messages to all subscribers on the mailing list. This is handled with three routines. The first is the high-level routine that is called from the command button routine. The other two routines handle the details of reading the schedule file, reading the subscriber file, and composing and sending the messages.

Create a new routine called SendMail and add the code shown in Listing 9.18.


Listing 9.18. Adding the SendMail routine.
Public Sub SendMail()
    '
    ' read mail
    '
    Status ""
    ControlsLoad
    MAPIStart
    ProcessSubList
    MAPIEnd
    Status "Outbound processing complete."
    MsgBox "Outbound processing complete", vbInformation, "SendMail"
    '
End Sub

The SendMail routine first clears the status box and loads the master control file. Then the MAPIStart routine is called. Once the MAPI session is established, the routine calls ProcessSubList to handle all processing of the subscriber list. After the list is processed, the MAPIEnd routine is called and the status box is updated along with message to the user announcing the completion of the processing.

Next add the ProcessSubList subroutine, and enter the code shown in Listing 9.19.


Listing 9.19. Adding the ProcessSubList routine.
Public Sub ProcessSubList()
    '
    ' read sublist to send messages
    '
    Dim cErr As String
    '
    If bErr <> 0 Then
        Exit Sub
    Else
        bErr = False
    End If
    '
    Dim cSubList As String
    Dim nSubList As Integer
    Dim cListSked As String
    Dim nListSked As Integer
    Dim cSkedFile As String
    Dim cFileDate As String
    Dim cFileName As String
    Dim cFileTitle As String
    Dim cLine As String
    Dim nPos1 As Integer
    Dim nPos2 As Integer
    '
    cSubList = ControlSetting("ListSubs")
    cListSked = ControlSetting("ListSchedule")
    cSkedFile = Format(Now, "YYMMDD")
    '
    Status "Opening Schedule File [" & cListSked & "]..."
    nListSked = FreeFile
    Open cListSked For Input As nListSked
    '
    On Error Resume Next
    Do While Not EOF(nListSked)
        Line Input #nListSked, cLine
        nPos1 = InStr(cLine, ",")
        If nPos1 <> 0 Then
            cFileDate = Left(cLine, nPos1 - 1)
        End If
        nPos2 = InStr(nPos1 + 1, cLine, ",")
        If nPos2 <> 0 Then
            cFileName = Mid(cLine, nPos1 + 1, nPos2 - (nPos1 + 1))
        End If
        If nPos2 + 1 < Len(cLine) Then
            cFileTitle = Mid(cLine, nPos2 + 1, 255)
        Else
            cFileTitle = cFileName
        End If
        If cFileDate = cSkedFile Then
            Exit Do
        End If
    Loop
    Close nListSked
    '
    Status "Opening Subscriber List [" & cSubList & "]..."
    nSubList = FreeFile
Listing 9.19. continued
    Open cSubList For Input As nSubList
    '
    Do While Not EOF(nSubList)
        Line Input #nSubList, cLine
        If Left(cLine, 1) <> ";" Then
            ProcessSubListMsg cLine, cFileName, cFileTitle
        End If
    Loop
End Sub

The main job of the ProcessSubList routine is to open the schedule file, and see if there is a message to send for today's date. If one is found, the routine opens the subscriber control file and calls the ProcessSubListMsg routine to compose and send the message.

Finally, add the ProcessSubListMsg routine and enter the code that appears in Listing 9.20.


Listing 9.20. Adding the ProcessSubListMsg routine.
Public Sub ProcessSubListMsg(cListAddr As String, cFile As String, cTitle As ÂString)
    '
    ' send out message
    '
    Dim nFile As Integer
    Dim cLine As String
    Dim cMsgBody As String
    Dim cType As String
    Dim cAddr As String
    Dim cName As String
    Dim objMsg As Object
    Dim objRecip As Object
    Dim nPos1 As Integer
    Dim nPos2 As Integer
    '
    ' parse address line
    nPos1 = InStr(cListAddr, "^")
    If nPos1 <> 0 Then
        cName = Left(cListAddr, nPos1 - 1)
    Else
        Exit Sub
    End If
    '
    nPos2 = InStr(nPos1 + 1, cListAddr, "^")
    If nPos2 <> 0 Then
        cAddr = Mid(cListAddr, nPos1 + 1, nPos2 - (nPos1 + 1))
    Else
        Exit Sub
    End If
    '
    cType = Mid(cListAddr, nPos2 + 1, 255)
    '
    ' now create message
    Status "Sending Msg to " & cName & "..."
    '
    ' get message text
    nFile = FreeFile
    '
    Open cFile For Input As nFile
    While Not EOF(nFile)
        Line Input #nFile, cLine
        cMsgBody = cMsgBody & EOL & cLine
    Wend
    Close #nFile
    '
    ' now build a new message
    Set objMsg = objMAPISession.Outbox.Messages.Add
    objMsg.subject = ControlSetting("ListName") & " [" & cTitle & "]"
    objMsg.Text = cMsgBody
    ' create the recipient
    Set objRecip = objMsg.Recipients.Add
    If cType = "MS" Then
        objRecip.Name = cName ' handle local users
        objRecip.Type = mapiTo
        objRecip.Resolve
    Else
        objRecip.Name = cType & ":" & cAddr
        objRecip.address = cType & ":" & cAddr
        objRecip.Type = mapiTo
    End If
    ' send the message
    objMsg.Update
    objMsg.Send showDialog:=False
    '
End Sub

The most important part of the ProcessSubListMsg routine is the last section of code that composes and addresses the message. There are two main processes in this part of the routine. The first process is the creation of a new Message object:

   ' now build a new message
    Set objMsg = objMAPISession.Outbox.Messages.Add
    objMsg.subject = ControlSetting("ListName") & " [" & cTitle & "]"
    objMsg.Text = cMsgBody

The second process is the creation of a new Recipient object and the addressing of the message:

' create the recipient
    Set objRecip = objMsg.Recipients.Add
    If cType = "MS" Then
        objRecip.Name = cName ' handle local users
        objRecip.Type = mapiTo
        objRecip.Resolve
    Else
        objRecip.Name = cType & ":" & cAddr
        objRecip.address = cType & ":" & cAddr
        objRecip.Type = mapiTo
    End If

Notice that addressing is handled a bit differently for MS-type messages. Messages with the address type of MS are addresses within the Microsoft addressing scheme-they're local addresses. To handle these items, you only need to load the Name property, set the recipient type (To:), and then call the MAPI Resolve method to force MAPI to look up the name in the address book(s). When the name is found, MAPI loads the Address property with the complete transport and e-mail address for routing. This is how most MAPI messages are usually sent.

However, for messages of type other than MS, it is likely that they are not in the locally available address books. These messages can still be sent if you load the Address property of the message with both the transport type and the user's e-mail address. This is the way to handle processing for messages that were sent to you from someone who is not in your address book. This is known as one-off addressing. One-off addressing ignores the Name property and uses the Address property to route the message.

That is all the code you need to send out daily messages to your subscriber list. The next set of routines will allow your application to scan incoming messages for mailing list-related items and process them as requested.

Tip
It is a good idea to save your project as you go along. The next set of routines are a bit longer and you may want to take a break before continuing.

Coding the Inbox Routines

The next set of routines handles the process of scanning the subject line of incoming messages for mailing-list commands. These commands are then processed and subscribers are added or dropped from the list and archive items are sent to subscribers as requested.

The Inbox processing can recognize four different commands on the subject line. These commands are:

Tip
The exact word used for each of these four commands is determined by settings in the master control file. See Listing 9.9 for an example of the master control file. If you want to change the values for these commands, you can do so in the control file. This is especially useful if you plan to manage more than one list from the same e-mail address. Adding prefixes to the commands will help MLM distinguish which command should be respected and which commands are for some other mailing list.

The first three routines for handling the inbox are rather simple. The first routine clears the progress box, loads the controls, calls the ProcessInbox routine, and performs cleanup functions upon return. Add the ReadInbox subroutine and add the code shown in Listing 9.21.


Listing 9.21. Adding the ReadInbox routine.
Public Sub ReadInbox()
    '
    ' read mail
    '
    Status ""
    ControlsLoad
    MAPIStart
    ProcessInbox
    MAPIEnd
    Status "Inbox processing complete."
    MsgBox "Inbox processing complete", vbInformation, "ReadInbox"
    '
End Sub

Next, add a new subroutine called ProcessInbox, and add the code that appears in Listing 9.22.


Listing 9.22. Adding the ProcessInbox routine.
Public Sub ProcessInbox()
    '
    ' read inbox for MLM messages
    '
    Dim cErr As String
    '
    If bErr <> 0 Then
        Exit Sub
    Else
        bErr = False
    End If
    '
    Dim objFolder As Object
    Dim objMsgColl As Object
    Dim objMessage As Object
    Dim cSubject As String
    '
Listing 9.22. continued
    Status "Opening Inbox..."
    Set objFolder = objMAPISession.Inbox
    If objFolder Is Nothing Then
        MsgBox "Unable to Open Inbox", vbCritical, "ProcessInbox Error"
        bErr = True
        Exit Sub
    End If
    '
    Status "Collecting Messages..."
    Set objMsgColl = objFolder.Messages
    If objMsgColl Is Nothing Then
        MsgBox "Unable to access Folder's Messages", vbCritical, "ProcessInbox ÂError"
        bErr = True
        Exit Sub
    End If
    '
    Status "Scanning Messages for [" & ControlSetting("SearchKey") & "]..."
    Set objMessage = objMsgColl.GetFirst
    Do Until objMessage Is Nothing
        cSubject = objMessage.subject
        If InStr(cSubject, ControlSetting("SearchKey")) Then
            ProcessInboxMsg objMessage
        End If
        Set objMessage = objMsgColl.GetNext
    Loop
    '
End Sub

This routine performs three main tasks. The first is to open the messages stored in the Inbox folder. Every message store has an Inbox folder. All new messages are sent to the Inbox folder upon receipt.

Status "Opening Inbox..."
    Set objFolder = objMAPISession.Inbox
    If objFolder Is Nothing Then
        MsgBox "Unable to Open Inbox", vbCritical, "ProcessInbox Error"
        bErr = True
        Exit Sub
    End If

The second step is to create a collection of all the messages in the Inbox folder. You access messages as a collection of objects in the folder.

Status "Collecting Messages..."
    Set objMsgColl = objFolder.Messages
    If objMsgColl Is Nothing Then
        MsgBox "Unable to access Folder's Messages", vbCritical, "ProcessInbox ÂError"
        bErr = True
        Exit Sub
    End If

The third process in this routine is to inspect each message in the collection to see if its subject line contains the search key word from the master control file. If found, the message is passed to the ProcessInboxMsg routine for further handling.

Status "Scanning Messages for [" & ControlSetting("SearchKey") & "]..."
    Set objMessage = objMsgColl.GetFirst
    Do Until objMessage Is Nothing
        cSubject = objMessage.subject
        If InStr(cSubject, ControlSetting("SearchKey")) Then
            ProcessInboxMsg objMessage
        End If
        Set objMessage = objMsgColl.GetNext
    Loop

Now add the ProcessInboxMsg subroutine. This routine checks the content of the message for the occurrence of MLM command words (SUB, UNSUB, LIST, GET). If one is found, the appropriate routine is called to handle the request. Enter the code shown in Listing 9.23.


Listing 9.23. Adding the ProcessInboxMsg routine.
Public Sub ProcessInboxMsg(objMsg As Object)
    '
    ' check out message subject
    '
    Dim cSubject As String
    '
    cSubject = UCase(objMsg.subject)
    '
    If InStr(cSubject, ControlSetting("NewSub")) Then
        ProcessInboxMsgNewSub objMsg
    End If
    '
    If InStr(cSubject, ControlSetting("UnSub")) Then
        ProcessInboxMsgUnSub objMsg
    End If
    '
    If InStr(cSubject, ControlSetting("GetArchive")) Then
        ProcessInboxMsgArcGet objMsg
    End If
    '
    If InStr(cSubject, ControlSetting("ListArchive")) Then
        ProcessInboxMsgArcList objMsg
    End If
    '
End Sub

In the next few sections you'll add supporting code to handle all the MLM subject-line commands. It's a good idea to save the project at this point before you continue.

Adding New Subscribers

When a person sends you an e-mail message with the words MLM SUB in the subject line, the MLM application adds that person's e-mail address to the subscriber list. The next set of routines handles all the processing needed to complete that task. This version of the program will also automatically send the new subscriber a greeting message (one that you designate in the control file).

First, add a new subroutine called ProcessInboxMsgNewSub to the project, and enter the code shown in Listing 9.24.


Listing 9.24. Adding the ProcessInboxMsgNewSub routine.
Public Sub ProcessInboxMsgNewSub(objMsg As Object)
    '
    ' add a new sub
    '
    Dim objAddrEntry As Object
    Dim cName As String
    Dim cAddress As String
    Dim cType As String
    '
    On Error Resume Next
    Set objAddrEntry = objMsg.Sender
    If SubFind(objAddrEntry) = False Then
        SubWrite objAddrEntry
        SubGreet objAddrEntry
    End If
    '
End Sub

This routine first checks to see if the name already exists in the subscriber control file. If not, it is added and the new subscriber is sent a friendly greeting message.

Create a new function called SubFind, and enter the code from Listing 9.25.


Listing 9.25. Adding the SubFind function.
Public Function SubFind(objAddr As Object) As Boolean
    '
    ' see if sub is in list
    '
    Dim cSubList As String
    Dim bReturn As Boolean
    Dim nFile As Integer
    Dim cRdLine As String
    Dim cSrchLine As String
    '
    cSubList = ControlSetting("ListSubs")
    cSrchLine = objAddr.Name & "^" & objAddr.address & "^" & objAddr.Type
    nFile = FreeFile
    bReturn = False
    '
    Open cSubList For Input As nFile
    While Not EOF(nFile)
        Line Input #nFile, cRdLine
        If cRdLine = cSrchLine Then
            bReturn = True
        End If
    Wend
    Close #nFile
    SubFind = bReturn
    '
End Function

The SubFind routine accepts one parameter (the name to look up), and returns True if the name is found and False if the name is not in the subscriber list.

Next, add the SubWrite subroutine to the project. The code for this routine is in Listing 9.26.


Listing 9.26. Adding the SubWrite routine.
Public Sub SubWrite(objAddr As Object)
    '
    ' write new address to subscriber list
    '
    Dim cSubList As String
    Dim nFile As Integer
    '
    cSubList = ControlSetting("ListSubs")
    Status "Adding New Sub..." & objAddr.Name
    '
    nFile = FreeFile
    Open cSubList For append As nFile
    Print #nFile, objAddr.Name; "^"; objAddr.address; "^"; objAddr.Type
    Close nFile
    '
End Sub

Notice that the SubWrite routine copies the Name, Address, and Type properties from the AddressEntry object into the subscriber control file. Each value is separated by a caret (^). This separator character was chosen somewhat arbitrarily. You can change it if you wish.

Warning
If you change the separator value, be sure you don't use a character that could be part of a valid e-mail address. E-mail addresses today can have a comma (,), colon (:), semicolon (;), slashes (/ or \), and other characters. You'll need to be careful when you choose your separator character!

Next add the SubGreet routine. This composes and sends a friendly greeting message to all new subscribers. Add the code from Listing 9.27 to your project.


Listing 9.27. Adding the SubGreet routine.
Public Sub SubGreet(objAddr As Object)
    '
    ' send new sub a welcome greeting
    '
    Dim cSubGreet As String
    Dim nFile As Integer
    Dim cLine As String
    Dim cMsgBody As String
    Dim objMsg As Object
    Dim objRecip As Object
    '
    Status "Sending Greet Msg to " & objAddr.Name & "..."
    '
    ' get greeting message text
    cSubGreet = ControlSetting("NewSubMsg")
    nFile = FreeFile
    '
    Open cSubGreet For Input As nFile
    While Not EOF(nFile)
        Line Input #nFile, cLine
        cMsgBody = cMsgBody & EOL & cLine
    Wend
    Close #nFile
    '
    ' now build a new message
    Set objMsg = objMAPISession.Outbox.Messages.Add
    objMsg.subject = "Welcome to the " & ControlSetting("ListName")
    objMsg.Text = cMsgBody
    ' create the recipient
    Set objRecip = objMsg.Recipients.Add
    If objAddr.Type = "MS" Then
        objRecip.Name = objAddr.Name ' handle local users
        objRecip.Type = mapiTo
        objRecip.Resolve
    Else
        objRecip.Name = objAddr.Type & ":" & objAddr.Address
        objRecip.address = objAddr.Type & ":" & objAddr.Address
        objRecip.Type = mapiTo
    End If
    ' send the message and log off
    objMsg.Update
    objMsg.Send showDialog:=False
    '
End Sub

The SubGreet routine looks similar to the routine used to send daily messages to subscribers. The message sent as the greeting pointed to the NewSubMsg parameter in the master control file.

Save your work before adding the code to drop subscribers from the list.

Dropping Subscribers

The routines needed to drop subscribers from the mailing list are very similar to the code needed to add them. You'll create a routine to respond to the request and two supporting routines-one to delete the name from the list, and one to send a goodbye message to the requester.

First add the ProcessInboxMsgUnSub subroutine to the project and enter the code from Listing 9.28.


Listing 9.28. Adding the ProcessInboxMsgUnSub routine.
Public Sub ProcessInboxMsgUnSub(objMsg As Object)
    '
    ' drop an existing sub
    '
    Dim objAddrEntry As Object
    Dim cName As String
    Dim cAddress As String
    Dim cType As String
    '
    On Error Resume Next
    Set objAddrEntry = objMsg.Sender
    If SubFind(objAddrEntry) = True Then
        SubDelete objAddrEntry
        SubBye objAddrEntry
    End If
    '
End Sub

This routine checks to make sure the name is in the subscriber list. If it is, then the name is dropped and a goodbye message is sent. Add the SubDelete routine to the project by copying the code from Listing 9.29.


Listing 9.29. Adding the SubDelete routine.
Public Sub SubDelete(objAddr As Object)
    '
    ' delete an address from the subscriber list
    '
    Dim cSubList As String
    Dim cSubTemp As String
    Dim nList As Integer
    Dim nTemp As Integer
    Dim cRdLine As String
    Dim cSrchLine As String
    '
    cSubList = ControlSetting("ListSubs")
    Status "Dropping a Sub..." & objAddr.Name
    cSrchLine = objAddr.Name & "^" & objAddr.address & "^" & objAddr.Type
    cSubTemp = "tmp001.txt"
    '
Listing 9.29. continued
    nList = FreeFile
    Open cSubList For Input As nList
    nTemp = FreeFile
    Open cSubTemp For Output As nTemp
    '
    While Not EOF(nList)
        Line Input #nList, cRdLine
        If cRdLine <> cSrchLine Then
        Print #nTemp, cRdLine
        End If
    Wend
    '
    Close #nList
    Close #nTemp
    Kill cSubList
    Name cSubTemp As cSubList
    '
End Sub

This routine accomplishes the delete process by copying all the valid names to a temporary file, and then erasing the old file and renaming the temporary file as the new master subscriber list. While this may seem a bit convoluted, it is the quickest and simplest way to handle deletes in a sequential ASCII text file.

Note
In a more sophisticated project, you could build the subscriber list in a database and use database INSERT and DELETE operations to manage the list.

Next add the SubBye routine. This sends a goodbye message to the subscriber that was just dropped from the list. The greeting message is kept in the text file pointed to by the value of the UnSubMsg control parameter in the master control file.

Add the code shown in Listing 9.30.


Listing 9.30. Adding the SubBye routine.
Public Sub SubBye(objAddr As Object)
    '
    ' send old sub a goodbye msg
    '
    Dim cSubBye As String
    Dim nFile As Integer
    Dim cLine As String
    Dim cMsgBody As String
    Dim objMsg As Object
    Dim objRecip As Object
    '
    Status "Sending Bye Msg to " & objAddr.Name & "..."
    '
    ' get bye message text
    cSubBye = ControlSetting("UnSubMsg")
    nFile = FreeFile
    '
    Open cSubBye For Input As nFile
    While Not EOF(nFile)
        Line Input #nFile, cLine
        cMsgBody = cMsgBody & EOL & cLine
    Wend
    Close #nFile
    '
    ' now build a new message
    Set objMsg = objMAPISession.Outbox.Messages.Add
    objMsg.subject = "So long from the " & ControlSetting("ListName")
    objMsg.Text = cMsgBody
    ' create the recipient
    Set objRecip = objMsg.Recipients.Add
    If objAddr.Type = "MS" Then
        objRecip.Name = objAddr.Name ' handle local users
        objRecip.Type = mapiTo
        objRecip.Resolve
    Else
        objRecip.Name = objAddr.Type & ":" & objAddr.Address
        objRecip.address = objAddr.Type & ":" & objAddr.Address
        objRecip.Type = mapiTo
    End If
    ' send the message and log off
    objMsg.Update
    objMsg.Send showDialog:=False
    '
End Sub

The next code routines will handle subscriber requests for the list of retrievable archive
messages.

Listing Archives

One of the added features of MLM is to allow subscribers to send requests for copies of old, archived messages. Users can also request a list of messages that are in the archive. You need two routines to handle the LIST command-the main caller, and the one to actually assemble and send the list.

Add the new subroutine ProcessInboxMsgArcList to the project and enter the code shown in Listing 9.31.


Listing 9.31. Adding the ProcessInboxMsgArcList routine.
Public Sub ProcessInboxMsgArcList(objMsg As Object)
    '
    ' get list of archives and
    ' send to requestor
    '
Listing 9.31. continued
    On Error Resume Next
    '
    Dim objAddrEntry As Object
    '
    Set objAddrEntry = objMsg.Sender
    If SubFind(objAddrEntry) = True Then
        WriteArcList objAddrEntry
    End If
    '
End Sub

Now create the WriteArcList subroutine and add the code shown in Listing 9.32.


Listing 9.32. Adding the WriteArcList routine.
Public Sub WriteArcList(objAddr As Object)
    '
    ' make list of archives
    ' build message and send
    '
    Dim objMsg As Object
    Dim objRecip As Object
    Dim cArcFile As String
    Dim nArcFile As Integer
    Dim cLine As String
    Dim cFileDate As String
    Dim cFileName As String
    Dim cFileTitle As String
    Dim cMsgBody As String
    Dim nPos1 As Integer
    Dim nPos2 As Integer
    '
    Status "Sending Archive List to " & objAddr.Name & "..."
    '
    cMsgBody = "Archive List for " & ControlSetting("ListName") & EOL & EOL
    cMsgBody = cMsgBody & "All records are in the following format:" & EOL
    cMsgBody = cMsgBody & "Date(YYMMDD),FileName,Title" & EOL & EOL
    '
    cArcFile = ControlSetting("ArchiveFile")
    nArcFile = FreeFile
    Open cArcFile For Input As nArcFile
    Do While Not EOF(nArcFile)
        Line Input #1, cLine
        If Left(cLine, 1) <> ";" Then
            cMsgBody = cMsgBody & cLine & EOL
        End If
    Loop
    Close #nArcFile
    '
    ' now add message to outbox
    Set objMsg = objMAPISession.Outbox.Messages.Add
    objMsg.subject = "Archive List from " & ControlSetting("ListName")
    objMsg.Text = cMsgBody
    ' create the recipient
    Set objRecip = objMsg.Recipients.Add
    If objAddr.Type = "MS" Then
        objRecip.Name = objAddr.Name ' handle local users
        objRecip.Type = mapiTo
        objRecip.Resolve
    Else
        objRecip.Name = objAddr.Type & ":" & objAddr.Address
        objRecip.address = objAddr.Type & ":" & objAddr.Address
        objRecip.Type = mapiTo
    End If
    ' send the message
    objMsg.Update
    objMsg.Send showDialog:=False
    '
End Sub

The WriteArcList routine reads the MLMARch.TXT control file and creates a message body that has a brief set of instructions and lists all available archived messages. Once this is done, the message is addressed and sent.

Save the project before you go on to the last coding section.

Sending Requested Archives

Once subscribers have received a list of available archives, they can send a MLM GET YYMMDD command on the subject line of a message to ask for a specific message to be sent to them. You need three routines to handle this processing:

First add the ProcessInboxMsgArcGet subroutine to your project and enter the code in Listing 9.33.


Listing 9.33. Adding the ProcessInboxMsgArcGet routine.
Public Sub ProcessInboxMsgArcGet(objMsg As Object)
    '
    ' get single archive and
    ' send to requestor
    '
    On Error Resume Next
    '
    Dim objAddrEntry As Object
    '
    Set objAddrEntry = objMsg.Sender
    If SubFind(objAddrEntry) = True Then
        WriteArcGet objMsg.subject, objAddrEntry
    End If
    '
End Sub

Next, add the FindArc function to the project and enter the code shown in Listing 9.34.


Listing 9.34. Adding the FindArc function.
Public Function FindArc(cFile As String) As String
    '
    ' search for requested file in archive list
    '
    Dim cFileDate As String
    Dim cFileName As String
    Dim cFileTitle As String
    Dim cArchFile As String
    Dim nArchFile As Integer
    Dim cLine As String
    Dim nPos1 As Integer
    Dim nPos2 As Integer
    Dim cReturn As String
    '
    cReturn = ""
    cArchFile = ControlSetting("ArchiveFile")
    nArchFile = FreeFile
    Open cArchFile For Input As nArchFile
    Do Until EOF(nArchFile)
        Line Input #nArchFile, cLine
        If Left(cLine, 1) <> ";" Then
            nPos1 = InStr(cLine, ",")
            If nPos1 <> 0 Then
                cFileDate = Left(cLine, nPos1 - 1)
            End If
            nPos2 = InStr(nPos1 + 1, cLine, ",")
            If nPos2 <> 0 Then
                cFileName = Mid(cLine, nPos1 + 1, nPos2 - (nPos1 + 1))
            End If
            If nPos2 < Len(cLine) Then
                cFileTitle = Mid(cLine, nPos2 + 1, 255)
            Else
                cFileTitle = cFileName
            End If
        End If
        '
        ' now compare!
        If UCase(cFileDate) = UCase(cFile) Then
            cReturn = cFileName
            Exit Do
        End If
    Loop
    Close #nArchFile
    '
    FindArc = cReturn
    '
End Function

FindArc accepts one parameter (the archive file search number-YYMMDD) and returns the actual operating system filename of the archived message. If no message is found, the return value is a zero-length string.

Finally, add the WriteArcGet routine to the project and enter the code shown in Listing 9.35.


Listing 9.35. Adding the WriteArcGet routine.
Public Sub WriteArcGet(cSubject As String, objAddr As Object)
    '
    ' get single archive
    ' build message and send
    '
    Dim objMsg As Object
    Dim objRecip As Object
    Dim cArcFile As String
    Dim nArcFile As Integer
    Dim cLine As String
    Dim cFileDate As String
    Dim cFileName As String
    Dim cFileTitle As String
    Dim cMsgBody As String
    Dim nPos1 As Integer
    Dim nPos2 As Integer
    Dim cArchive As String
    '
    'get archive name
    nPos1 = InStr(UCase(cSubject), "GET")
    If nPos1 <> 0 Then
        nPos2 = InStr(nPos1 + 1, cSubject, " ") ' look for next space
        If nPos2 <> 0 Then
            cArchive = Mid(cSubject, nPos2 + 1, 255)
        Else
            cArchive = ""
        End If
    End If
    '
    cArcFile = FindArc(cArchive)
    If Len(cArcFile) <> 0 Then
        cMsgBody = "Archive File [" & cArchive & "]" & EOL & EOL
        ' read archive file
        nArcFile = FreeFile
        Open cArcFile For Input As nArcFile
        Do While Not EOF(nArcFile)
            Line Input #1, cLine
            If Left(cLine, 1) <> ";" Then
                cMsgBody = cMsgBody & cLine & EOL
            End If
        Loop
        Close #nArcFile
    Else
        cMsgBody = "MLM Archive Error" & EOL & EOL
        cMsgBody = cMsgBody & "*** Unable to locate Archive [" & cArchive & "]." & ÂEOL
    End If
    '
    ' now add message to outbox
    Status "Sending Archive " & cArchive & "to " & objAddr.Name & "..."
    Set objMsg = objMAPISession.Outbox.Messages.Add
    objMsg.subject = "Archive " & cArchive & "from " & ControlSetting("ListName")
Listing 9.35. continued
    objMsg.Text = cMsgBody
    ' create the recipient
    Set objRecip = objMsg.Recipients.Add
    If objAddr.Type = "MS" Then
        objRecip.Name = objAddr.Name ' handle local users
        objRecip.Type = mapiTo
        objRecip.Resolve
    Else
        objRecip.Name = objAddr.Type & ":" & objAddr.Address
        objRecip.address = objAddr.Type & ":" & objAddr.Address
        objRecip.Type = mapiTo
    End If
    ' send the message
    objMsg.Update
    objMsg.Send showDialog:=False
    '
End Sub

The WriteArcGet routine picks the archive name out of the subject line and, if it is found, reads the archived message, composes a new message, and sends it to the requestor.

That is all the code for this project. The next step is to test the various MLM functions. Be sure to save the project before you begin testing. Once testing is complete, you can make an executable version of the project for installation on any workstation that has the MAPI OLE Messaging Library installed.

Running the MLM Application

In a production setting, you can set up an e-mail account that is dedicated to processing MLM requests. All e-mail messages regarding that list can be addressed to this dedicated account. You (or someone else) can run the MLM once a day and it can automatically log onto the dedicated account and perform the list processing. Also, since MLM accepts a command-line parameter, you can build control files for several different mailing lists and run them all from the same workstation. You just need to keep in mind that in order to process the messages, you need to start the MLM application and run it at least once a day.

Tip
If you have the Microsoft Plus! pack installed, you can use the System Agent to schedule MLM to run at off-peak times. If you decide to use the System Agent, you need to add additional code to the project to allow you to select the Read & Send button automatically. Just add an additional parameter to the command line that will execute the Command1_Click event for Read & Send.

For testing purposes, set the MLM control file to log onto your own e-mail account. You can then send messages to yourself and test the features of MLM to make sure they are working properly.

Before you start testing you need to make sure you have valid ASCII files for the new subscribe greeting (MLMHELLO.TXT) and the unsubscribe departing message (MLMBYE.TXT). Refer to Listings 9.36 and 9.37 for examples of each of these files.

Tip
You can also find examples in the source code directory Chap08\MLM created when you installed the CD-ROM.


Listing 9.36. Default MLMHELLO.TXT message.
You are now added to the MLM Mailing List!

Welcome!

MCA


Listing 9.37. Default MLMBYE.TXT message.
Sorry you're leaving us!

BYE - MCA

Use NOTEPAD.EXE to create these two files and save them in the project directory.

You may also need to modify the MLMSKED.TXT file to match today's date. Change the first entry from 960225 to make sure that any registered subscriber gets a message when you run the SendMail routine.

Testing the MLM Application

Now you're ready to test MLM!

First, add your name to the mailing list. To do this, start up your MAPI client (you could use the one you built in Chapter 7,"Creating a Simple MAPI Client with the MAPI Controls") and send a message with the words MLM SUB in the subject line. It does not matter what you put in the message body; it will be ignored by MLM (see Figure 9.2).

Figure 9.2 : Sending a request to join the MLM mailing list.

Once the request is sent, load MLM and select the ReadMail button. This will scan all the messages in your inbox, and (if all goes right!) find and process your request to be added to the list. Figure 9.3 shows how the MLM screen looks as it is processing.

Figure 9.3 : Running the MLM ReadMail process.

After MLM is done, start up your MAPI client again. You should see a greeting message in your inbox confirming that you have been added to the MLM mailing list (see Figure 9.4).

Figure 9.4 : Confirmation greeting message from MLM.

Next, try sending a message that requests a list of available archives. Use your MAPI client to compose a message with MLM LIST in the subject line. After sending the message, run the MLM ReadMail option again, and then check your inbox with the MAPI client. You should see a message similar to the one in Figure 9.5.

Figure 9.5 : Receiving a list of available archives from MLM.

Once you get the list, you can send MLM a command to retrieve a specific message in the archive. Use your MAPI client to send a message that asks for one of the messages. After sending the message, run MLM ReadMail, and then recheck your MAPI client for the results. You should get a message like the one in Figure 9.6.

Figure 9.6 : Receiving a requested archive message from MLM.

Tip
You have probably noticed that messages that have already been read and processed are being processed again each time you run MLM. You can prevent this from happening by deleting the messages from the inbox by hand, or by adding code to the project to delete each message after you are finished processing it.

You can test the SendMail option by simply starting MLM and pressing the SendMail button. This will search for a message file tagged with today's date and send it to all subscribers in the list. To test this, modify one of the entries in the MLMSKED.TXT file so that it has the current date as its key number. Figure 9.7 shows what the MLM progress screen looks like as it is working.

Figure 9.7 : Running the SendMail option of MLM.

Finally, you can test the unsubscribe feature of MLM by sending a message with MLM UNSUB in the subject. When MLM receives this message, it will drop your name from the subscriber list and send you a departing message.

Summary

In this chapter you built a mailing list manager using Microsoft's OLE Messaging Library. This application lets you define a mailing list, allow others to become list subscribers, publish messages automatically (based on date), and allow others to query and retrieve old messages from the list archives. This program can accept members from within a single network, or from around the world through Internet (or other) transports.

In the next chapter you'll use OLE to build a MAPI application that allows distant users to query databases and other collections of information by way of e-mail.