Как программа Delphi может отправить электронное письмо с вложениями через клиента по умолчанию по умолчанию?

StackOverflow https://stackoverflow.com/questions/1962765

Вопрос

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

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

Я чуть не работал, используя 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 «Create Mail», с помощью, субъектом и корпусом правильно заполнено. Однако файл не подключается.

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

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

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

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

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

  1. Чтобы поднять электронное письмо в программе почты пользователя.

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

  3. Работать с (надеюсь) со всеми почтовыми клиентами на любой машине Windows от XP UP (IE XP, Vista или 7).

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

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


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

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

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

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

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

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

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

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

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

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


Вероятное решение найдено: Мжустин указал на альтернативу, которая использует команду Sendto операционной системы. Это, скорее всего, путь.

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

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


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

Я, однако, нашел в статье: Отправить по почте получатель что:

На этом этапе вы можете заменить :: Shellexecute хорошо продуманным :: Winexec Call, используя фактическую командную строку Mailto, объявленную в реестре, и нацелиться на текущего клиента электронной почты (например, «%Programfiles% Outlook Express msimn .exe " /mailurl:%1). Но тогда ограничение составляет 32 кб. В заключение, нет способа отправлять электронные письма более 32 КБ, используя протокол Mailto.

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

Еще одна вещь, которую я узнал, это то, что Mailto позволяет устанавливать «на», «cc», «bcc», «субъект» и «тело», но без вложений. Принимая во внимание, что SendTO разрешает только вложения, а затем устанавливает электронное письмо по умолчанию с сообщением по умолчанию и не позволяет вам установить различные поля и тело.

Это было полезно?

Решение 3

Похоже, что Mailto в Shellexecute не способна отправлять вложения.

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

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

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

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

В настоящее время это решение будет работать.

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

Другие советы

Не усложняйте, просто используйте Jcl MAPI CODE. Это в блоке jclmapi.pas. Я думаю, что у них также есть пример для этого. Код очень мощный, и вы можете сделать все, что позволяет Mapi.

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

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

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

Я использую два метода для отправки MAPI MAIL, в зависимости от того, необходима ли Attatchment. Для простого случая без вложения я использую следующее:

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;

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

Для Attatchments я использую следующий код, изначально взятый из журнала Delphi Brian Long. Также можно отправить электронное письмо без использования клиента 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;

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

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

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

enter image description here

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

Большое предупреждение !!!!!!!!!
Я видел, что количество ложных положительных сигналов от антивирусных сканеров намного выше, когда вы активируете Eurekalog. Таким образом, используйте Eurekalog только тогда, когда это абсолютно необходимо.
Кроме того, сама Эврика засыпана ошибками (просто посмотрите на историю выпуска и посмотрите, что для каждой новой функции (или даже изменения) они выпускают, они позже исправляют несколько ошибок! Так что, если вы отслеживаете ошибки, обратите внимание, что сам eurekalog может представить немногие в вашем EXE!

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top