Introduction

This article provides a code snippet in order to programmatically send one or more files to the mail recipient, mimicking the SendTomail recipient shell extension. I have heard many people searching for this feature, and actually I thought that, as there doesn't seem to be such source code posted on the net, I could just as well post it.

Introducing the SendTo mail shortcut

You may skip this section if you are not interested in the r.e. technique.

The SendTo mail shortcut is a shell extension. See Mike Dunn's complete idiot guide for further info.

The trick is to find out that the SendTo mail recipient shortcut is actually essentially an empty file with .MAPIMAIL as (hidden) extension name. Then, by looking up file type association in the registry (HKCR\.MAPIMAIL), it's straight forward to figure out that it is targeting a COM object with clsid = {9E56BE60-C50F-11CF-9A2C-00A0C90A90CE}. This object is sendmail.dll, a COM helper which takes advantage of either Outlook Express or Outlook to send e-mail file attachments. Next, looking up this CLSID in OLE View clearly showed that the sendmail COM component also implements the IDropTarget, IShellExtInit and IPersistFile interfaces, just like any drop handler.

Ok, basically I need to prepare a bag with dropped filenames, make sure they can be retrieved by implementing the IDataObject interface, a simple communication interface, and then mimic a standard drag and drop sequence, with two check points : DragEnter(IDataObject*) and Drop(IDataObject*).

One of the interesting points is to start implementing the IDataObject interface starting with contract, i.e. the methods it is supposed to expose. And then cowardly insert a breakpoint into any default method implementation only to get to know whether it's called or not, and in what order.

If you are interested in mimicking other SendTo shortcuts, don't hesitate to check out this registry key: HKLM\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved.

IDataObject implementation

The following code implements the IDataObject interface. In fact, only a few methods are required to be implemented. Those are, in order:

EnumFormatEtc(), called by the sendmail helper to know what content formats the IDataObject is holding

IEnumFORMATETC::Next(),Reset(), called to list all formats. We are expected to let the sendmail helper know that we do hold the CF_HDROP clipboard format (standard used for file drag-and-drop support), even if in fact we are not using the clipboard at all.

GetData(), called to actually get the list of files we are willing to send.

Because on Windows file drag-and-drop operations rely on the CF_HDROP / DROPFILES structure, we simply prepare such a structure to play with. Here is the code :

Why SendTo is better than the mailto trick

You could tell me that shell-executing a mailto URL is just as fine, and much simpler code in practice. Yes and no. Yes, it does so, and it can't be simpler since that's only one line of code (just remember to escape ASCII chars in the body with hex replacements of the form %0D, %20, ...).

No, it does not provide support for file attachment(s), which is why the SendTo shortcut comes handy. And there is more to it. The truth is that ::ShellExecute() cannot handle parameter strings over 2048 bytes, which means your e-mail body size cannot go beyond 2048 bytes. If you try to send a large e-mail, it will result in a GPF. At this point, you could replace ::ShellExecute with a well thought ::WinExec call, using the actual mailto command line declared in the registry and target the current e-mail client (for instance, "%ProgramFiles%\Outlook Express\msimn.exe" /mailurl:%1). But then the limitation is 32 KB. As a conclusion, there is no way to send e-mails larger than 32KB using the mailto protocol. The SendTo shortcut explained in this article is definitely the way to go !

History

First relaese -March 22, 2003.

Updated on April 1st, removal of the clipboard use (Chris Guzak).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

I'm not sure what the difference is between your and Rods code, seems to be in using IDataObject in the shell, and not simulating (impl) the interface.
Probably some subtle change from NT to XP.

On NT I notice in Rods code it doesnt call unknown interface, but on XP it goes crazy. Naturally the shell on XP will know how to deal with the different conversation, and your code allows for that.

I think Rod had a fantastic "reverse engineering" idea, and I think you nailed the code.

I think you should mail your code to Rod and let him update this article with your alternative.

I took your code out of the DLL and ran it in a WTL app, on NT and XP, works.

Do have some questions, I dont really understand shell programming too good, IData on a collection of files seems strange to me. I was wondering if that collection can be across paths, or more to the point, do you think its possible to trick the shell into attaching files from different drives.

One solution is to create copies in a temp folder, but I was wondering anyway.

Anyway well done, I searched the web and found nothing like this... should call it RodsCrash technique.

Should also send it to Microsoft with a footnote, "if you still do C++, heres an idea"

HEY IT WORKS ON XP! You are the first to test it there. I hadn't got to it yet. Thanks!

