Link to home
Start Free TrialLog in
Avatar of Stef Merlijn
Stef MerlijnFlag for Netherlands

asked on

Send E-mails in background process (and continue working)

Hi,

As I personilize the E-mails per customer, I need to send them one-by-one.
When 1000 E-mail are send this way, the enduser of my app will have to wait until the all have finished. If attachments are added it will even take much longer.

What I'm looking for is a way to send the E-mail in an optional backgroudprocess.
During sending of the E-mails, the enduser can decide to wait for it all to happen, or to send it to the background and continue other work with the app.

Can anybody supply me with some codesample on how to accomplish this?
procedure SendEmails;
var  IdSMTP: TIdSMTP;
     IdMsg: TIdMessage;
     I : Integer;
begin
  IdSMTP:=TIdSMTP.Create(nil);
  IdSMTP.Host:='my.domain.nl'; // replace by your outgoing server
  IdSMTP.Port:=25;
  IdSMTP.Username := 'MyUserName';
  IdSMTP.Password := 'MyPassword';
  try
    if NOT IdSMTP.Connected then
      IdSMTP.Connect;

    For I := 0 to Query1.Recordcount -1 do
    begin
      try
        IdMsg:=TIdMessage.Create(nil);
        IdMsg.From.Address:= edit1.Text;
        IdMsg.Subject:= edit2.text;
        IdMsg.Recipients.EMailAddresses:= Query1.FieldByName('Email').Text;

        // Start some function to personalize body per customer
        IdMsg.Body.Text := PersonalizeEmailForCustomer;
        try
          IdSMTP.Connect;
          IdSMTP.Send(IdMsg);
        except
          Showmessage('Error when sending E-mail');
        end;
      finally
        IdMsg.Free;
      end;
      Query1.Next;
    end;
  finally
    if IdSMTP.Connected then
      IdSMTP.Disconnect;
    IdSMTP.Free;
  end;
end;

Open in new window

Avatar of aflarin
aflarin

use the attached unit, but add to uses section your unit where your Query is declared.

Using:

uses
  EmailThread;

procedure TForm1.Button2Click(Sender: TObject);
begin
  with TEmailThread.Create(True) do
  begin
    FreeOnTerminate:= True;
    Resume;
  end;
end;

unit EmailThread;

interface

uses
  Classes;

type
  TEmailThread = class(TThread)
  protected
    procedure Execute; override;
  end;


implementation

uses
  IdSMTP, IdMessage;

{ TEmailThread }

procedure TEmailThread.Execute;
var  IdSMTP: TIdSMTP;
     IdMsg: TIdMessage;
     I : Integer;
begin
  IdSMTP:=TIdSMTP.Create(nil);
  IdSMTP.Host:='my.domain.nl'; // replace by your outgoing server
  IdSMTP.Port:=25;
  IdSMTP.Username := 'MyUserName';
  IdSMTP.Password := 'MyPassword';
  try
    if NOT IdSMTP.Connected then
      IdSMTP.Connect;

    For I := 0 to Query1.Recordcount -1 do
    begin
      if Terminated then Exit;

      try
        IdMsg:=TIdMessage.Create(nil);
        IdMsg.From.Address:= edit1.Text;
        IdMsg.Subject:= edit2.text;
        IdMsg.Recipients.EMailAddresses:= Query1.FieldByName('Email').Text;

        // Start some function to personalize body per customer
        IdMsg.Body.Text := PersonalizeEmailForCustomer;
        try
          IdSMTP.Connect;
          IdSMTP.Send(IdMsg);
        except
          Showmessage('Error when sending E-mail');
        end;
      finally
        IdMsg.Free;
      end;
      Query1.Next;
    end;
  finally
    if IdSMTP.Connected then
      IdSMTP.Disconnect;
    IdSMTP.Free;
  end;
end;

end.

Open in new window

Avatar of Stef Merlijn

ASKER

Thank you. Could you explain how this works?
Suppose I send 100 Emails. Then I start the thread by:
  with TEmailThread.Create(True) do
  begin
    FreeOnTerminate:= True;
    Resume;
  end;

Can the user from that moment on do anything within the application?
What if an error occurs. Will the user still be able to get this?
Is it still possible to inform the enduser that sending has finished, show some result?

Some issues:
f.e: The Memo containing the content of the Email-body is on the send-form, which will be freed, when user starts working in the app. Therefore I need to pass that content to the thread somehow.
ASKER CERTIFIED SOLUTION
Avatar of aflarin
aflarin

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Geert G
2. don't change query1, edit1, edit2 because the thread uses them

it would be even better if everything is inside the query
and this has it's own connection within the thread

--
sql.Text := 'select :fix1 subject, a.* from table a';
paramByname('SUBJECT').AsString := 'subject of email';
--

you can set fixed values inside a query too to accomplish
or pass parameter to the thread

this would make your thread totally independant of any form

and fwiw
don't use edit1.text or edit2.text inside a thread
Oke guys, give me some time to do some testing, but it looks very promising.
Thank you for now.
Suppose the enduser will enter the mailing form again, but before the thread has finished. Is there a way to determine if it is still running?
use you own boolean variable like this:

  IsThreadRunning: Boolean;

with TEmailThread.Create(True) do
 begin
     OnTerminate:= ThreadOnTerminate;
     FreeOnTerminate:= True;
     IsThreadRunning:= True;
     Resume;
 end;

procedure TMainForm.ThreadOnTerminate(ASender: TObject);
begin
  IsThreadRunning:= False;
  ShowMessage('Job is done');
end;
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Did some testing, but I have no experience with contructors and threads, so I get a bit confused...

