?
Solved

MAPISendMail with multiple attachments not working

Posted on 2011-04-29
22
Medium Priority
?
1,420 Views
Last Modified: 2012-05-11
I'm using Delphi 7 to send an email using MAPISendMail. I attach full code for the function - the problem occurs within the TMapiMessage.lpFiles file attaching section.

The function receives all the information required to send the email such as sender, recipient, subject, body, etc. It also receives two matching strings sPhysPathname, sAttachFilename, which contain the physical files to be attached and the names as they are to appear in the email attachments. These will often be a single file but may on occasions be multiple files separated by '+' sign e.g.
  single file:
     sPhysPathname = 'c:\myfile.txt',
     sAttachFilename = 'My Attached File.txt'
  multiple files:
     sPhysPathname = 'c:\myfile1.txt+c:\myfile2.txt',
     sAttachFilename = 'My Attached File #1.txt+My Attached File #2.txt'
The function parses the + format strings and places them in two stringlists. These both look fine in debug and if I place a FileExists line in the code they are verified as existing.

The function works fine where it is a single file but I'm having problems where there are two or more files. In most cases MAPISendMail simply returns error code 11 which is physical file not found. Occasionally it will load the email fine but the first attached filename will have the final character removed from its name.

I'm running this on Windows 7 which is what most of our customers will be using.


function LoadEmail2(sRecipEmail, sSenderEmail, sSubj, sBody, sPhysPathname, sAttachFilename: String): Boolean;
var
  sMsg, sPhysName, sAttachName: String;
  slPhysName, slAttachName: TStringlist;
  mmMsg: TMapiMessage;
  mrSender, mrRecip: TMapiRecipDesc;
  mfFileAttach: TMapiFileDesc;
  SM: TFNMapiSendMail;
  Files, FilesTmp: PMapiFileDesc;
  i, iError: Integer;
  MAPIModule: HModule;
begin
  Result := True;

  FillChar(mmMsg, SizeOf(mmMsg), 0);
  with mmMsg do
  begin
    if sSubj <> '' then
      lpszSubject := PChar(sSubj);

    if (sBody <> '') then
      lpszNoteText := PChar(sBody);

    if (sSenderEmail <> '') then
    begin
      mrSender.ulRecipClass := MAPI_ORIG;
      mrSender.lpszName := PChar(sSenderEmail);
      mrSender.lpszAddress := PChar(sSenderEmail);
      mrSender.ulReserved := 0;
      mrSender.ulEIDSize := 0;
      mrSender.lpEntryID := nil;
      lpOriginator := @mrSender;
    end;

    if (sRecipEmail <> '') then
    begin
      mrRecip.ulRecipClass := MAPI_TO;
      mrRecip.lpszName := PChar(sRecipEmail);
      mrRecip.lpszAddress := PChar('SMTP:' + sRecipEmail);     // PChar('SMTP:' + sRecipEmail);
      mrRecip.ulReserved := 0;
      mrRecip.ulEIDSize := 0;
      mrRecip.lpEntryID := nil;
      nRecipCount := 1;
      lpRecips := @mrRecip;
    end
    else
      lpRecips := nil;

    if (sPhysPathname = '') then
    begin
      nFileCount := 0;
      lpFiles := nil;
    end
    else
    begin
      slPhysName := TStringlist.Create;
      slAttachName := TStringlist.Create;

      sPhysPathname := sPhysPathname + '+';              // parse string into individual attachment names in stringlist
      sAttachFilename := sAttachFilename + '+';
      while Pos('+', sPhysPathname) > 0 do
      begin
        sPhysName := Copy(sPhysPathname, 1, Pos('+', sPhysPathname) - 1);
        sPhysPathname := Copy(sPhysPathname, Pos('+', sPhysPathname) + 1, MAXCOPY);
        sAttachName := Copy(sAttachFilename, 1, Pos('+', sAttachFilename) - 1);
        sAttachFilename := Copy(sAttachFilename, Pos('+', sAttachFilename) + 1, MAXCOPY);
        slPhysName.Add(sPhysName);
        slAttachName.Add(sAttachName);
      end;