I can't take much credit for this, Rod explained it all and reversed it waay cool. Fuchs (check his posts) worked on the xp thing by using the Seashell Code (that seashell guy is a shell god - you should download that code and check it out) but from what Fuchs was saying, Fuchs still was using Rods code (even though I don't think it was executing). I pretty much stared at all 3 of these guys' code for half a day and simplified it (I think). I'm not going to give microsoft any credit for making this easy, but it is kind of cool the way you can just hand it any file (after doing 50 pain in the arse steps that is) and get the already created interfaces. It actually does work if you pass it the full path and filename of any link in the "send to" folder instead of the classid of the drop target. It doesnt work for some reason if you pass it the full path and filename of the sendmail.dll.

I don't know didly about shell programming, and as a matter of fact, when they started sticking 'I' in front of everything a couple years back I just hid out in vb6 waiting for it all to go away. So this was my first attempt doing anything with GUID's and COM. From what I could tell about the IDataObject, it looks like it only supports one folder.

"pFolder" implies the Idl's have to be relative to 1 folder. Then in GetUIObjectOf, MS says apIdlFiles is an "address of an array of pointers to ITEMIDLIST structures (I think it is really an array of pointers, not an address of an array), each of which uniquely identifies a file object or subfolder relative to the parent folder. Each item identifier list must contain exactly one SHITEMID structure followed by a terminating zero." Well if you can only give it an Idl that is one SHITEMID deep, then you can only provide files (or folders), no paths\filenames, since a path entry (entry1:\entry2\entry3\entry4.ext) counts as one (or more) SHITEMID's in an idl and a file would count as another. I tried to hand it longer Idl's and it barfed. So I ended up doing the temp folder thing, just like you said.

You can see why rods IDataObject Implementation fails on XP, when you run your code on XP and attach jpegs, it doesnt just attach them, it asks you if you want to make them smaller because email will go quicker etc.

All that stuff I imagine means the IDataObject and the IDropTarget are having a huge conversation, even IPicture and stuff must get involved. Your way, that stuff doesnt matter.

One thing I have noticed and I'm not sure if it has anything to do with the shell, but if one allocates strings like
TCHAR *lpString = new TCHAR[500];
and then later
delete [] lpString;

it doesnt like it.
Probably have to use IMalloc and do things like you did it, which is totally foreign to me.

Wow! Where did you see documentation about asking for smaller files? Amazing. Yeah, I pretty much figured if it was all done by the shell, then it should work everywhere. I think doing it the way I did is the way it was intended. Who knows, the dang thing is so complex. There are at least 50 ISomethingOrAnother interfaces in the shell api.

IMalloc. Whenever microsoft gives you a new way to allocate memory when using a set of functions, I always figure its a lot safer to use it. Differnent mechanisms to allocate memory use different heaps, local, global, shared memory, etc who knows. Wouldn't surprise me if new worked or if it didn't work. You can't tell till you try. It makes sense that it does not work, since it looks like there is some interprocess stuff going on. New is your local heap obviously.

Your welcome, thanks for testing it on XP.

By the way if you are going to use it for doing email attachements, and you don't know the email client, you might want to make the clsid for the drop target a setting that can be changed. Thats what I did. sendmail.dll seems to be used by eudora, outlook and outlook express. I downloaded a email client called calypso, and it did not use sendmail.dll. Calypso added their own send to link. I just change the classid to "c:\\blah\\blah\\calypsosendorsomething.lnk" and it works.

Also I noticed you can pass %program files% as the path. I think you can also pass %send to% as well. There are things called CSIDL's (search for CSIDL Values) that are paths to all the standard paths.

Thanks to everyone in this forum, I was able to make a dll that can be called from vb6 (in case anyone cares). This sure was a pain in the butt, without this article and subsequent posts, I never would have figured this out. If someone can tell me where to put it on some ftp site, I would love to upload it (compiled dll with all sourceode). Until then here is the source, and you can email me if you want the dll.

My method is a modification of the base method that requires no extra classes (yeah!), helper functions or anything. In the original example, stephan had to create a IDataObject derived class. In solving the XP problem, fuchs showed how the shell already has IDataObjects for files that you can use.

I think it is easy to understand (for com). It has to be since I knew diddly about c++ com programming until today. If I did some stupid stuff please tell me. I only tested this a couple times, so it probably has a bug. I will fix the bugs as I am using this in a deliverable, and I will repost.

Tested on Outlook express and Outlook.

Anyway, here is all the code

---------------------------------
vb code to call it. Throw this on a form with a button.
---------------------------------

---------------------------------
dll source code follows...
Use vc6 to make a simple win32 dll named xMain32b, and then replace stdafx.h and xmain32b.cpp with the below code, and add xmain32b.def.
---------------------------------

I tested this on a couple email programs and it works fine for outlook, outlook express, and eudora. I downloaded a random email client calypso and of course, they don't follow the standard. Calypso adds their own entry "Calypso E-mail" to the send to menu. It is implemented by a shortcut put into the sendo to directory. I looked in the internet explorer settings dialog under Tools|Internet Options|Programs and Calypso is listed there. Calypso is hooked into the mailto url.