From My Mail-form I try to start up the thread from within the mainform (as that form will not be destroyed).
// added
Uses MailingPerEmailThread;
procedure TFHoofdscherm.StartMailingPerEmailThread;
begin
  with TMailingPerEmailThread.Create(True) do
  begin
    OnTerminate:= MailingPerEmailThreadOnTerminate;
    FreeOnTerminate:= True;
    Resume;
  end;
end;

procedure TFHoofdscherm.MailingPerEmailThreadOnTerminate(ASender: TObject);
begin
  MyMessageDlg(Format(blcMessageInformatie
                    , [ConstantP])
              , blcMailingEmailIsVerzonden
              , dkInformatie, NMV);
end;

Error: Undeclared identifier: 'TMailingPerEmailThread'
Also the code below isn't quite correct.  

unit MailingPerEmailThread;

interface

uses Classes, IdSMTP, IdMessage;

type
  TMailingPerEmailThread = class(TThread)
  private
    FOnderwerp : String
    FVan : String
    FBody : String
  protected
    procedure Execute; override;
  public
    constructor Create(Const FOnderwerp : String ); overload;
    constructor Create(Const FVan : String ); overload;
    constructor Create(Const FBody : String ); overload;
  end;

implementation

constructor TMailingPerEmailThread.Create(Const AVan: String );
begin
  inherited Create(True);
  FVan := AVan;
end;

constructor TMailingPerEmailThread.Create(Const ABody: String );
begin
  inherited Create(True);
  FBody := ABody;
end;

constructor TMailingPerEmailThread.Create(Const AOnderwerp: String );
begin
  inherited Create(True);
  FOnderwerp := AOnderwerp;
end;

procedure TMailingPerEmailThread.Execute;
begin  
  // Do my stuff
end;

end.

Open in new window

The first part had to do with added the unit to the uses clause of the mainform.
In unit MailingPerEmailThread I have still some problems with constructors.

Error: Method 'Create' with identical parameters already exists

How do I get all variables assigned and available in this unit?
Got that solved too. They can be combined into one constructor....
unit MailingPerEmailThread;

interface

uses Classes, IdSMTP, IdMessage;

type
  TMailingPerEmailThread = class(TThread)
  private
    FIsMailingPerEmailBezig : Boolean;
    FOnderwerp : String;
    FVan : String;
    FBody : String;
  protected
    procedure Execute; override;
  public
    property IsThreadRunning: Boolean read FIsMailingPerEmailBezig;
    constructor Create(Const AOnderwerp, AVan, ABody : String ); overload;
  end;

implementation

constructor TMailingPerEmailThread.Create(Const AOnderwerp, AVan, ABody: String );
begin
  inherited Create(True);
  FOnderwerp := AOnderwerp;
  FVan := AVan;
  FBody := ABody;
end;

procedure TMailingPerEmailThread.Execute;
begin
  FIsMailingPerEmailBezig := True;

  // Code

  FIsMailingPerEmailBezig := False;
end;

end.

Open in new window

>> Error: Undeclared identifier: 'TMailingPerEmailThread'

It seems, your code is correct. Did you add MailingPerEmailThread to the project? In what line does the error occur?

Your improved unit:

procedure TFHoofdscherm.StartMailingPerEmailThread;
begin
  with TMailingPerEmailThread.Create('Onderwerp', 'Van', 'Body') do
  begin
    OnTerminate:= MailingPerEmailThreadOnTerminate;
    FreeOnTerminate:= True;
    Resume;
  end;
end;

unit MailingPerEmailThread;

interface

uses Classes, IdSMTP, IdMessage;

type
  TMailingPerEmailThread = class(TThread)
  private
    FOnderwerp : String;
    FVan : String;
    FBody : String;
  protected
    procedure Execute; override;
  public
    constructor Create(Const AOnderwerp, AVan, ABody: String ); overload;
  end;

implementation

constructor TMailingPerEmailThread.Create(Const AOnderwerp, AVan, ABody: String );
begin
  inherited Create(True);
  FVan := AVan;
  FBody := ABody;
  FOnderwerp := AOnderwerp;
end;

procedure TMailingPerEmailThread.Execute;
begin  
  // Do my stuff
end;

end.

Open in new window

Well done, you was faster :)
But you were very quick too my friend...

Is it correct to asume that if place this code in the mainform, and pass the content from the editfields as parameter. Like:

procedure TFHoofdscherm.StartMailingPerEmailThread(vVan, vOnderwerp, vBody);
begin
  with TMailingPerEmailThread.Create(vVan, vOnderwerp, vBody) do
  begin
    OnTerminate:= MailingPerEmailThreadOnTerminate;
    FreeOnTerminate:= True;
    Resume;
  end;
end;

From the Send-button the the mailingUnit (where the mail is composed).

procedure TFMailingUnit.btnSendMyEmailClick(Sender: TObject);
begin
  TFHoofdscherm.StartMailingPerEmailThread(EditVan.Text, EditOnderwerp.Text, MemoBody.Lines.Text);
end;

Open in new window

yes, that's ok

but
procedure TFHoofdscherm.StartMailingPerEmailThread(vVan, vOnderwerp, vBody: string);


Oke, whole thing is compiling now, but I haven't figured out how and where I pass the variables to the thread.
Somewhere I need to assign content to vVan, vOnderwerp, vBody
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Oke it works now.
Remains one little thing that I asked at the beginning:
"During sending of the E-mails, the enduser can decide to wait for it all to happen, or to send it to the background and continue other work with the app."
So the user has pressed the sendbutton and decides not wanting to wait anymore. Duration of sending an E-mail might differ a lot, when f.e. larger attachments are added.

SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I believe we have a winner...Thank you very much for all your time and effort. I'm very happy with the result.
You're welcome