// ********************************  this is where we're having the problem *****************************
// ******************************************************************************************************
      nFileCount := slPhysName.Count;
      Files := AllocMem(SizeOf(TMapiFileDesc) * nFileCount);
      lpFiles := Files;
      FilesTmp := Files;
      for i := 0 to slPhysName.Count - 1 do
      begin
        FilesTmp.nPosition := Cardinal($FFFFFFFF);
        FilesTmp.lpszPathName := PChar(slPhysName[i]);
        FilesTmp.lpszFileName := PChar(slAttachName[i]);
        Inc(FilesTmp);
      end;
// ******************************************* end of problem *******************************************
// ******************************************************************************************************
      slPhysName.Free;
      slAttachName.Free;
    end;
  end;

  MAPIModule := LoadLibrary(PChar(MAPIDLL));
  if MAPIModule = 0 then
    iError := -1
  else
    try
      @SM := GetProcAddress(MAPIModule, 'MAPISendMail');
      if @SM <> nil then
        iError := SM(0, GetDesktopWindow, mmMsg, MAPI_DIALOG or MAPI_LOGON_UI or MAPI_NEW_SESSION , 0)
        // returns error 11 most times = 'Cannot find attachment' which is the Physical Filepath is this case
      else
        iError := 1;
    finally
      FreeLibrary(MAPIModule);
    end;

  if iError <> 0 then
  begin
    if iError = 1 then
      fmMsg.MsgInit('Email not sent.', FLASHBN)
    else
      fmMsg.MsgInit('MS-MAPI Error ' + IntToStr(iError), FLASHBN);
    fmMsg.Show;
    Result := False;
  end;
end;

Open in new window

0
Comment
Question by:ChrisJonesLycos
  • 11
  • 10
22 Comments
 
LVL 23

Expert Comment

by:Ferruccio Accalai
ID: 35490151
There should be something related to your splitter function, as it seems that maybe it adds a CRLF or some other char that is parsed by Pchar and changes the real filename

How do you split the filenames?
0
 

Author Comment

by:ChrisJonesLycos
ID: 35490273
The filenames are simply stripped out by a Copy into a TStringlist so no CRLF is added.

A FileExists(filename) check also finds the file OK. And it works if there's just one file. My suspicion is far more with the adding of files after the first one to the lpFiles. I simply copied this code off the internet so I'm assuming something is missing or incorrect with it.

0
 
LVL 23

Expert Comment

by:Ferruccio Accalai
ID: 35490427

FileExist function is different from how MAPI manage its parameters
Your code seems right but to populate the stringlist I'd use the DelimitedText property of TStringList to prevent any blank or weird char.

Instead or your parsing loop id use something like

slPhysName.Delimiter := '+';
slPhysName.DelimitedText := sPhysName;

slAttachName.Delimiter := '+';
slAttachName.DelimitedText := sAttachName;

Give it a try and tell us the result



0
Concerto's Cloud Advisory Services

Want to avoid the missteps to gaining all the benefits of the cloud? Learn more about the different assessment options from our Cloud Advisory team.

 

Author Comment

by:ChrisJonesLycos
ID: 35490452
Hmmm I like the DelimitedText method for TStringlist - didn't know that existed.

But sorry exactly same result.
0
 
LVL 23

Expert Comment

by:Ferruccio Accalai
ID: 35490511
Are you sure about the count of both the lists?
Is it the same for both of them?

What happens if you use just the slPhysName list and skip the slAttachName?
0
 

Author Comment

by:ChrisJonesLycos
ID: 35490577
Yep, tried that too. Same result.
0
 

Author Comment

by:ChrisJonesLycos
ID: 35490587
Basically I've debugged it right through and examined every variable (over about 15 hours now!) and it's all doing what it's supposed to be doing. The bit I don't know about is the bit I can't see which is actually within the MAPISendMail function. I'm presuming it's memory or something like that.
0
 
LVL 32

Expert Comment

by:Ephraim Wangoya
ID: 35491906

try it this way
var
  ....
  Files: array of PMapiFileDesc;
  ...