Can this code easily be modified to work with .lnk files...how do you get the IDropTarget interface?

Ok, this does it all. I am done. You can send to anything that supports IDropTarget. This includes GUID's, .lnk files, .dll files, .exe files, and maybe more, I don't know. I can send files to anything in the send to folder easily. Use this dll (or code) to mimic Send To | Mail Recipient. I still don't have an ftp site to upload the zip file with the compiled dll and source code, so eMail me if you want it.

One problem is the implementation of QueryInterface().
There should be a check for the queried interface class.
Otherwise the code says, it would return a pointer to e.g. IMarshal, which it realy does'nt.

2) Have you tested this on windows 2000, running outlook express? I downloaded the test code, which does the test in main(). It works for outlook, but not outlook express. With express, after Drop() is called, the email appears in express' compose mail window like I want, but I cannot give that email the focus or type into it - its in a locked state, probably waiting for something from my test program. When the test program ends, the compose window disappears. What is going on? It seems like outlook express expects the handoff to be performed slightly differently.

The remaining problem is now, the process will run forever. It doesn't terminate when you close the mailer window.

Last thing, I would like to insert is a safety check in GetData():

if (!pFormatetc)
return S_FALSE;

I will post my own code without this IDataObject implementation which is discussed here to a new article when I will have made a bit of cleanup. I think, it's a bit cleaner and does not need as much code as with the method discussed here.

By the way:
Could someone point me to how I can insert empty lines in <code> sections of this editor?

You dont need to modify GetData(), since it is not being used nor called anymore! You get the IDataObject from GetUIObject() and are using that one!? I think I am right, but I have been programming this com stuff for 2 days now and could be mistaken. Check out my code in the new thread above to see what I mean. I didn't include any of stephan's classes and my code works.

I picked up on the message loop thing too. Since I am calling from vb6, I can tell you the vb6 message loop is sufficient, and no sleep, or doevents is needed in a dll function that sends files to email.

Also, the IDropTarget object needs to stay around for the duration of the email message within the email client. I made the IDropTarget global to the dll in my code and created/deleted it in dllmain().

This is a real good tool. I had been searching for ages to find something like this, of cause i actually would have liked to have had it within a activex dll, but thats no prob as was able to get working.

Ive been using this now for a week on a few operating systems and had been working well untill on a winxp machine.

I decided to see why i was getting an error on winxp and used the same code and yes it dies. Im unable to see exactly where, but was after a call to GetData that this seemed to happen.

I would be appreaciated for any assistance in working through this for WinXP as it works great eveywhere else.

Oh by the way the tests were on WinXP Pro (havent tested on home ed)
Email address is gsmith@autointegrity.com.au

This source code mimics the Send To mail recipient windows shortcut. It works regardless the email client, whether it's Outlook Express, Outlook, or whatever default email client. Of course, the point of this article is not to send an email using Outlook, when your default email client is Outlook Express, for instance. In order to do that, just use CDO (MS SDK).

Do you provide this in the cmdline and really get it working with an attachment ? I don't get it to work. I question it since :
- the "attach" header is not even documented in the standards (mailto RFC).
- this trick does not work on either of my W2K and WXP workstations
- on the web, about managing file attachments, there is not a single native solution using the mailto protocol.

I wonder you have either of these :
- a custom mailto protocol handler. Can you check out your registry and tell me if the CLSID at this key "HKCR \ PROTOCOLS \ Handler \ mailto" is this : {3050f3DA-98B5-11CF-BB82-00AA00BDCE0B}
- you have a custom OS install or QFE where mailto is enhanced.

My OS is Windows 2000 SP3. I use Internet Explorer 6.0.2600. The registry key is as you indicated (which is mshtml.dll). Nothing special about my machine (that I know of) as it's used for development and I keep it pretty clean.

May be that's the SP3. I'll check it out tomorrow, but that's only in order to know why this works. In practice, I believe no one can assume that those using the application have the SP3 installed.
Thanks.

"attach" is not part of standard mailto: protocol. It worked for me when I had Outlook 2000 installed, no longer works when I installed Outlook 2002 (XP). It doesn't work with Outlook Express, and same about many other email clients.

Yes MFC provides a default support for the simple MAPI. And CDocument provided you with the default handlers for the mentioned ID which call MAPISendDocuments() internally to attach your application specific document with the mail message. So this is very simple, just a matter of selecting the MAPI option in the wizard.

The point of this article is to show the technique and what's required to mimic a SendTo shortcut by code, and especially SendTo mail recipient.
This is beneficial not only to SendTo mail recipient, but actually to all SendTo shortcuts you might want to mimic, for instance SendTo MyDocuments.

It's true that, while we are at it, using Simple MAPI for sending an email with or without interaction, with or without file attachments, is simple stuff you can rely on.