Saturday, May 11, 2013

Sending email with attachments using Delphi, 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;

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;










1 comment:

  1. dear friend,
    do you have zipped working example?
    Can you send me on email.

    ReplyDelete