14

在我的程序中,我正在编写一封电子邮件以使用安装在用户计算机上的默认电子邮件客户端软件发送。

我已经编写了邮件地址、主题、多行正文,并且还有几个附件要包含在内。

我几乎使用 mailto 和 ShellExecute 完成了这项工作,如下所示:

  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);

在 Windows Vista 机器上使用 Delphi 2009,这将打开一个 Microsoft Mail“创建邮件”窗口,其中正确填写了收件人、主题和正文。但是该文件没有附加。

当我对此进行研究时,我注意到一些评论指出这种技术不适用于所有邮件客户端。但是,大多数评论都相当陈旧,因为我意识到这是一种非常古老的技术。

然后我发现Zarko Gajic说“这种方式还可以,但是你不能用这种方式发送附件”。

我还看到了 Windows 简单邮件 API (MAPI),但 Zarko 说只有在最终用户拥有符合 MAPI 的电子邮件软件时才有效。有很多关于在 Delphi 中使用 MAPI 的技术(例如使用 mapi 发送电子邮件),但它们都有免责声明 MAPI 并不总是随 Windows 一起安装。

此外,我真的希望消息首先出现在用户的默认电子邮件程序中,以便他们将其作为电子邮件记录的一部分,他们可以对其进行编辑并决定是否以及何时发送。我不确定 MAPI 是如何工作的,以及它是否会这样做。

所以我的要求是:

  1. 在用户的邮件程序中显示电子邮件。

  2. 允许一个或多个附件。

  3. 从 XP 以上(即 XP、Vista 或 7)的任何 Windows 机器上使用(希望)所有电子邮件客户端。

有这样的动物吗?或者也许有人知道如何使用 mailto/ShellExecute 技术获取附件?

大多数人做什么?


编辑:

MAPI 解决方案甚至是 Indy 解决方案都有一些答案。

我对他们的问题是他们不一定使用默认邮件客户端。例如,在我的 Vista 机器上,我已将 Windows Mail 设置为我的默认客户端。当我进行 MAPI 发送时,它不会启动 Windows Mail,而是会在 Outlook 中启动并设置电子邮件。我不想要那个。

我的程序的两个用户抱怨说:

您的调试例程无法发送文件,因为它尝试启动 Windows 邮件,原因是它自己知道的某种原因,而不是使用默认邮件客户端(在我的情况下为雷鸟)

我试图填写异常报告,但当它要求这台服务器,那台服务器时放弃了!然后我真的很生气,因为它启动了 Outlook - 我从来没有,曾经使用它或想要使用它。

我不需要 MAPI 或 Indy 的代码。它们很容易获得。但是,如果您建议使用 MAPI 或 Indy,我真正需要的是一种找到默认客户端并确保它是通过要发送的电子邮件的客户端。

另外,我需要知道 MAPI 现在是否通用。5 年前,不能保证它可以在所有机器上运行,因为它没有作为操作系统的一部分安装。这仍然是真的,还是默认情况下 MAPI 现在随 Windows XP、Vista 和 7 一起提供?

同样的问题也适用于 Indy 或任何其他建议的解决方案。它可以与默认客户端一起使用吗?它可以在几乎所有 Windows XP 和更高版本的机器上运行吗?

“mailto”解决方案之所以这么好,是因为所有机器都必须支持它才能处理网页上的 HTML mailto 语句。现在要是我能用它来添加附件就好了……


找到了可能的解决方案:mjustin 指出了一种利用操作系统的 sendto 命令的替代方案。这很可能是要走的路。

mailto 并不像 HTML mailto 那样被限制为 256 个字符,但我很震惊地发现它最终被限制为 2048 个字符。好在几个小时后,mjustin给出了答案。

如果实施顺利,他的回答将为我完成。如果没有,我会在这里添加我的评论。


不。事实证明,sendto 解决方案不会总是打开默认的电子邮件程序。在我的机器上,当我的默认邮件程序是 Windows Mail 时,它会打开 Outlook。太糟糕了。尽管有 2048 个字符的限制,但我不得不回到 mailto 方法。

但是,我确实在文章中找到了:SendTo 邮件收件人

