Hardcore Clarion - Email-enabling your Applications with Simple MAPI

by Paul Attryde

Published 1999-03-01    Printer-friendly version

With computers and the Internet becoming more and more prevalent in society, people are becoming more accustomed to using email, surfing the 'Web etc. One of the things that you as a developer can do (and that I've been asked for more than once) is to add email support to your application.

As with most things in life there are many means to obtain the same goal. In the case of email support the easiest is to use one of the 3rd party tools that already exist. However, being the sort of person I am, I'd rather implement my own interface - it may take more time than I'd like, but I have exact control over everything and get to understand the underlying philosophy as well.

Unfortunately, as with most things, there can be quite a lot to learn. Once you understand the basic principles it's easy, but getting there can be tough, so this month I'm just going to provide a quick overview on the basic API calls you'll need to use to implement email support.

Which interface?

There are a number of different interfaces that a developer can use to add email functionality to his application. Unfortunately not all of these options are available to a Clarion developer. Here's what you can and can't use, and why:

MAPI (Mail API)

The base mail system upon which the other 3 options are based. It offers very high control over all aspects of the mail system, but is also fully a object-orientated COM-compliant implementation. What this means to us as developers (bearing in mind that Clarion currently isn't COM-compliant) is that basically we can't use it. If there are features that you want that the other options can't address, then there's probably a VBX or OCX that can.

CMC (Common Messaging Calls)

Simple MAPI (Simple Mail API)

CMC and SMAPI provide a simple set of about a dozen high-level API calls that Clarion developers can use. The features provided by SMAPI and CMC are nearly identical, but CMC is slightly more compact, combining multiple features in a single function. Although the Microsoft implementation is based on MAPI, the CMC API itself was developed in conjunction with the X.400 API Association standards organization, and is supported on Windows, MS-DOS, OS/2, Macintosh, and UNIX platforms.

AML (Active Message Library)

AML is an OLE automation programming interface that is a component of the MAPI interface. The AML furnishes programmable objects, like Microsoft Excel objects and Microsoft Access objects, that make available properties and methods that can then be managed by Visual Basic (VB) and Visual Basic for Applications (VBA) programs. Again, because Clarion is COM and OLE challenged (I'm being PC here), it means we can't use it.

As far as I can tell, that leaves us with the choice of CMC or SMAPI. As I described above, they are basically identical except that CMC is cross-platform and SMAPI isn't. Seeing as how we can only create Windows applications anyway, choosing CMC for it's cross platform support isn't high on my decision tree.

With that in mind, I'm going to describe the basics of using Simple MAPI (SMAPI) from a Clarion program. There are 12 Simple MAPI functions - I'm going to use 5 of them to send and receive mail. They're all basically the same, so if you want to use the ones I don't, they'll take similar parameters to the ones you'll see listed below.
 

Simple MAPI function
Description
MAPIAddress Addresses a message.
MAPIDeleteMail Deletes a message.
MAPIDetails Displays a recipient-details dialog box.
MAPIFindNext Returns the identifier of the first or next message of a specified type.
MAPIFreeBuffer Frees memory allocated by the messaging system.
MAPILogoff Ends a session with the messaging system.
MAPILogon Establishes a messaging session.
MAPIReadMail Reads a message.
MAPIResolveName Displays a dialog box to resolve an ambiguous recipient name.
MAPISaveMail Saves a message.
MAPISendDocuments Sends a standard message using a dialog box.
MAPISendMail  Sends a message, allowing greater flexibility than MAPISendDocuments() in message generation.

Is our client interface installed?

Although we've just decided on our mail interface, not all computers may have it installed, so we should really determine if it is and disable the relevant menu options if not. We can test for the presence of each of the 4 mail interfaces by using the GETINI procedure on WIN.INI. The relevant section will resemble the following - if the interface is installed, it's option will be set to 1. If it is not installed, it's option either won't be there, or will be set to 0
[Mail]
;MAPI
MAPIX=0 or 1
;CMC
CMC=0 or 1
;Simple MAPI
MAPI=0 or 1
;AML
OLEMessaging=0 or 1
What our application should really do is test for the presence of the desired interface and, once detected, call the Windows API functions LoadLibrary() and GetProcAddress() on the relevant DLL's. Once we've done that, we can call the procedures we need. On the other hand, it's a lot easier as a developer if we link in the MAPI import library to our application. Of course, if we do that and MAPI isn't installed, the application will fail to run. So, what do we do? Morally we should not link against the MAPI DLL (in case it's not installed), but technically it's a lot easier to.

My theory on this is two-fold. Firstly, virtually every new PC comes pre-installed with some flavour of Windows, and email support is virtually guaranteed. Secondly, if you are developing email support in your application at the request of a client, then he should have some email system installed anyway.

As you can probably tell from my reasoning, I link my application against an import library. Don't go looking for one on your machine, because you probably won't find it. It's not shipped with Clarion because Clarion doesn't offer SMAPI support, and it's not in your \WINDOWS directory because Microsoft isn't in the habit of shipping import libraries for it's DLLs. To get the .LIB file we need, you'll have to run LibMaker. This handy utility ships with nearly every version of Clarion in one form or another, and simply creates a .LIB so that you can link against the original .DLL. To make your .LIB, run LibMaker and go hunt for MAPI32.DLL (or MAPI.DLL if you're coding 16-bit). It'll probably be in \WINDOWS\SYSTEM (or \WINNT\SYSTEM if you're running Windows NT).

Sessions

The interface between your application and the SMAPI subsystem is based on the concept of sessions. Before your client application can call an underlying messaging system, it must establish a session, or connection, with the MAPI subsystem. Sessions are initiated when a user logs on, a process that consists of accessing a valid profile, validating messaging system and messaging service credentials, and insuring that all of the profile's message services are properly configured.

Clients can specify one of two types of sessions to be established in the logon call: an individual session or a shared session. Individual sessions are private connections; there is a one-to-one relationship between a client application and the session it is using. As a consequence, client applications sharing a session also share a profile. Shared sessions are established once but can be "used" by other client applications who need to use them.

Logging on and creating a new MAPI session

To create a new session you will need to use the MAPILogon() function. It's API prototype is:
ULONG FAR PASCAL MAPILogon( 
ULONG ulUIParam, 
LPTSTR lpszProfileName, 
LPTSTR lpszPassword, 
FLAGS flFlags, 
ULONG ulReserved, 
LPLHANDLE lplhSession )
Its Clarion prototypes are:
MAPILogon(ULONG,ULONG,ULONG,ULONG,ULONG,*ULONG),ULONG,RAW,PASCAL

The parameters take some explaining:

  • ulUIParam is the Prop:Handle of the owner window, or 0 if you want a application modal dialog to appear.
  • lpszProfileName is a pointer to a null-terminated profile name string. This is the profile to use when logging on. If the lpszProfileName parameter is NULL or points to an empty string, and the flFlags parameter is set to MAPI_LOGON_UI, MAPILogon() displays a logon dialog box with an empty name field
  • lpszPassword is a pointer to a null-terminated password. If the messaging system does not require password credentials, or if it requires that the user enter them, the lpszPassword parameter should be NULL or point to an empty string. When the user must enter credentials, the flFlags parameter must be set to MAPI_LOGON_UI to allow a logon dialog box to be displayed.
  • LpFlags is a bitmask of flag options:
MAPI_FORCE_DOWNLOAD Attempt to download all of the user's messages before returning. If the MAPI_FORCE_DOWNLOAD flag is not set, messages can be downloaded in the background after the function call returns.
MAPI_NEW_SESSION Attempt to create a new session rather than acquire the environment's shared session. If the MAPI_NEW_SESSION flag is not set, MAPILogon uses an existing shared session.
MAPI_LOGON_UI A logon dialog box should be displayed to prompt the user for logon information. If the user needs to provide a password and profile name to enable a successful logon, MAPI_LOGON_UI must be set.
MAPI_PASSWORD_UI MAPILogon should only prompt for a password and not allow the user to change the profile name. Either MAPI_PASSWORD_UI or MAPI_LOGON_UI should not be set, since the intent is to select between two different dialog boxes for logon.
  • lplhSession contains the session handle on a successful logon, and you'll need this (obviously) for all the subsequent MAPI functions.
To use the procedure, I find it relatively easy to write a wrapper of some sort, normally pretty similar to the following:
LogOn FUNCTION (Prm:UserName,Prm:Password)
LocalVars           Group,Pre(Loc)
ulMapiStatus            ULong
ulMapiSession           ULong
                    End
  CODE 
    Loc:ulMapiStatus = MAPILogon( 0,|
                                  Address(Prm:Username),|
                                  Address(Prm:Password),|
                                  MAPI_LOGON_UI + MAPI_NEW_SESSION,|
                                  0,|
                                  Loc:ulMapiSession)
    IF Loc:ulMapiStatus = SUCCESS_SUCCESS Then
       Return(Loc:ulMapiSession)
    Else
       Return(0)
    End
This has the effect of logging you on to MAPI. It always creates a new session and always displaying a dialog box to the user. If the logon is successful, you'll return the session handle back to the calling procedure.

If you always know what the username and password are, you can change the flFlags parameter to not display a UI, but to simply create a new session. However, in my experience it's always easier (at least while you're still developing the application) to simply show a dialog box and let the user fill it in. That way you don't have to worry about the user incorrectly specifying the username and password in your setup information, and the problems that you'll get from that.

Sending email

To send email we use the MAPISendMail() procedure. It's API prototype is
ULONG FAR PASCAL MAPISendMail( 
LHANDLE lhSession, 
ULONG ulUIParam, 
lpMapiMessage lpMessage, 
FLAGS flFlags, 
ULONG ulReserved )
It's Clarion prototype is:

MAPISendMail(ULONG,ULONG,*GROUP,ULONG,ULONG),ULONG,RAW,PASCAL

Again, the parameters need some explaining:

  • lhSession is the handle to the session (returned by MAPILogon())
  • ulUIParam is the Prop:Handle of the owner window, or 0 if you want a application modal dialog to appear.
  • lpMessage is a pointer to a MapiMessage structure containing the message to be sent. If the MAPI_DIALOG flag is not set, the nRecipCount and lpRecips members must be valid for successful message delivery. All unused pointers should be NULL
  • flFlags is a bitmask of option flags. It's 3 options (MAPI_DIALOG, MAPI_LOGON_UI and MAPI_NEW_SESSION ) are the same as for MAPILogon().
  • ulReserved is reserved and must be zero.
The important paramters here are lpMessage and flFlags. Between them, they determine how the email is created, and what the user sees.

lpMessge is a group, whose structure is

MapiMessageType GROUP,TYPE 
ulReserved          ULONG ! Reserved, must be 0.
lpszSubject         ULONG ! Pointer to subject, CSTRING max 256 chars.
lpszNoteText        ULONG ! Pointer to text, CSTRING, CR at paragraphs.
lpszMessageType     ULONG ! Pointer to message type, null is default.
lpszDateReceived    ULONG ! Pointer to string in YYY/MM/DD format.
lpszConversationID  ULONG ! Ignored.
flFlags             ULONG ! 1 - MAPI_UNREAD
                          ! 2 - MAPI_RECEIPT_REQUESTED
                          ! 3 - MAPI_SENT
lpOriginator        ULONG ! Pointer to MapiRecipDesc group.
nRecipCount         ULONG ! Count of groups pointed to by lpRecips.
lpRecips            ULONG ! Pointer to array of recipient descriptor groups.
nFileCount          ULONG ! Count of file attachment groups pointed to by lpFiles.
lpFiles             ULONG ! Pointer to array of file attachment groups.
                END
When sending email, you must first determine if your application is responsible for prompting the user for subject, destination address and mail text , or whether you want MAPI to display it's own window. If the former, you must fill in the respective fields of the group and call MAPISendMail() without theMAPI_DIALOGflag set. If you want MAPI to do all the work for you (and why not?) then simply set theMAPI_DIALOGflag. That will display the standard Microsoft email window, and frees you from the burden of writing yet another window. One thing I've found in the past is that even if you do set the MAPI_DIALOG flag, you still have to provide one destination address (described below) otherwise SendMail() returns an 'invalid recipients' error.

If you don't want MAPI to do all the work, it gets rather messy rather quickly. You have to prime the lpszSubject and lpszNoteText fields with the ADDRESS() of the respective CSTRING, and you have to manage the destination addresses manually. They work on the principle of having a count (the number of people the mail is going to), and a pointer to an array of groups (one for each person). So, to send a mail message to someone, you have to set nRecipCount to 1, and set lpRecips to the ADDRESS() of structure holding the address of the person you are sending it to. That structure looks like this:

MapiRecipDescType  GROUP,TYPE  ! RECIPIENT DESCRIPTOR
ulReserved             ULONG   ! Reserved, must be 0.
ulRecipClass           ULONG   ! Recipients role in message
                               ! 0 - MAPI_ORIG
                               ! 1 - MAPI_TO
                               ! 2 - MAPI_CC
                               ! 3 - MAPI_BCC
lpszName               ULONG   ! Pointer to diplayed name of recipient.
lpszAddress            ULONG   ! Pointer to recipient's mail address.
ulEIDSize              ULONG   ! Size (bytes) of binary data in lpEntryID.
lpEntryID              ULONG   ! Pointer to binary data representing the recipient.
                   END
In practice I've found the only fields you need to set in this structure are ulRecipClass (to MAPI_TO) and lpszName as the ADDRESS() of the CSTRING holding their name. If you also want to CC or BCC someone else, you'll have to increment the nRecipCount in the original group, and set the ulRecipClass to MAPI_CC or MAPI_BCC in the nth copy of the group. Everything else you can leave as 0 or NULL.

If you are sending attachments, it gets messier still. Attachments work on the same principle as destination addresses - you have a count (the number of attachments), and a pointer to an array of groups (one for each attachment). The attachment group is as follows:

MapiFileDescType   GROUP,TYPE    ! FILE DESCRIPTOR
ulReserved             ULONG(0)  ! Reserved, must be 0.
flFlags                ULONG(0)  ! Ignored.
nPosition              ULONG     ! where in the message text to place the file
lpszPathName           ULONG     ! Pointer to full pathname of attached file.
lpszFileName           ULONG     ! Pointer to file name seen by user.
lpFileType             ULONG(0)  ! Reserved, must be NULL.
                   END
As you can see, you can leave virtually all the fields blank, except for lpszPathName, nPosition and lpszFileName. I have found in the past that it's a good idea not to pass fully qualified pathnames in lpszPathName, because you get problems on the receiving end if the path doesn't exist. Instead, I'd try using PATH() to save the current path, SETPATH() to the path where the file is, then SETPATH() back again once you've called MAPISendMail().

The only other problem with attachments is in setting the nPosition field. This field determines where the attachment is placed, so if you set it to 1, the attachment overwrites the first character of the text. What I've done in the past is add an extra character to the end of the text, then set the nPosition field to the position of that character.

Checking for / receiving email

One of the drawbacks of Simple MAPI is it's lack of features (Duh! that's why it's called Simple).

This lack of features shows itself in the fact that you can only check for mail in the main incoming mail directory. If you're a power user who uses Inbox Assistant to redirect your mail to different folders, Simple MAPI won't be able to read the mail in those folders, only the main inbox.

However, if that's all you want to do, then this is how you do it. This time, thought, you'll need three functions:

ULONG FAR PASCAL MAPIFindNext( 
LHANDLE lhSession, 
ULONG ulUIParam, 
LPTSTR lpszMessageType, 
LPTSTR lpszSeedMessageID, 
FLAGS flFlags, 
ULONG ulReserved, 
LPTSTR lpszMessageID )
ULONG FAR PASCAL MAPIReadMail( 
LHANDLE lhSession, 
ULONG ulUIParam, 
LPTSTR lpszMessageID, 
FLAGS flFlags, 
ULONG ulReserved, 
lpMapiMessage FAR * lppMessage )
ULONG FAR PASCAL MAPIFreeBuffer( LPVOID pv )
Their respective Clarion prototypes are:
MAPIFindNext(ULONG,ULONG,ULONG,ULONG,ULONG,ULONG,ULONG),ULONG,RAW,PASCAL
MAPIReadMail(ULONG,ULONG,ULONG,ULONG,ULONG,ULONG),ULONG,RAW,PASCAL
MAPIFreeBuffer(ULONG),ULONG,RAW,PASCAL
MAPIFindNext() finds the next unread email, and passes back a message identifier. MAPIReadMail() takes that identifier and actually gets the mail (marking it as read in the process). MAPIFreeBuffer() frees the buffer that was allocated for the mail. Not freeing the buffer leads to memory leaks, which over time can bring the machine to it's knees. Always remember to free the buffer!

We start the process by calling MAPIFindNext():

  • lhSession is the handle to the session (returned by MAPILogon())
  • ulUIParam is the Prop:Handle of the owner window, or 0 if you want a application modal dialog to appear.
  • lpszMessageType is a pointer to a string identifying the message class to search. To find an interpersonal message (IPM), specify NULL in the lpszMessageType parameter or have it point to an empty string.
  • LpszMessageSeedID is a pointer to a string containing the message identifier seed for the request. If the lpszSeedMessageID parameter is NULL or points to an empty string, MAPIFindNext retrieves the first message that matches the type specified in the lpszMessageType parameter.
  • flFlags is yet another bitmask of flags.
MAPI_GUARANTEE_FIFO 
The message identifiers returned should be in the order of time received. MAPIFindNext calls can take longer if this flag is set. Some implementations cannot honor this request and return the MAPI_E_NO_SUPPORT value.
MAPI_LONG_MSGID The returned message identifier can be as long as 512 characters. If this flag is set, the lpszMessageID parameter must be large enough to accomodate 512 characters.

Older versions of MAPI supported smaller message identifiers (64 bytes) and did not include this flag. MAPIFindNext will succeed without this flag set as long as lpszMessageID is large enough to hold the message identifier. If lpszMessageID cannot hold the message identifier, MAPIFindNext will fail.

MAPI_UNREAD_ONLY Only unread messages of the specified type should be enumerated. If this flag is not set, MAPIFindNext can return any message of the specified type.
  • LpszMessageID is returned, and is the identifier for the first unread message found.
In keeping with the idea of writing a wrapper around everything, the procedure to find the next unread mail may look something similar to;
FindNextUnread FUNCTION 
LocalVars           Group,Pre(Loc)
ulMapiStatus            ULong
MessageType             CString(10)
MessageID               CString(515)
                      End
  CODE 
        Loc:MessageType = ''
        Loc:ulMapiStatus = MapiFindNext(SMAPI:Session,|
                                        0,|
                                        0,|
                                        0,|
                                        MAPI_UNREAD_ONLY+MAP
                                        0,|
                                        Address(Loc:MessageI
        If Loc:ulMapiStatus <> SUCCESS_SUCCESS Then
            Clear(Loc:MessageID)
        End
        Return(Clip(Loc:MessageID))
Once we've found the next new email, the next thing we need to do is actually read the message. The parameters for MAPIReadMail() are:
 
  • lhSession is the handle to the session (returned by MAPILogon())
  • ulUIParam is the Prop:Handle of the owner window, or 0 if you want a application modal dialog to appear.
  • lpszMessageID is a pointer to a message identifier string (IE the identifier)
  • flFlags is yet another bitmask of flags.
MAPI_BODY_AS_FILE
MAPIReadMail should write the message text to a temporary file and add it as the first attachment in the attachment list.
MAPI_ENVELOPE_ONLY
MAPIReadMail should read the message header only. File attachments are not copied to temporary files, and neither temporary file names nor message text is written. Setting this flag enhances performance.
MAPI_PEEK
MAPIReadMail does not mark the message as read. Marking a message as read affects its appearance in the user interface and generates a read receipt. If the messaging system does not support this flag, MAPIReadMail always marks the message as read. 
MAPI_SUPPRESS_ATTACH
MAPIReadMail should not copy file attachments but should write message text into the MapiMessage structure. MAPIReadMail ignores this flag if the calling application has set the MAPI_ENVELOPE_ONLY flag. Setting the MAPI_SUPPRESS_ATTACH flag enhances performance.

lppMessage is a pointer to a message structure (the same structure we used when sending the email). The structure can be freed with a single call to the MAPIFreeBuffer() function. When MAPI_ENVELOPE_ONLY and MAPI_SUPPRESS_ATTACH are not set, attachments are written to temporary files pointed to by the lpFiles member of the MapiMessage structure. It is the caller's responsibility to delete these files when they are no longer needed.

The code looks something similar to:

ReadMail FUNCTION (Prm:MessageID) 
LocalVars           Group,Pre(Loc)
ulMapiStatus            ULong
MessageType             CString(10)
MessageID               CString(100)
MailTitle               CString(255)
MailSender              CString(255)
MailText                CString(8192)
MessagePtrPtr           ULong
MessagePtr              ULong
                    End
RxMessage           Like(MapiMessageType)     ! message info
RxSender            Like(MapiRecipDescType)   ! sender info
RxFile              Like(MapiFileDescType)    ! attached file info

  CODE
    If SMAPI:LoggedOn Then
        Loc:MessageID = Clip(Prm:MessageID) & Chr(0)
        Clear(RxMessage)
        Loc:MessagePtrPtr = Address(RxMessage)
        Loc:ulMapiStatus = MAPIReadMail( SMAPI:Session,|
                                         0,|
                                         Address(Loc:Me
                                         MAPI_SUPPRESS_
                                         0,|
                                         Loc:MessagePtr
        If Loc:ulMapiStatus = SUCCESS_SUCCESS Then
           MemCpy( Address(Loc:MessagePtr) , Loc:MessagePtrPtr , 4)
           MemCpy( Address(RxMessage) , Loc:MessagePtr , Size(RxMessage) )
           If RxMessage:lpszSubject <> 0 Then
               MemCpy( Address(Loc:MailTitle) , RxMessage:lpszSubject , 100 )
           Else
               Loc:MailTitle = 'Untitled'
           End
           If RxMessage:lpszNoteText <> 0 Then
               memcpy( Address(Loc:MailText) , RxMessage:lpszNoteText , 100 )
           Else
               Clear(Loc:MailText)
           End

           If RxMessage:lpOriginator <> 0 Then
               memcpy( Address(RxSender), RxMessage:lpOriginator, Size(RxSender) )
               If RxSender:lpszAddress <> 0 Then
                   memcpy( Address(Loc:MailSender), RxSender:lpszAddress, 100 )
               End
           Else
               Clear(Loc:MailSender)
           End

           SMAPI:Rx:MailTitle = Clip(Loc:MailTitle)
           SMAPI:Rx:MailText = Clip(Loc:MailText)
           SMAPI:Rx:MailSender = Clip(Loc:MailSender)

           Loc:ulMapiStatus = MAPIFreeBuffer(Loc:MessagePtr)
           If Loc:ulMapiStatus <> SUCCESS_SUCCESS Then
               SMAPI:ErrorString = MapiError(Loc:ulMapiStatus)
           End
       End
   End
   Return(Loc:ulMapiStatus)
If you look carefully at the code, you'll see that we end up with a pointer to a pointer to the message structure. Once we know that we read the message successfully, we use MemCpy to copy the address of the structure back into the first pointer. We copy 4 bytes because that's the size of a long, which is basically what a pointer is in Clarion. Once we have a pointer to the message structure, we de-reference it again and actually copy the data from memory into a message structure so that's its easier to work with. Once the email has been read (and replied to if necessary), we need to free the message structure.

Logging off from MAPI

The last thing you have to do is logoff and close the session. The API prototype is:
ULONG FAR PASCAL MAPILogoff ( 
LHANDLE lhSession, 
ULONG ulUIParam, 
FLAGS flFlags, 
ULONG ulReserved )
It's Clarion prototype is:

MAPILogoff(ULONG,ULONG,ULONG,ULONG),ULONG,PASCAL,RAW

Compared to everything else to do with MAPI, the code to implement it is very easy.

LogOff PROCEDURE
LocalVars            Group,Pre(Loc)
ulMapiStatus             ULong
                     End
  CODE 
    Loc:ulMapiStatus = MAPILogoff( SMAPI:Session,|
                                   0,|
                                   0,|
                                   0)
    IF Loc:ulMapiStatus = SUCCESS_SUCCESS then
        SMAPI:LoggedOn = False
    End

Summary

Once you understand the principles involved, using Simple MAPI to add email support to your application is quite easy. There's a lot that I haven't been able to cover, just because the subject is rather complicated, but this should definitely get you started.

Of course, by it's nature Simple MAPI is limited in features, but at least this gives you some idea of the work involved. If you don't think Simple MAPI fits your situation, try one of the 3rd party templates or VBX/OCX controls - I'm sure they'll have the functionality you're looking for.

Next Time ...

How to locate GPF's in your application.

And Remember ...

The best place for information is MSDN. If you don't subscribe, you can still get to the information via the web at http://msdn.microsoft.com/

Download the source

Printer-friendly version

Reader Comments

Posted on Thursday, March 28, 2002 by Carl Barnes

You can download the files from: http://www.attryde.com/clarion/col_smapi.htm

Watch out! When you MAPILogon it may change your current directory to the mail client. So save your PATH() and SETPATH(). (Changed for me with OE5 under Win2K.)

Another confusing point is the Attachment structure _appears_ to split the attachment file into path and file name:
MapiFileDescType    GROUP,TYPE          ! FILE DESCRIPTOR
ulReserved              ULONG(0)
flFlags                 ULONG(0)        !  Ignored.
nPosition               ULONG           !  Ignored.
lpszPathName            ULONG    
lpszFileName            ULONG    
lpFileType              ULONG(0)        ! must be NULL
                    END

what you want is:
lpszPathName = path+FILENAME / source computer file name that MAPI needs to attach, must have path AND name
lpszFileName = file name receiver sees and is optional

so all you have to do is define
  lpszPathName = 'C:mypathmyfiletosend.txt'
and leave lpszFileName=0 and you'll get 'myfiletosend.txt'

The sample app goes to some work splitting the path and file name and changing paths when it does not have to, this simpler code worked for me:

If ~Omitted(Omitted:Prm:Attachment) AND |
        Clip(Prm:Attachment) <> '' Then    

    Loc:Attachment = Clip(Prm:Attachment) & Chr(0)
    If EXISTS(Loc:Attachment)  
       TxMessage:lpFiles = Address(TXFile)
       TxMessage:nFileCount += 1
       Clear(TXFile)
       TXFile:nPosition = Len(Clip(Loc:Text)) + 1
       Loc:Text = Clip(Loc:Text) & ' '
       TXFile:lpszPathName = Address(Loc:Attachment)
       TXFile:lpszFileName = 0 !Take name from above
    ELSE
       Message('Unable to find attachment file|' & CLIP(Loc:Attachment) & '|' & CLIP(Prm:Attachment) )
    End
End

To add a comment to this article you must log in.