субота, 13 лютого 2021 р.

Delphi: Sending email with attachments using Indy 10.5.5 and GMail

 

Sending email using Delphi is not difficult, you just need to know the carious bits and pieces that go together to make it happen. Over the years Indy has changed its classes and methods so your version of Indy might function slightly different to mine, Indy 10.5.5, its just the one that I have to hand right now. I'm also using Rad Studio 2010. To send email using GMail and Indy you'll need to download the Open SSL libraries. This is because GMail sensibly requires a secure connection while sending, and also receiving email.

You can download the Open SSL libraries here: http://www.openssl.org/related/binaries.html (32-bit builds only at time of writing). You need to place 2 of the files contained in the download (libeay32.dll, and ssleay32.dll) into your system's path or just place them into the same directory as the executable you are building/running. Now for the Delphi part.

I create the Indy components dynamically rather than using ones dropped on the Form during design time because its generally more memory efficient for normal use.

This code is fairly specific to Gmail because passing anything other than utNoTLSSupport as the IdSmtp UseSSL property will then set the IdSSLIOHandlerSocketOpenSSL.SSLOptions.Method to sslvTLSv1. So mightn't work with another SSL only email service. Easy to change though.

These are the namespaces you need to add to your units uses section:
uses IdIOHandler, IdIOHandlerSocket,
  IdIOHandlerStack, IdSSL, IdSSLOpenSSL, IdBaseComponent, IdComponent,
  IdTCPConnection, IdTCPClient, IdExplicitTLSClientServerBase,
  IdSMTPBase, IdSMTP, IdMessage, IdAttachment,IdAttachmentFile;

Add this method declaration to the Private section of the unit
    function SendEmail(sendTo: string;
                    subject: string;
                    body: string;
                    attachFiles: TStringList;
                    smtpHost: string;
                    smtpPort: Integer;
                    smtpUser: string;
                    smtpPass: string;
                    tls: TIdUseTLS): boolean;

The method itself. Ive commented out the lines where the TIdSMTP and TIdSSLIOHandlerSocketOpenSSL events are assigned. If you want to include these events and trace through the network connection, handshake, encoding, sending and disconnection stages of the SMTP session, then uncomment these lines and include the events at the bottom of this article.

function TForm1.SendEmail(sendTo: string;
                    subject: string;
                    body: string;
                    attachFiles: TStringList;
                    smtpHost: string;
                    smtpPort: Integer;
                    smtpUser: string;
                    smtpPass: string;
                    tls: TIdUseTLS): boolean;
var
    smtp: TIdSmtp;
    ssl: TIdSSLIOHandlerSocketOpenSSL;
    msg: TIdMessage;
    i: Integer;
begin
    smtp:=TIdSmtp.Create(nil);
    ssl:=TIdSSLIOHandlerSocketOpenSSL.Create(nil);
    msg:=TIdMessage.Create(nil);
    try

        try
            smtp.Host:=smtpHost;
            smtp.Port:=smtpPort;
            smtp.Username:=smtpUser;
            smtp.Password:=smtpPass;

            //smtp.OnConnected :=IdSMTP1Connected;
            //smtp.OnDisconnected :=IdSMTP1Disconnected;
            //smtp.OnFailedRecipient :=IdSMTP1FailedRecipient;
            //smtp.OnStatus :=IdSMTP1Status;
            //smtp.OnTLSNotAvailable :=IdSMTP1TLSNotAvailable;
            //smtp.OnWork :=IdSMTP1Work;

            if not (tls=utNoTLSSupport) then begin
                ssl.Destination:=smtpHost + ':' + IntToStr(smtpPort);
                ssl.Host:=smtpHost;
                ssl.Port:=smtpPort;
                ssl.SSLOptions.Method:=sslvTLSv1;

                //ssl.OnStatusInfo:=IdSSLIOHandlerSocketOpenSSL1StatusInfo;
                //ssl.OnGetPassword:=IdSSLIOHandlerSocketOpenSSL1GetPassword;
                //ssl.OnStatus:=IdSSLIOHandlerSocketOpenSSL1Status;

                smtp.IOHandler:=ssl;
                smtp.UseTLS:=tls;
            end;

            msg.Recipients.EMailAddresses := sendTo;
            msg.Subject:=subject;
            msg.Body.Text:=body;

            if(Assigned(attachFiles)) then begin
                for i := 0 to attachFiles.Count - 1 do begin
                   if FileExists(attachFiles[i]) then
                        TIdAttachmentFile.Create(msg.MessageParts, attachFiles[i]);
                end;
            end;

            smtp.Connect;
            smtp.Send(msg);
            smtp.Disconnect;

            result:=true;
        finally
            msg.Free;
            ssl.Free;
            smtp.Free;
        end;
    except
       result:=false;
    end;

end;

NOTE: Do not forget about SMTP.HeloName if exists!

Here's the code to call the send mail method (in an auto generated button click event)

procedure TForm1.Button1Click(Sender: TObject);
var
    attachmentFiles: TStringList;
begin
    attachmentFiles:=TStringList.Create;
    try
        attachmentFiles.Add('c:\file1.txt');
        attachmentFiles.Add('c:\file2.jpg');

        try

            SendEmail( 'mybuddy@example.com',
                    'This is the subject',
                    'This is the body of the email....',
                    attachmentFiles,
                    'smtp.gmail.com',
                    587,
                    'myusername@gmail.com',
                    'mypassword', utUseExplicitTLS);
        except
             on E : Exception do
             begin
                ShowMessage('EXCEPTION: message=' + E.Message);
             end;
        end;
    finally
        attachmentFiles.Free;
    end;
end;