begin
  ......

  SetLength(Files, slPhysName.Count);
  for i := 0 to slPhysName.Count - 1 do
  begin
    Files[i].ulReserved := 0;
    Files[i].flFlags := 0;
    Files[i].nPosition := Cardinal($FFFFFFFF);
    Files[i].lpszPathName := PChar(slPhysName[i]);
    Files[i].lpszFileName := PChar(slAttachName[i]);
    Files[i].lpFileType := nil;
  end;

  nFileCount := slPhysName.Count;
  lpFiles := @Files[0]; 
 
  ........

Open in new window

0
 

Author Comment

by:ChrisJonesLycos
ID: 35496698
OK, I'm coming up with an access violation as soon as it tries to access Files in the
      for i := 0 to slPhysName.Count - 1 do
      begin
      ...
      end;
block.

I've tried remming out ulReserved := 0 and flFlags := 0 lines but same result.
0
 
LVL 23

Expert Comment

by:Ferruccio Accalai
ID: 35496727
Just from head. Another 2 cents
I see that you are filling with a 0 byte value your variable mmMsg

FillChar(mmMsg, SizeOf(mmMsg), 0);

I usually do prefer fill any text pointer using #0
and then assign the value using StrNew

As you said that sometime the last char of the first string is lost, maybe it depends on the last empty byte

So try rewriting your function as follows

function LoadEmail2(sRecipEmail, sSenderEmail, sSubj, sBody, sPhysPathname, sAttachFilename: String): Boolean;
var
  sMsg, sPhysName, sAttachName: String;
  slPhysName, slAttachName: TStringlist;
  mmMsg: TMapiMessage;
  mrSender, mrRecip: TMapiRecipDesc;
  mfFileAttach: TMapiFileDesc;
  SM: TFNMapiSendMail;
  Files, FilesTmp: PMapiFileDesc;
  i, iError: Integer;
  MAPIModule: HModule;