此时,您可以使用经过深思熟虑的 ::WinExec 调用替换 ::ShellExecute,使用注册表中声明的实际 mailto 命令行并以当前电子邮件客户端为目标(例如,“%ProgramFiles%\Outlook Express\msimn .exe" /mailurl:%1)。但是限制是 32 KB。总而言之,没有办法使用 mailto 协议发送大于 32KB 的电子邮件。

但是我必须在每种情况下确定邮件客户端是谁。我预计这会导致进一步的并发症。

我发现的另一件事是 mailto 允许设置“to”、“cc”、“bcc”、“subject”和“body”,但不允许设置附件。而 sendto 只允许附件,然后设置带有默认消息的默认电子邮件,您无法设置各种字段和正文。

4

5 回答 5

5

不要复杂,只需使用JCL MAPI 代码即可。它在单元 JclMapi.pas 中。我认为他们也有这个例子。代码非常强大,你可以做任何 MAPI 允许你做的事情。

使用 ShellExecute,您无法发送附件,并且邮件正文也被限制为 255 个字符。

只要 MAPI 运行,它总是安装在旧 Windows 上(2000,XP)。它与 Outlook Express 一起提供,并且几乎总是安装 Outlook Express。对于较新的 Windows(Vista、7),没有 Outlook Express,因此没有 MAPI。但如果您安装 MS Outlook 或 Mozzila Thunderbird,则会自动安装 MAPI。所以你很安全。这是基本的 MAPI,而不是扩展的 MAPI。但它涵盖了你所需要的一切。

如果安装了 MAPI 并采取相应措施,您还可以签入您的代码 (JCL)。不久前我做过类似的事情,它工作正常。我还没有找到不支持简单 MAPI 的流行 Windows 邮件客户端。这是一个简单的 JCL 代码包装器和下面的示例用法:

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.

示例用法:

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.
于 2009-12-26T14:35:25.120 回答
4

我使用两种方法发送 MAPI 邮件,具体取决于是否需要附件。对于没有附件的简单案例,我使用以下内容:

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;

这使用了一个简单的 shell 执行方法,所以除了最近的警报让用户确认他们对您的程序发送电子邮件没问题之外,您应该没有任何实际问题。

对于附件,我使用以下代码,最初取自 Brian Long 的 Delphi Magazine。也可以在不使用 MAPI 客户端但使用指定的 SMTP 服务器的情况下发送电子邮件,但我认为您明确不希望这样做。如果您愿意,我可以为此提供代码。

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;
于 2009-12-26T07:46:40.487 回答
4

ShellExecute 中的 mailto 似乎无法发送附件。

MAPI 和 Indy 的不幸特点是不一定要选择用户的电子邮件客户端。

所以另一种可能性是继续使用 ShellExecute,但要找到另一种方法将附件放入电子邮件客户端。

我决定做的是在创建电子邮件的对话框上,我现在有一个 FileListBox 列出用户可能想要附加到电子邮件的文件。当电子邮件弹出时,他们可以简单地将它们拖放到电子邮件中。

就我而言,这实际上是一个很好的解决方案,因为这允许用户选择他们想要包含的文件。另一种方法(自动附加它们)将要求他们删除他们不想包含的那些。(即已经为您选中了“添加 Google 工具栏”选项并不好)

暂时这个解决方案会起作用。

感谢所有提供答案并帮助我度过难关的人(全部+1)。

于 2009-12-27T01:40:10.583 回答
2

本文展示了 Delphi 如何模拟“发送到...”shell 上下文菜单命令并以编程方式打开带有附件的默认邮件客户端。

该解决方案不需要 MAPI 并与默认邮件客户端一起使用,但并不完整,因为不会自动填写邮件收件人、正文和主题。(可以使用剪贴板复制消息正文)。

于 2009-12-27T11:43:34.050 回答
1

以下是有关所有这些电子邮件设置及其作用的摘要:http:
//thesunstroke.blogspot.de/2017/03/how-to-configure-eurekalog-to-send-bugs.html

在此处输入图像描述

所以,远离壳牌(mailto)。
Mapi 也是一个坏主意,因为它只适用于 MS 电子邮件客户端。
我默认设置了 Simple MAPI,但我很少收到此频道发送的电子邮件。大多数电子邮件都是通过 SMTP 服务器接收的。

大警告!!!!!!!!!
我已经看到,当您激活 EurekaLog 时,来自防病毒扫描程序的误报数量要高得多。因此,仅在绝对必要时才使用 EurekaLog。
此外,Eureka 本身也充满了错误(只需查看发布历史,就会发现对于他们发布的每个新功能(甚至更改),他们稍后会修复一些错误!因此,如果您正在跟踪错误,请注意 EurekaLog 本身可能会在你的EXE中引入一些!

于 2017-05-19T11:32:24.657 回答