How can a Delphi Program send an Email with Attachments via the DEFAULT E-mail Client?
Within my program, I am composing an email to send using the default e-mail client software installed on a user's machine.
I have composed the mailto address, the subject, the multilined body, and I have several attachments to be included.
I almost got this working using mailto and ShellExecute as follows:
Message := 'mailto:someone@somewhere.com'
+ '?subject=This is the subjectBehold Error Report'
+ '&body=This is line 1' + '%0D%0A'
+ 'This is line 2' + '%0D%0A'
+ 'This is line 3'
+ '&Attach=c:\file1.txt';
RetVal := ShellExecute(Handle, 'open', PChar(Message), nil, nil, SW_SHOWNORMAL);
if RetVal <= 32 then
MessageDlg('Cannot find program to send e-mail.', mtWarning, [mbOK], 0);
Using Delphi 2009 on a Windows Vista machine, this will open a Microsoft Mail "Create Mail" window, with the To, Subject and Body filled correctly. However the file does not get attached.
As I researched this, I noticed some commentary stating that this technique does not work with all mail clients. However, most of the commentary is fairly old, as I realize this is a very old technique.
Then I found that Zarko Gajic said that "this approach is ok, but you are unable to send attachments in this way".
I have seen theres also the Windows Simple Mail API (MAPI), but Zarko says that only works if the end-user has MAPI-compliant email software. There are well documented techniques on using MAPI with Delphi (e.g. Sending e-mail using mapi), but they all have the disclaimer that MAPI is not always installed with Windows.
Besides, I really want the message to come up first in the user's default email program, so they will have it as part of their email records and they can edit it and decide if and when they want to send it. I'm not sure how MAPI works and if it will do that.
So my requirements are:
To bring the email up in the user's mail program.
To allow one or more attachments.
To work with (hopefully) all email clients on any Windows machine from XP up (i.e. XP, Vista or 7).
Is there such an animal? Or maybe does someone know how to get attachments to work with the mailto/ShellExecute technique?
What do most people do?
Edit:
There have been a few answers with MAPI solutions and even an Indy so开发者_Go百科lution.
The problem I have with them is that they don't necessarily use the default mail client. On my Vista machine, for example, I have set up Windows Mail as my default client. When I do a MAPI send, it does not bring up Windows Mail, but it brings up and sets up the email in Outlook instead. I don't want that.
Two of my users of my program complained:
Your debug routine fails to send the file, as it tries to start windows mail for some reason known to it's self rather than using the default mail client (in my case thunderbird)
I tried to fill up the exception report but gave up when it asked for this server, that server! I then got really annoyed because it launched Outlook - I never, ever use it or want to use it.
I don't need code for MAPI or Indy. They are readily available. But if you suggest MAPI or Indy, what I really need is a way to find the default client and ensure that it is the one that is passed the email to be sent.
Also, I need to know if MAPI is now universal. 5 years ago, it wasn't guaranteed to work on all machines because it wasn't installed as part of the operating system. Is that still true, or does MAPI now come with Windows XP, Vista and 7 by default?
Same questions go for Indy or any other suggested solutions. Can it work with the default client and will it work on almost all Windows XP and later machines?
The reason why the "mailto" solution is so nice, is that all machines have to support it for the purpose of handling the HTML mailto statement found on webpages. Now if only I could use it to add attachments ...
Likely solution found: mjustin pointed out an alternative that makes use of the Operating System's sendto command. That most likely is the way to go.
The mailto was not limited to 256 characters like the HTML mailto is, but I was devastated to find out it ended up being limited to 2048 characters. Fortunately a few hours later, mjustin gave his answer.
If implementation of that goes okay, his answer will have done it for me. If not, I'll add my comments here.
No. As it turns out, the sendto solution will not always open the default email program. On my machine, it opens Outlook when my default mailer is Windows Mail. Too bad. I've had to go back to the mailto method, despite the 2048 character limit.
I did, however, find in the article: SendTo mail recipient that:
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.
but then I'd have to determine who the mail client is in each case. I expect that would lead to further complications.
The one other thing I found out is that mailto allows setting of "to", "cc", "bcc", "subject" and "body" but no attachments. Whereas sendto ONLY allows attachments and then sets up a default email with a default message and no way for you to set the various fields and body.
Do not complicate, just use the JCL MAPI code. It is in the unit JclMapi.pas. I think they also have the example for it. The code is very powerfull and you can do anything that MAPI allows you.
With ShellExecute you cannot send the attachment and you are also limited to 255 characters for the mail body.
As long as MAPI goes, with old windows it is always installed (2000, XP). It comes together with Outlook Express and Outlook Express is almost always installed. With newer windows (Vista, 7) there is no Outlook Express and so no MAPI. But MAPI is automatically installed if you install MS Outlook or Mozzila Thunderbird. So you are pretty safe. This is basic MAPI and not extended MAPI. But it covers all you need.
You can also check in your code (JCL) if MAPI is installed and act acordingly. I have done a similar thing not so long ago and it works ok. I have not found a popular windows mail client that does not support simple MAPI. This is a simple wrapper around JCL code and the sample usage bellow:
unit MAPI.SendMail;
interface
uses
SysUtils, Classes, JclMapi;
type
TPrerequisites = class
public
function IsMapiAvailable: Boolean;
function IsClientAvailable: Boolean;
end;
TMAPISendMail = class
private
FAJclEmail: TJclEmail;
FShowDialog: Boolean;
FResolveNames: Boolean;
FPrerequisites: TPrerequisites;
// proxy property getters
function GetMailBody: string;
function GetHTMLBody: Boolean;
function GetMailSubject: string;
// proxy property setters
procedure SetMailBody(const Value: string);
procedure SetHTMLBody(const Value: Boolean);
procedure SetMailSubject(const Value: string);
protected
function DoSendMail: Boolean; virtual;
public
constructor Create;
destructor Destroy; override;
// properties of the wrapper class
property MailBody: string read GetMailBody write SetMailBody;
property HTMLBody: Boolean read GetHTMLBody write SetHTMLBody;
property ShowDialog: Boolean read FShowDialog write FShowDialog;
property MailSubject: string read GetMailSubject write SetMailSubject;
property ResolveNames: Boolean read FResolveNames write FResolveNames;
property Prerequisites: TPrerequisites read FPrerequisites;
// procedure and functions of the wrapper class
procedure AddRecipient(const Address: string; const Name: string = '');
procedure AddAttachment(const FileName: string);
function SendMail: Boolean;
end;
implementation
{ TMAPISendMail }
constructor TMAPISendMail.Create;
begin
FPrerequisites := TPrerequisites.Create;
FAJclEmail := TJclEmail.Create;
FShowDialog := True;
end;
destructor TMAPISendMail.Destroy;
begin
FreeAndNil(FAJclEmail);
FreeAndNil(FPrerequisites);
inherited;
end;
function TMAPISendMail.DoSendMail: Boolean;
begin
Result := FAJclEmail.Send(FShowDialog);
end;
function TMAPISendMail.SendMail: Boolean;
begin
Result := DoSendMail;
end;
function TMAPISendMail.GetMailBody: string;
begin
Result := FAJclEmail.Body;
end;
procedure TMAPISendMail.SetMailBody(const Value: string);
begin
FAJclEmail.Body := Value;
end;
procedure TMAPISendMail.AddAttachment(const FileName: string);
begin
FAJclEmail.Attachments.Add(FileName);
end;
procedure TMAPISendMail.AddRecipient(const Address, Name: string);
var
LocalName: string;
LocalAddress: string;
begin
LocalAddress := Address;
LocalName := Name;
if FResolveNames then
if not FAJclEmail.ResolveName(LocalName, LocalAddress) then
raise Exception.Create('Could not resolve Recipient name and address!');
FAJclEmail.Recipients.Add(LocalAddress, LocalName);
end;
function TMAPISendMail.GetMailSubject: string;
begin
Result := FAJclEmail.Subject;
end;
procedure TMAPISendMail.SetMailSubject(const Value: string);
begin
FAJclEmail.Subject := Value;
end;
function TMAPISendMail.GetHTMLBody: Boolean;
begin
Result := FAJclEmail.HtmlBody;
end;
procedure TMAPISendMail.SetHTMLBody(const Value: Boolean);
begin
FAJclEmail.HtmlBody := Value;
end;
{ TPrerequisites }
function TPrerequisites.IsClientAvailable: Boolean;
var
SimpleMAPI: TJclSimpleMapi;
begin
SimpleMAPI := TJclSimpleMapi.Create;
try
Result := SimpleMAPI.AnyClientInstalled;
finally
SimpleMAPI.Free;
end;
end;
function TPrerequisites.IsMapiAvailable: Boolean;
var
SimpleMAPI: TJclSimpleMapi;
begin
SimpleMAPI := TJclSimpleMapi.Create;
try
Result := SimpleMAPI.SimpleMapiInstalled;
finally
SimpleMAPI.Free;
end;
end;
end.
Sample usage:
unit f_Main;
interface
uses
Windows, SysUtils, Classes, Controls, Forms, Graphics, StdCtrls, XPMan,
// project units
JclMapi, MAPI.SendMail, Dialogs;
type
TfMain = class(TForm)
XPManifest: TXPManifest;
gbMailProperties: TGroupBox;
eMailSubject: TEdit;
stMailSubject: TStaticText;
stMailBody: TStaticText;
mmMailBody: TMemo;
cbHTMLBody: TCheckBox;
gbAttachments: TGroupBox;
gbRecipients: TGroupBox;
btnSendMail: TButton;
lbRecipients: TListBox;
eRecipAddress: TEdit;
StaticText1: TStaticText;
eRecipName: TEdit;
btnAddRecipient: TButton;
stRecipName: TStaticText;
OpenDialog: TOpenDialog;
lbAttachments: TListBox;
btnAddAttachment: TButton;
stMAPILabel: TStaticText;
stClientLabel: TStaticText;
stMAPIValue: TStaticText;
stClientValue: TStaticText;
procedure btnSendMailClick(Sender: TObject);
procedure btnAddRecipientClick(Sender: TObject);
procedure btnAddAttachmentClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
fMain: TfMain;
implementation
{$R *.dfm}
procedure TfMain.btnSendMailClick(Sender: TObject);
var
I: Integer;
Name: string;
Address: string;
ItemStr: string;
Pos1, Pos2: Integer;
MAPISendMail: TMAPISendMail;
begin
MAPISendMail := TMAPISendMail.Create;
try
for I := 0 to lbRecipients.Items.Count - 1 do
begin
ItemStr := lbRecipients.Items[I];
Pos1 := Pos('[', ItemStr);
Pos2 := Pos(']', ItemStr);
Name := Trim(Copy(ItemStr, Pos1 + 1, Pos2 - Pos1 - 1));
Address := Trim(Copy(ItemStr, 1, Pos1 - 1));
MAPISendMail.AddRecipient(Address, Name);
end;
for I := 0 to lbAttachments.Items.Count - 1 do
MAPISendMail.AddAttachment(lbAttachments.Items[I]);
MAPISendMail.MailSubject := eMailSubject.Text;
MAPISendMail.HTMLBody := cbHTMLBody.Checked;
MAPISendMail.MailBody := mmMailBody.Text;
MAPISendMail.SendMail;
finally
MAPISendMail.Free;
end;
end;
procedure TfMain.btnAddRecipientClick(Sender: TObject);
begin
lbRecipients.Items.Add(Format('%s [%s]', [eRecipAddress.Text,
eRecipName.Text]));
end;
procedure TfMain.btnAddAttachmentClick(Sender: TObject);
begin
if OpenDialog.Execute then
lbAttachments.Items.Add(OpenDialog.FileName);
end;
procedure TfMain.FormCreate(Sender: TObject);
var
ValidHost: Boolean;
MAPISendMail: TMAPISendMail;
begin
MAPISendMail := TMAPISendMail.Create;
try
ValidHost := True;
if MAPISendMail.Prerequisites.IsMapiAvailable then
begin
stMAPIValue.Caption := 'Available';
stMAPIValue.Font.Color := clGreen;
end
else
begin
stMAPIValue.Caption := 'Unavailable';
stMAPIValue.Font.Color := clRed;
ValidHost := False;
end;
if MAPISendMail.Prerequisites.IsClientAvailable then
begin
stClientValue.Caption := 'Available';
stClientValue.Font.Color := clGreen;
end
else
begin
stClientValue.Caption := 'Unavailable';
stClientValue.Font.Color := clRed;
ValidHost := False;
end;
btnSendMail.Enabled := ValidHost;
finally
MAPISendMail.Free;
end;
end;
end.
I use two methods for sending a MAPI mail, depending on whether an attatchment is needed. For the simple case with no attachment I use the following:
function SendShellEmail( ARecipientEmail, ASubject, ABody : string ) : boolean;
// Send an email to this recipient with a subject and a body
var
iResult : integer;
S : string;
begin
If Trim(ARecipientEmail) = '' then
ARecipientEmail := 'mail';
S := 'mailto:' + ARecipientEmail;
S := S + '?subject=' + ASubject;
If Trim(ABody) <> '' then
S := S + '&body=' + ABody;
iResult := ShellExecute( Application.Handle,'open', PChar(S), nil, nil, SW_SHOWNORMAL);
Result := iResult > 0;
end;
This uses a simple shell execute method, so you should not have any real problems other than the more recent alerts to get the User to confirm that they are ok with your program sending an email.
For attatchments I use the following code originally taken from the Delphi Magazine by Brian Long. It is also possible to send an email WITHOUT using the MAPI client but using a nominated SMTP server but I think you explicitly do not want this. I can provide code for this if you do.
uses
SysUtils,
Windows,
Dialogs,
Forms,
MAPI;
procedure ArtMAPISendMail(
const Subject, MessageText, MailFromName, MailFromAddress,
MailToName, MailToAddress: String;
const AttachmentFileNames: array of String);
//Originally by Brian Long: The Delphi Magazine issue 60 - Delphi And Email
var
MAPIError: DWord;
MapiMessage: TMapiMessage;
Originator, Recipient: TMapiRecipDesc;
Files, FilesTmp: PMapiFileDesc;
FilesCount: Integer;
begin
FillChar(MapiMessage, Sizeof(TMapiMessage), 0);
MapiMessage.lpszSubject := PAnsiChar(AnsiString(Subject));
MapiMessage.lpszNoteText := PAnsiChar(AnsiString(MessageText));
FillChar(Originator, Sizeof(TMapiRecipDesc), 0);
Originator.lpszName := PAnsiChar(AnsiString(MailFromName));
Originator.lpszAddress := PAnsiChar(AnsiString(MailFromAddress));
// MapiMessage.lpOriginator := @Originator;
MapiMessage.lpOriginator := nil;
MapiMessage.nRecipCount := 1;
FillChar(Recipient, Sizeof(TMapiRecipDesc), 0);
Recipient.ulRecipClass := MAPI_TO;
Recipient.lpszName := PAnsiChar(AnsiString(MailToName));
Recipient.lpszAddress := PAnsiChar(AnsiString(MailToAddress));
MapiMessage.lpRecips := @Recipient;
MapiMessage.nFileCount := High(AttachmentFileNames) - Low(AttachmentFileNames) + 1;
Files := AllocMem(SizeOf(TMapiFileDesc) * MapiMessage.nFileCount);
MapiMessage.lpFiles := Files;
FilesTmp := Files;
for FilesCount := Low(AttachmentFileNames) to High(AttachmentFileNames) do
begin
FilesTmp.nPosition := $FFFFFFFF;
FilesTmp.lpszPathName := PAnsiChar(AnsiString(AttachmentFileNames[FilesCount]));
Inc(FilesTmp)
end;
try
MAPIError := MapiSendMail(
0,
Application.MainForm.Handle,
MapiMessage,
MAPI_LOGON_UI {or MAPI_NEW_SESSION},
0);
finally
FreeMem(Files)
end;
case MAPIError of
MAPI_E_AMBIGUOUS_RECIPIENT:
Showmessage('A recipient matched more than one of the recipient descriptor structures and MAPI_DIALOG was not set. No message was sent.');
MAPI_E_ATTACHMENT_NOT_FOUND:
Showmessage('The specified attachment was not found; no message was sent.');
MAPI_E_ATTACHMENT_OPEN_FAILURE:
Showmessage('The specified attachment could not be opened; no message was sent.');
MAPI_E_BAD_RECIPTYPE:
Showmessage('The type of a recipient was not MAPI_TO, MAPI_CC, or MAPI_BCC. No message was sent.');
MAPI_E_FAILURE:
Showmessage('One or more unspecified errors occurred; no message was sent.');
MAPI_E_INSUFFICIENT_MEMORY:
Showmessage('There was insufficient memory to proceed. No message was sent.');
MAPI_E_LOGIN_FAILURE:
Showmessage('There was no default logon, and the user failed to log on successfully when the logon dialog box was displayed. No message was sent.');
MAPI_E_TEXT_TOO_LARGE:
Showmessage('The text in the message was too large to sent; the message was not sent.');
MAPI_E_TOO_MANY_FILES:
Showmessage('There were too many file attachments; no message was sent.');
MAPI_E_TOO_MANY_RECIPIENTS:
Showmessage('There were too many recipients; no message was sent.');
MAPI_E_UNKNOWN_RECIPIENT:
Showmessage('A recipient did not appear in the address list; no message was sent.');
MAPI_E_USER_ABORT:
Showmessage('The user canceled the process; no message was sent.');
SUCCESS_SUCCESS:
Showmessage('MAPISendMail successfully sent the message.');
else
Showmessage('MAPISendMail failed with an unknown error code.');
end;
end;
It appears that mailto in a ShellExecute is not capable of sending attachments.
MAPI and Indy have the unfortunate characteristic of not necessarily selecting the user's email client.
So the other possibility is to continue using ShellExecute, but find another way to get the attachments into the email client.
What I decided to do was on my Dialog that creates the email, I now have a FileListBox listing the files the user may want to attach to the email. When the email pops up, they can simply drag and drop them to the email.
In my case, this is actually a good solution, since this allows the users to select the files they want to include. The other method (automatically attaching them) will require they delete the ones they don't want included. (i.e. Having the "Add Google toolbar" option already checked for you is NOT good)
For the time being this solution will work.
Thank you to all those who contributed answers and helped me see my way through this (all +1).
This article shows how Delphi can simulate the "Send to..." shell context menu command and open the default mail client with attachments programatically.
This solution does not need MAPI and works with the default mail client, but is not complete, because the message recipients, body and subject will not be filled in automatically. (The message body could be copied using the clipboard).
Here is a summary about all those email settings and what they do:
http://thesunstroke.blogspot.de/2017/03/how-to-configure-eurekalog-to-send-bugs.html
So, stay away from Shell (mailto).
Mapi is also a bad idea since it only works with MS email clients.
I set by default Simple MAPI but I rarely receive emails sent by this channel. Most emails are received via SMTP Server.
BIG WARNING!!!!!!!!!
I have seen that the number of false positive alarms from antivirus scanners is much higher when you activate EurekaLog. So, only use EurekaLog when absolutely necessary.
Also, Eureka itself is peppered with bugs (just look at the release history and see that for each new feature (or even change) they release, they later fix a few bugs! So, if you are tracking for bugs, notice that EurekaLog itself may introduce few in your EXE!
精彩评论