If you opted to trace through the network events, then uncomment the commented lines in the method above and add these declarations just before the Private declaration of your unit.

    procedure IdSMTP1Connected(Sender: TObject);
    procedure IdSMTP1Disconnected(Sender: TObject);
    procedure IdSMTP1FailedRecipient(Sender: TObject; const AAddress, ACode,
      AText: string; var VContinue: Boolean);
    procedure IdSMTP1Status(ASender: TObject; const AStatus: TIdStatus;
      const AStatusText: string);
    procedure IdSMTP1Work(ASender: TObject; AWorkMode: TWorkMode;
      AWorkCount: Int64);
    procedure IdSMTP1TLSNotAvailable(Asender: TObject; var VContinue: Boolean);
    procedure IdSSLIOHandlerSocketOpenSSL1GetPassword(var Password: AnsiString);
    procedure IdSSLIOHandlerSocketOpenSSL1Status(ASender: TObject;
      const AStatus: TIdStatus; const AStatusText: string);
    procedure IdSSLIOHandlerSocketOpenSSL1StatusInfo(const AMsg: string);

...and add these procedures to your units implementation (As well as dropping a TMemo onto your Form and naming it 'Memo1')

procedure TForm1.IdSMTP1Connected(Sender: TObject);
begin
    self.Memo1.Lines.Add('IdSMTP1Connected');
end;

procedure TForm1.IdSMTP1Disconnected(Sender: TObject);
begin
    self.Memo1.Lines.Add('IdSMTP1Disconnected');
end;

procedure TForm1.IdSMTP1FailedRecipient(Sender: TObject; const AAddress, ACode,
  AText: string; var VContinue: Boolean);
begin
    self.Memo1.Lines.Add('IdSMTP1FailedRecipient AAddress:=' + AAddress + ' ACode=' + ACode + ' AText='+AText);
    VContinue:=true;
end;

procedure TForm1.IdSMTP1Status(ASender: TObject; const AStatus: TIdStatus;
  const AStatusText: string);
begin
    self.Memo1.Lines.Add('IdSMTP1Status AStatusText:=' + AStatusText);
end;

procedure TForm1.IdSMTP1TLSNotAvailable(Asender: TObject;
  var VContinue: Boolean);
begin
   self.Memo1.Lines.Add('IdSMTP1TLSNotAvailable: not continuing');
   VContinue:=false;
end;

procedure TForm1.IdSMTP1Work(ASender: TObject; AWorkMode: TWorkMode;
  AWorkCount: Int64);
begin
   self.Memo1.Lines.Add('IdSMTP1Work AWorkCount:=' + IntToStr(AWorkCount));
end;

procedure TForm1.IdSSLIOHandlerSocketOpenSSL1GetPassword(
  var Password: AnsiString);
begin
    self.Memo1.Lines.Add('IdSSLIOHandlerSocketOpenSSL1GetPassword Password:=' + Password);
end;

procedure TForm1.IdSSLIOHandlerSocketOpenSSL1Status(ASender: TObject;
  const AStatus: TIdStatus; const AStatusText: string);
begin
    self.Memo1.Lines.Add('IdSSLIOHandlerSocketOpenSSL1Status AStatusText:=' + AStatusText);
end;

procedure TForm1.IdSSLIOHandlerSocketOpenSSL1StatusInfo(const AMsg: string);
begin
   self.Memo1.Lines.Add('IdSSLIOHandlerSocketOpenSSL1StatusInfo AMsg:=' + AMsg);
end;

procedure TMain.SendEmailIndy(
    const SMTPServer: string;
    const FromName, FromAddress: string;
    const ToAddresses: string; //comma separated list of e-mail addresses
    const CCAddresses: string; //comma separated list of e-mail addresses
    const BCCAddresses: string; //comma separated list of e-mail addresses
    const Subject: string;
    const EmailBody: string;
    const IsBodyHtml: Boolean);
var
  smtp: TIdSMTP; // IdSmtp.pas
  msg: TidMessage; // IdMessage.pas
  builder: TIdMessageBuilderHtml; //IdMessageBuilder.pas
begin
  msg := TidMessage.Create(nil);
  try
    builder := TIdMessageBuilderHtml.Create;
    try
      if IsBodyHtml then
      begin
        builder.Html.Text := EmailBody;
        builder.HtmlCharSet := 'utf-8';
        builder.HtmlContentTransfer := 'quoted-printable';
      end else
      begin
        builder.PlainText.Text := EmailBody;
        builder.PlainTextCharSet := 'utf-8';
        builder.PlainTextContentTransfer := 'quoted-printable';
      end;

      if Realization.AttachmentD.FileName <> '' then
      begin
        builder.Attachments.Add(Realization.AttachmentD.FileName);
        ShowMessage('Sending: ' + Realization.AttachmentD.FileName);
      end;

      builder.FillMessage(msg);
    finally
      builder.Free;
    end;

    msg.From.Name := FromName;
    msg.From.Address := FromAddress;
    msg.Subject := Subject;

    msg.Recipients.EmailAddresses := ToAddresses;
    msg.CCList.EmailAddresses := CCAddresses;
    msg.BccList.EmailAddresses := BCCAddresses;

    smtp := TIdSMTP.Create(nil);
    try
      smtp.Host := SMTPServer; // IP Address of SMTP server
      Smtp.UseTLS := utNoTLSSupport;
      smtp.Port := 587; //The default already is port 25 (the SMTP port)
      smtp.Username := _GlobalData.EMail;
      smtp.Password := _GlobalData.Password;
      smtp.AuthType := satDefault;

      smtp.Connect;    
      try
        smtp.Send(msg);
      finally
        smtp.Disconnect;
      end;
    finally
      smtp.Free;
    end;
  finally
    msg.Free;
  end;

  ShowMessage('Wiadomość wysłana.');
  Realization.AttachmentD.FileName := '';
end;

Немає коментарів:

Дописати коментар