begin
  Result := True;
  FillChar(mmMsg, SizeOf(mmMsg), #0);
  with mmMsg do
  begin
    if sSubj <> '' then
      lpszSubject := strnew(PChar(sSubj));
    if (sBody <> '') then
      lpszNoteText := strnew(PChar(sBody));
    if (sSenderEmail <> '') then
    begin
      mrSender.ulRecipClass := MAPI_ORIG;
      mrSender.lpszName := strnew(PChar(sSenderEmail));
      mrSender.lpszAddress := strnew(PChar(sSenderEmail));
      mrSender.ulReserved := 0;
      mrSender.ulEIDSize := 0;
      mrSender.lpEntryID := nil;
      lpOriginator := @mrSender;
    end;
    if (sRecipEmail <> '') then
    begin
      mrRecip.ulRecipClass := MAPI_TO;
      mrRecip.lpszName := strnew(PChar(sRecipEmail));
      mrRecip.lpszAddress := strnew(PChar('SMTP:' + sRecipEmail)); // PChar('SMTP:' + sRecipEmail);
      mrRecip.ulReserved := 0;
      mrRecip.ulEIDSize := 0;
      mrRecip.lpEntryID := nil;
      nRecipCount := 1;
      lpRecips := @mrRecip;
    end
    else
      lpRecips := nil;
    if (sPhysPathname = '') then
    begin
      nFileCount := 0;
      lpFiles := nil;
    end
    else
    begin
      slPhysName := TStringlist.Create;
      slAttachName := TStringlist.Create;
      slPhysName.Delimiter := '+';
      slPhysName.DelimitedText := sPhysName;
      slAttachName.Delimiter := '+';
      slAttachName.DelimitedText := sAttachName;
      // ********************************  this is where we're having the problem *****************************
      // ******************************************************************************************************
      nFileCount := slPhysName.Count;
      Files := AllocMem(SizeOf(TMapiFileDesc) * nFileCount);
      lpFiles := Files;
      FilesTmp := Files;
      for i := 0 to slPhysName.Count - 1 do
      begin
        FilesTmp.nPosition := Cardinal($FFFFFFFF);
        FilesTmp.lpszPathName := strnew(PChar(slPhysName[i]));
        FilesTmp.lpszFileName := strnew(PChar(slAttachName[i]));
        Inc(FilesTmp);
      end;
      // ******************************************* end of problem *******************************************
      // ******************************************************************************************************
      slPhysName.Free;
      slAttachName.Free;
    end;
  end;
  MAPIModule := LoadLibrary(PChar(MAPIDLL));
  if MAPIModule = 0 then
    iError := -1
  else
    try
      @SM := GetProcAddress(MAPIModule, 'MAPISendMail');
      if @SM <> nil then
        iError := SM(0, GetDesktopWindow, mmMsg, MAPI_DIALOG or MAPI_LOGON_UI or MAPI_NEW_SESSION, 0)
        // returns error 11 most times = 'Cannot find attachment' which is the Physical Filepath is this case
      else
        iError := 1;
    finally
      FreeLibrary(MAPIModule);
    end;
  if iError <> 0 then
  begin
    if iError = 1 then
      fmMsg.MsgInit('Email not sent.', FLASHBN)
    else
      fmMsg.MsgInit('MS-MAPI Error ' + IntToStr(iError), FLASHBN);
    fmMsg.Show;
    Result := False;
  end;
end;

Open in new window

0
 

Author Comment

by:ChrisJonesLycos
ID: 35496756
Thanks Ferruccio but unfortunately still no luck with that.

The thing is that with one attachment it works fine. When you add a second one it destroys the structure of the first TMapiFileDesc record so that it can't recognise the physical filename and throws error 11.
0
 
LVL 23

Expert Comment

by:Ferruccio Accalai
ID: 35496782
Ok. Tell me: why are you using files and filesTmp?

I'd change this approach using an array of TMapiFileDesc and then assigning its pointer to lpFiles

I mean as follows (take care about any syntax error, as I'm coding directly on the ExEx editor

function LoadEmail2(sRecipEmail, sSenderEmail, sSubj, sBody, sPhysPathname, sAttachFilename: String): Boolean;
var
  sMsg, sPhysName, sAttachName: String;
  slPhysName, slAttachName: TStringlist;
  mmMsg: TMapiMessage;
  mrSender, mrRecip: TMapiRecipDesc;
  mfFileAttach: TMapiFileDesc;
  SM: TFNMapiSendMail;
  FilesTmp: Array of TMapiFileDesc;
  i, iError: Integer;
  MAPIModule: HModule;
begin
  Result := True;
  FillChar(mmMsg, SizeOf(mmMsg), #0);
  with mmMsg do
  begin
    if sSubj <> '' then
      lpszSubject := strnew(PChar(sSubj));
    if (sBody <> '') then
      lpszNoteText := strnew(PChar(sBody));
    if (sSenderEmail <> '') then
    begin
      mrSender.ulRecipClass := MAPI_ORIG;
      mrSender.lpszName := strnew(PChar(sSenderEmail));
      mrSender.lpszAddress := strnew(PChar(sSenderEmail));
      mrSender.ulReserved := 0;
      mrSender.ulEIDSize := 0;
      mrSender.lpEntryID := nil;
      lpOriginator := @mrSender;
    end;
    if (sRecipEmail <> '') then
    begin
      mrRecip.ulRecipClass := MAPI_TO;
      mrRecip.lpszName := strnew(PChar(sRecipEmail));
      mrRecip.lpszAddress := strnew(PChar('SMTP:' + sRecipEmail)); // PChar('SMTP:' + sRecipEmail);
      mrRecip.ulReserved := 0;
      mrRecip.ulEIDSize := 0;
      mrRecip.lpEntryID := nil;
      nRecipCount := 1;
      lpRecips := @mrRecip;
    end
    else
      lpRecips := nil;
    if (sPhysPathname = '') then
    begin
      nFileCount := 0;
      lpFiles := nil;
    end
    else
    begin
      slPhysName := TStringlist.Create;
      slAttachName := TStringlist.Create;
      slPhysName.Delimiter := '+';
      slPhysName.DelimitedText := sPhysName;
      slAttachName.Delimiter := '+';
      slAttachName.DelimitedText := sAttachName;
      // ********************************  this is where we're having the problem *****************************
      // ******************************************************************************************************
      nFileCount := slPhysName.Count;
      SetLength(FilesTmp, nFileCount);
      for i := 0 to nFileCount - 1 do
      begin
        FilesTmp.nPosition := Cardinal($FFFFFFFF);
        FilesTmp.lpszPathName := strnew(PChar(slPhysName[i]));
        FilesTmp.lpszFileName := strnew(PChar(slAttachName[i]));
      end;
      // ******************************************* end of problem *******************************************
      // ******************************************************************************************************
      slPhysName.Free;
      slAttachName.Free;
      lpFiles := PMapiFileDesc(FilesTmp);
    end;
  end;
  MAPIModule := LoadLibrary(PChar(MAPIDLL));
  if MAPIModule = 0 then
    iError := -1
  else
    try
      @SM := GetProcAddress(MAPIModule, 'MAPISendMail');
      if @SM <> nil then
        iError := SM(0, GetDesktopWindow, mmMsg, MAPI_DIALOG or MAPI_LOGON_UI or MAPI_NEW_SESSION, 0)
        // returns error 11 most times = 'Cannot find attachment' which is the Physical Filepath is this case
      else
        iError := 1;
    finally
      FreeLibrary(MAPIModule);
    end;
  if iError <> 0 then
  begin
    if iError = 1 then
      fmMsg.MsgInit('Email not sent.', FLASHBN)
    else
      fmMsg.MsgInit('MS-MAPI Error ' + IntToStr(iError), FLASHBN);
    fmMsg.Show;
    Result := False;
  end;
end;

Open in new window

0
 
LVL 23

Expert Comment

by:Ferruccio Accalai
ID: 35496795
Mmm, forgot to fill any FilesTmp array before to assign values.
This one should be correct
function LoadEmail2(sRecipEmail, sSenderEmail, sSubj, sBody, sPhysPathname, sAttachFilename: String): Boolean;
var
  sMsg, sPhysName, sAttachName: String;
  slPhysName, slAttachName: TStringlist;
  mmMsg: TMapiMessage;
  mrSender, mrRecip: TMapiRecipDesc;
  mfFileAttach: TMapiFileDesc;
  SM: TFNMapiSendMail;
  FilesTmp: Array of TMapiFileDesc;
  i, iError: Integer;
  MAPIModule: HModule;
begin
  Result := True;
  FillChar(mmMsg, SizeOf(mmMsg), #0);
  with mmMsg do
  begin
    if sSubj <> '' then
      lpszSubject := strnew(PChar(sSubj));
    if (sBody <> '') then
      lpszNoteText := strnew(PChar(sBody));
    if (sSenderEmail <> '') then
    begin
      mrSender.ulRecipClass := MAPI_ORIG;
      mrSender.lpszName := strnew(PChar(sSenderEmail));
      mrSender.lpszAddress := strnew(PChar(sSenderEmail));
      mrSender.ulReserved := 0;
      mrSender.ulEIDSize := 0;
      mrSender.lpEntryID := nil;
      lpOriginator := @mrSender;
    end;
    if (sRecipEmail <> '') then
    begin
      mrRecip.ulRecipClass := MAPI_TO;
      mrRecip.lpszName := strnew(PChar(sRecipEmail));
      mrRecip.lpszAddress := strnew(PChar('SMTP:' + sRecipEmail)); // PChar('SMTP:' + sRecipEmail);
      mrRecip.ulReserved := 0;
      mrRecip.ulEIDSize := 0;
      mrRecip.lpEntryID := nil;
      nRecipCount := 1;
      lpRecips := @mrRecip;
    end
    else
      lpRecips := nil;
    if (sPhysPathname = '') then
    begin
      nFileCount := 0;
      lpFiles := nil;
    end
    else
    begin
      slPhysName := TStringlist.Create;
      slAttachName := TStringlist.Create;
      slPhysName.Delimiter := '+';
      slPhysName.DelimitedText := sPhysName;
      slAttachName.Delimiter := '+';
      slAttachName.DelimitedText := sAttachName;
      // ********************************  this is where we're having the problem *****************************
      // ******************************************************************************************************
      nFileCount := slPhysName.Count;
      SetLength(FilesTmp, nFileCount);
      for i := 0 to nFileCount - 1 do
      begin
        FillChar(FilesTmp[i], SizeOf(TMapiFileDesc), #0);
        FilesTmp.nPosition := $FFFFFFFF;
        FilesTmp.lpszPathName := strnew(PChar(slPhysName[i]));
        FilesTmp.lpszFileName := strnew(PChar(slAttachName[i]));
      end;
      // ******************************************* end of problem *******************************************
      // ******************************************************************************************************
      slPhysName.Free;
      slAttachName.Free;
      lpFiles := PMapiFileDesc(FilesTmp);
    end;
  end;
  MAPIModule := LoadLibrary(PChar(MAPIDLL));
  if MAPIModule = 0 then
    iError := -1
  else
    try
      @SM := GetProcAddress(MAPIModule, 'MAPISendMail');
      if @SM <> nil then
        iError := SM(0, GetDesktopWindow, mmMsg, MAPI_DIALOG or MAPI_LOGON_UI or MAPI_NEW_SESSION, 0)
        // returns error 11 most times = 'Cannot find attachment' which is the Physical Filepath is this case
      else
        iError := 1;
    finally
      FreeLibrary(MAPIModule);
    end;
  if iError <> 0 then
  begin
    if iError = 1 then
      fmMsg.MsgInit('Email not sent.', FLASHBN)
    else
      fmMsg.MsgInit('MS-MAPI Error ' + IntToStr(iError), FLASHBN);
    fmMsg.Show;
    Result := False;
  end;
end;

Open in new window

0
 

Author Comment

by:ChrisJonesLycos
ID: 35496839
Hi Ferroccio. That throws up an error on compile at:

FilesTmp.nPosition := $FFFFFFFF;

"Error: Record, object or class type required"
0
 
LVL 23

Accepted Solution

by:
Ferruccio Accalai earned 2000 total points
ID: 35496858
Of course, my fault.
We are in a loop, so it must be as follows



for i := 0 to nFileCount - 1 do
      begin
        FillChar(FilesTmp[i], SizeOf(TMapiFileDesc), #0);
        FilesTmp[i].nPosition := $FFFFFFFF;
        FilesTmp[i].lpszPathName := strnew(PChar(slPhysName[i]));
        FilesTmp[i].lpszFileName := strnew(PChar(slAttachName[i]));
      end;

Open in new window

0
 

Author Comment

by:ChrisJonesLycos
ID: 35496898
Well I think you've probably cracked it! It's coming out great with no errors. Let me test it with a range of file attachments and I'll come back tomorrow and award points as soon as I'm sure it works under all circumstances. Many thanks for you help.
0
 
LVL 23

Expert Comment

by:Ferruccio Accalai
ID: 35496903
Great. Have a nice testing weekend ;)
0
 
LVL 23

Expert Comment

by:Ferruccio Accalai
ID: 35503180
Well, glad to have helped you
Anyway maybe you have choosed to close the question by accepting my comment as answer for 0 points by mistake.
An A grade assigned to the provided solution would be appreciated
0
 
LVL 23

Expert Comment

by:Ferruccio Accalai
ID: 35503207
Well, glad to have helped you
Anyway maybe you have choosed to close the question by accepting my comment as answer for 0 points by mistake.
An A grade assigned to the provided solution would be appreciated
0
 

Author Comment

by:ChrisJonesLycos
ID: 35503263
Not sure what happened Ferruccio. I assigned you full points!

I'll try again.
0
 

Author Comment

by:ChrisJonesLycos
ID: 35503274
Sorry about that. Hope that's better :-)
0
 
LVL 23

Expert Comment

by:Ferruccio Accalai
ID: 35503299
Fine, I was sure about a mistake.
Thanks and cheers
0

Featured Post

Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

The recent Microsoft changes on update philosophy for Windows pre-10 and their impact on existing WSUS implementations.
Possible fixes for Windows 7 and Windows Server 2008 updating problem. Solutions mentioned are from Microsoft themselves. I started a case with them from our Microsoft Silver Partner option to open a case and get direct support from Microsoft. If s…
In this Micro Tutorial viewers will learn how to use Boot Corrector from Paragon Rescue Kit Free to identify and fix the boot problems of Windows 7/8/2012R2 etc. As an example is used Windows 2012R2 which lost its active partition flag (often happen…
This Micro Tutorial will give you a introduction in two parts how to utilize Windows Live Movie Maker to its maximum capability. This will be demonstrated using Windows Live Movie Maker on Windows 7 operating system.
Suggested Courses
Course of the Month15 days, 10 hours left to enroll

850 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question