Как программа Delphi может отправлять электронную почту с вложениями через почтовый клиент DEFAULT? - PullRequest
14 голосов
/ 26 декабря 2009

В моей программе я создаю электронное письмо для отправки, используя программное обеспечение почтового клиента по умолчанию, установленное на компьютере пользователя.

Я составил почтовый адрес, тему, многокомпонентное тело, и у меня есть несколько вложений, которые необходимо включить.

Я почти получил эту работу, используя 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);

Используя Delphi 2009 на компьютере с Windows Vista, откроется окно «Создание почты» в Microsoft Mail, в котором поля «Кому», «Тема» и «Тело» заполнены правильно. Однако файл не прикрепляется.

Когда я исследовал это, я заметил несколько комментариев о том, что этот метод работает не со всеми почтовыми клиентами. Однако большая часть комментариев довольно старая, так как я понимаю, что это очень старая техника.

Затем я обнаружил, что Зарко Гаджич сказал , что «этот подход приемлем, но вы не можете отправлять вложения таким образом».

Я видел также и Windows Simple Mail API (MAPI), но Зарко говорит, что это работает, только если у конечного пользователя есть MAPI-совместимое почтовое программное обеспечение. Существуют хорошо документированные методы использования MAPI с Delphi (например, Отправка электронной почты с помощью mapi ), но у всех них есть отказ от ответственности, что MAPI не всегда устанавливается с Windows.

Кроме того, я действительно хочу, чтобы сообщение появилось первым в почтовой программе пользователя по умолчанию, поэтому оно будет иметь его в своих записях электронной почты, и они смогут его редактировать и решать, когда и когда они захотят его отправить. Я не уверен, как работает MAPI и будет ли он это делать.

Итак, мои требования:

  1. Для вывода электронной почты в почтовой программе пользователя.

  2. Чтобы разрешить одно или несколько вложений.

  3. Для работы (надеюсь) со всеми почтовыми клиентами на любом компьютере под управлением Windows начиная с XP (т. Е. XP, Vista или 7).

Есть ли такое животное? Или, может быть, кто-то знает, как получить вложения для работы с техникой mailto / ShellExecute?

Что делает большинство людей?


Редактировать:

Было несколько ответов с решениями MAPI и даже с решением Indy.

У меня проблема с ними в том, что они не обязательно используют почтовый клиент по умолчанию. Например, на моем компьютере с Vista я установил Windows Mail в качестве клиента по умолчанию. Когда я отправляю MAPI, он не вызывает Почту Windows, но вместо этого вызывает и настраивает электронную почту в Outlook. Я не хочу этого

Двое из моих пользователей моей программы пожаловались:

Ваша процедура отладки не может отправить файл, так как она пытается запустить почту Windows по какой-то известной ей причине, а не с помощью почтового клиента по умолчанию (в моем случае thunderbird)

Я пытался заполнить исключение сообщить, но сдался, когда он попросил этот сервер, этот сервер! Я тогда получил действительно раздражен, потому что он запущен прогноз - Я никогда, никогда не использую это или не хочу использовать это.

Мне не нужен код для MAPI или Indy. Они легко доступны. Но если вы предложите MAPI или Indy, то мне действительно нужен способ найти клиента по умолчанию и убедиться, что именно ему передан адрес электронной почты для отправки.

Кроме того, мне нужно знать, универсален ли сейчас MAPI. 5 лет назад не было гарантированно работать на всех машинах, потому что он не был установлен как часть операционной системы. Это все еще правда, или MAPI теперь поставляется с Windows XP, Vista и 7 по умолчанию?

Те же вопросы относятся к Indy или любым другим предлагаемым решениям. Может ли он работать с клиентом по умолчанию и будет ли он работать практически на всех компьютерах с Windows XP и более поздними версиями?

Причина, по которой решение "mailto" так приятно, заключается в том, что все машины должны поддерживать его для обработки оператора mailto HTML, найденного на веб-страницах. Теперь, если бы я мог использовать его для добавления вложений ...


Вероятное решение найдено: mjustin указал на альтернативу, которая использует команду sendto операционной системы. Скорее всего, это путь.

mailto не было ограничено 256 символами, как HTML mailto, но я был опустошен, узнав, что оно ограничено 2048 символами. К счастью, несколько часов спустя Мюстин дал свой ответ.

Если реализация этого пойдет хорошо, его ответ сделает это за меня. Если нет, я добавлю свои комментарии здесь.


Нет. Оказывается, решение sendto не всегда открывает программу электронной почты по умолчанию. На моей машине он открывает Outlook, когда мой почтовый по умолчанию Windows Mail. Очень плохо. Мне пришлось вернуться к методу mailto, несмотря на ограничение в 2048 символов.

Я, однако, нашел в статье: SendTo получателю почты , что:

На этом этапе вы можете заменить :: ShellExecute с хорошо продуманной :: вызов WinExec, используя актуальный командная строка mailto, объявленная в зарегистрироваться и настроить адрес текущей электронной почты клиент (например, «% ProgramFiles% \ Outlook Express \ msimn.exe "/ mailurl:% 1). Но тогда ограничение составляет 32 КБ. Как вывод, нет возможности отправить сообщения электронной почты размером более 32 КБ, использующие протокол mailto.

но тогда я должен был бы определить, кто является почтовым клиентом в каждом случае. Я ожидаю, что это приведет к дальнейшим осложнениям.

Еще одна вещь, которую я обнаружил, состоит в том, что mailto позволяет устанавливать «to», «cc», «bcc», «subject» и «body», но без вложений. Принимая во внимание, что sendto ТОЛЬКО разрешает вложения, а затем устанавливает электронное письмо по умолчанию с сообщением по умолчанию, и вы не можете устанавливать различные поля и текст.

Ответы [ 5 ]

5 голосов
/ 26 декабря 2009

Не усложняйте, просто используйте код JCL MAPI. Он находится в модуле JclMapi.pas. Я думаю, что у них также есть пример для этого. Код очень мощный, и вы можете делать все, что позволяет MAPI.

С ShellExecute вы не можете отправить вложение, и вы также ограничены 255 символами в теле письма.

Пока MAPI работает, на старых окнах он всегда установлен (2000, XP). Он поставляется вместе с Outlook Express, и Outlook Express почти всегда устанавливается. В более новых окнах (Vista, 7) нет Outlook Express и поэтому нет MAPI. Но MAPI устанавливается автоматически, если вы устанавливаете MS Outlook или Mozzila Thunderbird. Так что вы в безопасности. Это базовый MAPI, а не расширенный MAPI. Но он охватывает все, что вам нужно.

Вы также можете проверить в своем коде (JCL), установлен ли MAPI, и действовать соответствующим образом. Я сделал подобную вещь не так давно, и она работает нормально. Я не нашел популярного почтового клиента Windows, который не поддерживает простой MAPI. Это простая оболочка вокруг кода 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.
4 голосов
/ 27 декабря 2009

Похоже, что mailto в ShellExecute не может отправлять вложения.

MAPI и Indy имеют неудачную характеристику необязательного выбора почтового клиента пользователя.

Таким образом, другая возможность состоит в том, чтобы продолжить использовать ShellExecute, но найти другой способ получить вложения в почтовый клиент.

Что я решил сделать, так это в моем диалоге, который создает электронную почту, у меня теперь есть FileListBox, в котором перечислены файлы, которые пользователь может захотеть прикрепить к электронной почте. Когда электронная почта всплывает, они могут просто перетащить их в электронную почту.

В моем случае это действительно хорошее решение, поскольку оно позволяет пользователям выбирать файлы, которые они хотят включить. Другой метод (автоматически присоединяющий их) потребует, чтобы они удалили те, которые они не хотят включать. (т. е. уже отмеченная опция «Добавить панель инструментов Google» НЕ подходит)

Пока это решение будет работать.

Спасибо всем тем, кто предоставил ответы и помог мне увидеть мой путь через это (все +1).

4 голосов
/ 26 декабря 2009

Я использую два метода для отправки почты 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;

При этом используется простой метод выполнения оболочки, поэтому у вас не должно быть никаких реальных проблем, кроме недавних предупреждений, чтобы пользователь подтвердил, что они в порядке с вашей программой, отправляющей электронное письмо.

Для присвоений я использую следующий код, первоначально взятый из журнала 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;
2 голосов
/ 27 декабря 2009

В этой статье показано, как Delphi может имитировать команду контекстного меню оболочки «Отправить в ...» и программно открывать почтовый клиент по умолчанию с вложениями.

Это решение не требует MAPI и работает с почтовым клиентом по умолчанию, но не является полным, поскольку получатели сообщения, текст и тема не будут заполняться автоматически. (Тело сообщения может быть скопировано с использованием буфера обмена).

0 голосов
/ 19 мая 2017

Вот краткая информация обо всех этих настройках электронной почты и о том, что они делают:
http://thesunstroke.blogspot.de/2017/03/how-to-configure-eurekalog-to-send-bugs.html

enter image description here

Итак, держитесь подальше от Shell (mailto).
Mapi также плохая идея, поскольку она работает только с почтовыми клиентами MS.
По умолчанию я установил Simple MAPI, но я редко получаю электронные письма, отправленные этим каналом. Большинство писем поступает через SMTP-сервер.

БОЛЬШОЕ ПРЕДУПРЕЖДЕНИЕ !!!!!!!!!
Я видел, что количество ложноположительных сигналов тревоги от антивирусных сканеров намного выше, когда вы активируете EurekaLog. Поэтому используйте EurekaLog только в случае крайней необходимости.
Кроме того, сама Eureka изобилует ошибками (просто просмотрите историю выпуска и убедитесь, что для каждой новой функции (или даже изменения), которую они выпускают, они позже исправляют несколько ошибок! может ввести несколько в ваш EXE!

...