News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Attachment for Mail Client ?

Started by Draakie, January 23, 2007, 01:25:03 PM

Previous topic - Next topic

Draakie

Hiya, having a spot of trouble getting information on how to attach a file to a Default Mail client under Windows.
The "mailto:" protocol does'nt look like it has this functionality. SDK's talk about OLE and MIME - and get all flustered
(I ca'nt make out what the hell they are saying and only god knows where to get the header and include files they whine about)
on the subject. Is there an idiot's guide to attaching a file to the body of a message - specifically OE6 if possible anyone ? This is
for strictly ligitimate purposes - just incase some-one was wondering. -> Creating a variable name .CSV file and need to send it via
default mail client to known e-mail addy daily. The users typically feel bogged down at present always having to browse and attach
the file themselves.

Thanx
Draakie


Does this code make me look bloated ? (wink)

Jimg

Hopefully, somebody else more knowledgeable will reply, but just in case nobody else answers, here's something I did last century in vb.  Hopefully it will give you a clue as to the headers involved-

SMTPSend ("From: " + CurFromName$ + vbCrLf)
SMTPSend ("To: " + CurToName$ + vbCrLf)
SMTPSend ("Date: " + CurMsgDate$ + vbCrLf)
SMTPSend ("Subject: " + CurSubject$ + vbCrLf)
' Send the main MIME header
Boundary = "================_" & CDbl(Now) * 10000000000# & "==_"  ' the boundary string
SMTPSend ("Mime-Version: 1.0" + vbCrLf)
SMTPSend ("Content-Type: multipart/mixed; boundary=""" + boundary + """" + vbCrLf)
SMTPSend (vbCrLf)
' Send attachment
SMTPSend (vbCrLf + vbCrLf + "--" + boundary + vbCrLf)
afname = LCase$(Dir(CurAttachment$))
SMTPSend ("Content-Type: application/ASC;" + " NAME=""" + afname + """" + vbCrLf)
SMTPSend ("Content-Type: text/plain; charset=""" + "us-ascii""" + vbCrLf)
SMTPSend (vbCrLf)
SMTPSend ("--" + boundary + vbCrLf)
SMTPSend ("Content-Type: text/plain; charset=""" + "us-ascii""" + vbCrLf)
SMTPSend ("Content-Disposition: attachment;" + vbCrLf + vbCrLf)
ch = FreeFile
Open CurAttachment$ For Binary As #ch
Do
   Buffer = String$(1024, 0)
   Get #ch, , Buffer
   Pos = InStr(Buffer, Chr$(0))
   If Pos > 0 Then Buffer = Left$(Buffer, Pos - 1)
   If Len(Buffer) > 0 Then SMTPSend (Buffer)
Loop Until Len(Buffer) < 1024
Close #ch
' Send the ending boundary string
SMTPSend (vbCrLf)
SMTPSend ("--" + boundary + "--" + vbCrLf)
Dummy = Smtp.SendMail


There are several RFC's having to do with Mime format.  Have a look at RFC1341.

Tedd

You have two options: MIME or OLE (whether either will work is another matter :bdg)

So, MIME is the way 'attachments' are encoded into the body of an email - basically it's a way of creating a text-representation of a file, so it can be inserted into the email and sent along with the 'normal' text. This is what Jimg's example does, and full details are in the relevant RFC(s).
The only problem you might have with this is that you're not sending the email yourself - you're giving it to outlook to send. So chances are that outlook will take your mime'd text and insert it as normal text into the email. Which means when the email client recieves it at the other end, it will come up as garbage added to the end of the email, instead of being recognised as an attachment. If you do have the option of actually sending the email yourself, then this won't be a problem (but you'll probably have other problems to solve ::))

The other way might be through OLE - if outlook provides a way of controlling itself (I think it does - you can create emails through word?), then you should be able to instruct it to create the email with the attachment and send it all off. Details on how to do this are another matter, but I'm guessing the details are out there somewhere. If you find an example from another language (most likely VB or .net) then drag it over here and we might be able to kick it until it gives up and works :wink
No snowflake in an avalanche feels responsible.

Draakie

[FROM MICROSOFT]
Simple MAPI is a set of functions and related data structures you can use to add messaging functionality to C, C++, or Visual Basic Windows applications. The Simple MAPI functions are available in C and C++ and Visual Basic versions.

[FROM a post by B. BRYANT via http://discuss.joelonsoftware.com]

#include <MAPI.h>
HWND hwndParent = NULL;
const char* pszAttachPathname = "c:\\temp\\test.txt";
const char* pszAttachFilename = "test.txt";
const char* pszTo = "someone@example.com";
const char* pszBody = "blah blah";
ULONG (PASCAL *lpfnMAPISendMail)(ULONG, ULONG, MapiMessage*, FLAGS, ULONG);
HMODULE hMAPI = LoadLibrary( "MAPI32.DLL" );
int nError = -1;
if ( hMAPI )
{
(FARPROC&)lpfnMAPISendMail = GetProcAddress(hMAPI,"MAPISendMail");
if ( lpfnMAPISendMail )
{
MapiMessage message;
MapiFileDesc filedesc;
MapiRecipDesc recip;
memset( &message, 0, sizeof(message) );
if ( pszAttachPathname )
{
ZeroMemory( &filedesc, sizeof(MapiFileDesc) );
filedesc.nPosition = (ULONG)-1;
filedesc.lpszPathName = pszAttachPathname;
filedesc.lpszFileName = pszAttachFilename;
message.nFileCount = 1;
message.lpFiles = &filedesc;
}
ZeroMemory( &recip, sizeof(MapiRecipDesc) );
recip.ulRecipClass = MAPI_TO;
recip.lpszName = pszTo;
message.nRecipCount = 1;
message.lpRecips = &recip;
message.lpszSubject = (char*)(LPCTSTR)csSubject;
message.lpszNoteText = pszBody;
// Bring up the send message dialog
nError = lpfnMAPISendMail(0, (ULONG)hwndParent, &message, MAPI_LOGON_UI|MAPI_DIALOG, 0);
}
FreeLibrary( hMAPI );
}
return nError; // 0/SUCCESS_SUCCESS 1/MAPI_USER_ABORT, 11/MAPI_E_ATTACHMENT_NOT_FOUND

- would this do Tedd ?

Thanx again
Draakie
Does this code make me look bloated ? (wink)

Tedd

#4
Aaah, MAPI - why didn't I think of that?! :lol

That example will do, as long as it does what you want :P

It uses the "MAPISendMail" function which allows you to specify all of the fields and have almost full control over the email created, you can even have it sent automatically. But, of course, more options means there's quite a bit to set up before you can use it (although you can avoid setting most things up and have the user fill in the "To:", "Subject:", etc.)

There's another function, "MAPISendDocuments", which doesn't require half as much set up, but the user has to fill in the all of fields. I thought this might be closer to what you require though - it just does the attachment, nothing else.

..et voila!

.586
.model flat, stdcall
option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
include user32.inc
includelib user32.lib

;***************************************************************************************************

;;"MAPISendDocuments" <http://msdn2.microsoft.com/en-us/library/ms529061.aspx>
;;ULONG MAPISendDocuments(ULONG ulUIParam,LPTSTR lpszDelimChar,LPTSTR lpszFullPaths,LPTSTR lpszFileNames,ULONG ulReserved)
; composes an email, with the given file(s) attached, user must fill in fields

;***************************************************************************************************

.data
AppName         db "Snail",0
mapiDll         db "MAPI32.DLL",0
mapiSendDocs    db "MAPISendDocuments",0

errDll          db "Unable to load MAPI32.DLL",0
errFunc         db "MAPISendMail function not found.",0
errCompose      db "Compose failed",0

files           db "C:\\temp\\report.txt",0     ;list of files to attach, delimited by char_sep (eg. "file1;file2;file3")
char_sep        db ";"
nil             db 0                            ;so the filenames are extracted from their pathname

.data?
hMapiDll    HMODULE ?
fSendDocs   DWORD ?     ;function pointer

.code
start:
    invoke LoadLibrary, ADDR mapiDll
    .IF (eax)
        mov hMapiDll,eax
        invoke GetProcAddress, eax,ADDR mapiSendDocs
        .IF (eax)
            mov fSendDocs,eax

            push NULL               ;ULONG ulReserved
            push OFFSET nil         ;LPTSTR lpszFileNames
            push OFFSET files       ;LPTSTR lpszFullPaths
            push OFFSET char_sep    ;LPTSTR lpszDelimChar
            push 0                  ;LONG ulUIParam
            call DWORD PTR [fSendDocs]

            .IF (eax != SUCCESS_SUCCESS)
                ;** check the error code and give more information
                invoke MessageBox, NULL,ADDR errCompose,ADDR AppName,MB_OK or MB_ICONERROR
            .ENDIF


        .ELSE
            invoke MessageBox, NULL,ADDR errFunc,ADDR AppName,MB_OK or MB_ICONERROR
        .ENDIF
        invoke FreeLibrary, hMapiDll
    .ELSE
        invoke MessageBox, NULL,ADDR errDll,ADDR AppName,MB_OK or MB_ICONERROR
    .ENDIF
    invoke ExitProcess, NULL

end start



If you really need the fields automatically filled then we can try using MAPISendMail :8)


[Edit: I'm SO smart I pushed the parameters in the wrong order - surprising it still worked though :dazzled:]
No snowflake in an avalanche feels responsible.

Draakie

Hi Teddy [(wink) - LOL at PM.]

;----------------------
O.k here's the other example hardcoded into ASM. Seesh this was all easier than
I thought. Just another API really - so why was I stressed ?  :P - I skipped the error
checking to make it readable - naughty really.
;----------------------

MapiLibStr    db 'MAPI32.dll',0
MapiSndStr db 'MAPISendMail',0
MapiPath   db 'C:\test.dat',0
MapiFile   db 'test.dat',0
MapiRecip   db 'a@b.co.za',0
MapiSubjct   db 'Testing MAPI',0
MapiBody   db 'Test Test 1 2 3 4 etc.',0

MMessage MapiMessage  <?>
MFileDsc MapiFileDesc     <?>
MRecipnt MapiRecipDesc <?>

SendMailProc proc hWnd:DWORD

LOCAL hMAPI,dll_SEND : DWORD

   invoke LoadLibrary, offset MapiLibStr
   mov hMAPI, eax
   invoke GetProcAddress,hMAPI, offset MapiSndStr
   mov dll_SEND, eax
   invoke RtlZeroMemory,addr MMessage, sizeof MMessage
   invoke RtlZeroMemory,addr MFileDsc, sizeof MFileDsc
   invoke RtlZeroMemory,addr MRecipnt, sizeof MRecipnt
   mov MFileDsc.nPosition      , -1
   mov MFileDsc.lpszPathName   , offset MapiPath
   mov MFileDsc.lpszFileName   , offset MapiFile
   mov MRecipnt.ulRecipClass   , MAPI_TO   
   mov MRecipnt.lpszName   , offset MapiRecip
   mov MMessage.nFileCount   , 1
   mov MMessage.nRecipCount   , 1
   mov MMessage.lpFiles      , offset MFileDsc
   mov MMessage.lpRecips   , offset MRecipnt
   mov MMessage.lpszSubject   , offset MapiSubjct   
   mov MMessage.lpszNoteText   , offset MapiBody
   push NULL
   push hWnd
   push offset MMessage
   push MAPI_LOGON_UI or MAPI_DIALOG
   push NULL
   call dll_SEND
   invoke FreeLibrary,hMAPI
   ret   
SendMailProc endp
Does this code make me look bloated ? (wink)

jj2007

Hi everybody,

SendMail works like a charm, with one caveat: While Outlook warns me that "a program is trying to automatically send e-mail" (fine for me), it refuses categorically to take notice of the MAPI_DIALOG flag. I tried several options, with and without MAPI_LOGON_UI and/or MAPI_NEW_SESSION, but no success - the damn thing gets (successfully) sent without user interaction.
A Google search reveils I am not the only one to complain (http://forge.novell.com/pipermail/gwmapi-dev/2005-March/000071.html), but no solutions offered. Does anybody observe the same behaviour?

Tedd

Well msdn says:
QuoteA dialog box should be displayed to prompt the user for recipients and other sending options. When MAPI_DIALOG is not set, at least one recipient must be specified.
But it depends what you take 'should' to mean :bdg. I'm guessing if there's already a recipient then it's not bothering to wait (which is still contrary to what's implied by the above.)

One thing to check: I made a slight (as in huge) mistake in my previous example - I pushed the parameters in forward order (left-to-right), when they should go in reverse. I did test the code, and by some miracle it works. And Draakie just copied my mistake.
Anyway, I've fixed it now, so you might want to check you're pushing yours in the correct order, which could explain why the flags are being ignored.
Failing that, it's just messed up and you'll have to leave out the recipient address so it's forced to ask for details.
No snowflake in an avalanche feels responsible.

jj2007

Quote from: Tedd on October 02, 2007, 12:14:49 PMI pushed the parameters in forward order (left-to-right), when they should go in reverse. I did test the code, and by some miracle it works. And Draakie just copied my mistake.
Tedd, how could you possibly do that  ::) ??
Anyway, now it works as it should. And I discovered a delicate "feature" of Outlook: if you use the MAPI_BCC feature, it does add a bcc recipient that is NOT visible in the Outlook dialog unless you choose to click on the To: or CC: button to open the "Select names" dialog.
Another "feature" of Outlook: the Send button doesn't work; the user has to press Ctrl+Enter to send it off.
Thanks a lot - this was really helpful. Below the full listing with the "delicate" feature.

.data?
   MMessage   MapiMessage <?>
   MFileDsc   MapiFileDesc <?>
   MRecipnt   MapiRecipDesc <?>
   MReciBCC   MapiRecipDesc <?>

.data
   MapiLibStr   db 'MAPI32.dll',0
   MapiSndStr   db 'MAPISendMail',0
   MapiBody   db 'Please find attached a file',0
   MapiTo   db ';',0
   MapiBCC   db 'MySecretAccount@masm32.com',0
.code
   invoke LoadLibrary, offset MapiLibStr
   mov hMAPI, eax
   invoke GetProcAddress,hMAPI, offset MapiSndStr
   mov dll_SEND, eax

   invoke RtlZeroMemory,addr MMessage, sizeof MMessage
   invoke RtlZeroMemory,addr MFileDsc, sizeof MFileDsc
   invoke RtlZeroMemory,addr MRecipnt, sizeof MRecipnt
   invoke RtlZeroMemory,addr MRecipnt, sizeof MReciBCC

   mov MFileDsc.nPosition, -1
   mov eax, MsgText
   mov MFileDsc.lpszPathName, eax   ; the DIS file path
   ; mov MFileDsc.lpszFileName, 0   ; not needed, path will be used
   mov MMessage.nFileCount, 1   ; one attachment
   mov MMessage.lpFiles, offset MFileDsc

   mov MRecipnt.ulRecipClass, MAPI_TO
   mov MRecipnt.lpszName, offset MapiTo ; not needed if nRecipCount=0

   mov MReciBCC.ulRecipClass, MAPI_BCC
   mov MReciBCC.lpszName, offset MapiBCC
   mov MMessage.nRecipCount, 0   ;2 if MapiBCC used, 0 if not
   mov MMessage.lpRecips, offset MRecipnt

   mov eax, MsgTitle
   mov MMessage.lpszSubject, eax   ; Subject=MsgTitle
   mov MMessage.lpszNoteText, offset MapiBody   ; DIS attached
   push NULL         ; reserved (order ok 3.10.)
   push MAPI_DIALOG or MAPI_LOGON_UI   ; flFlags
   push offset MMessage      ; lpMessage
   push NULL         ; ulUIParam
   push NULL         ; lhSession
   call dll_SEND
   push   eax
   invoke FreeLibrary,hMAPI
   pop   eax   ; give the SendMail error code back



jj2007

Folks, I tested the MAPISendMail extensively now. Here is my experience:
- the code posted above works like a charm; thanks to Tedd and Draakie!

- on my office pc here, I have Outlook installed:
  with MAPI_DIALOG set, and correct pushing order, it displays now the dialog
  without MAPI_DIALOG, there is a security warning (rightly so)
  with one HTML attachment, formatted text from the attachment appears in the body below the lpszNoteText followed by a horizontal line (a useful feature)
  with more than one attachment, the first HTML attachment will be displayed as attachment, and the body will contain only the lpszNoteText (a useless feature)
  as a consequence, there is apparently no way to have an attachment AND a formatted body (and I have googled a lot for finding one)

- on my home pc, I use Thunderbird:
  while the mailto: protocol launches Thunderbird, MAPISendMail launches Outlook Express
  so I was forced to put my login etc into Outlook Express
  then MAPISendMail works perfectly
  but Outlook Express downloaded 600+ messages from my webmail and deleted them on the server without asking for my consent. Thanxalot, Bill, you made my day again  :green

Just in case moderators wonder about my sinister intentions: I send attachments from the Dashboard of Sustainability, http://esl.jrc.it/envind/dashbrds.htm - install, move the mouse into the upper right corner, click on Export, then ZipDis at the bottom of the popup.

Regarding the wrong pushing order problem: My strong support to Hutch for insisting to use invoke and other high-level elements. Three experienced programmers overlooked this little problem, and lost time with it. Those who insist to write "pure" assembler should be warmly encouraged to poke their bytes directly into memory, instead of relying on these popular gimmicks called "mnemonics"  :bg