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).
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.
Control | Property | Setting |
Form | Name | frmMLM |
Height | 5325 | |
Left | 1140 | |
Top | 1230 | |
Width | 7380 | |
Text Box | Name | Text1 |
Font | 8pt MS Sans Serif, Bold | |
Height | 4635 | |
Left | 120 | |
MultiLine | -1 ' True | |
Scrollbars | 2 ' Vertical | |
Top | 120 | |
Width | 5115 | |
Command Button | Name | Command1 |
Caption | Edit &Controls | |
Font | 8pt MS Sans Serif, Bold | |
Height | 450 | |
Index | 0 | |
Left | 5400 | |
Top | 120 | |
Width | 1800 | |
Command Button | Name | Command1 |
Caption | Edit &Subs | |
Font | 8pt MS Sans Serif, Bold | |
Height | 450 | |
Index | 1 | |
Left | 5400 | |
Top | 720 | |
Width | 1800 | |
Command Button | Name | Command1 |
Caption | Edit &Sked | |
Font | 8pt MS Sans Serif, Bold | |
Height | 450 | |
Index | 2 | |
Left | 5400 | |
Top | 1320 | |
Width | 1800 | |
Command Button | Name | Command1 |
Caption | Edit &Archive | |
Font | 8pt MS Sans Serif, Bold | |
Height | 450 | |
Index | 3 | |
Left | 5400 | |
Top | 1920 | |
Width | 1800 | |
Command Button | Name | Command1 |
Caption | Read &Inbox | |
Font | 8pt MS Sans Serif, Bold | |
Height | 450 | |
Index | 4 | |
Left | 5400 | |
Top | 2520 | |
Width | 1800 | |
Command Button | Name | Command1 |
Caption | Send &Mail | |
Font | 8pt MS Sans Serif, Bold | |
Height | 450 | |
Index | 5 | |
Left | 5400 | |
Top | 3120 | |
Width | 1800 | |
Command Button | Name | Command1 |
Caption | &Read && Send | |
Font | 8pt MS Sans Serif, Bold | |
Height | 450 | |
Index | 6 | |
Left | 5400 | |
Top | 3720 | |
Width | 1800 | |
Command Button | Name | 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.
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.
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.
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.
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. |
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.
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.
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.
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.
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.
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.
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.
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.