Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people, just like you, are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
Solved

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

Posted on 2010-08-20
21
1,146 Views
Last Modified: 2012-08-14
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

0
Comment
Question by:Delphiwizard
  • 10
  • 10
21 Comments
 
LVL 13

Expert Comment

by:aflarin
ID: 33490579
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

0
 

Author Comment

by:Delphiwizard
ID: 33490620
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.
0
 
LVL 13

Accepted Solution

by:
aflarin earned 500 total points
ID: 33490653
it creates a new thread for sending emails.

>> Can the user from that moment on do anything within the application?

sure. but there are two things that you have to control:

1. user want to quit from your application but the emails are still sending. In this case you can terminate the thread or wait for its finishing

2. don't change query1, edit1, edit2 because the thread uses them

>> What if an error occurs. Will the user still be able to get this?

Yes, your try/except/showmessage still work:

        try
          IdSMTP.Connect;
          IdSMTP.Send(IdMsg);
        except
          Showmessage('Error when sending E-mail');
        end;

It would be better to wrap all execute method into try/except too like this:

procedure TEmailThread.Execute;
var  IdSMTP: TIdSMTP;
     IdMsg: TIdMessage;
     I : Integer;
begin
  try
    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
             on E: Exception do
               Showmessage('Error when sending E-mail: ' + E.Message);
          end;
        finally
          IdMsg.Free;
        end;
        Query1.Next;
      end;
    finally
      if IdSMTP.Connected then
        IdSMTP.Disconnect;
      IdSMTP.Free;
    end;
  except
    on E: Exception do
      Showmessage('Error when sending E-mail: ' + E.Message);
  end;
end;

>> Is it still possible to inform the enduser that sending has finished, show some result?

You can use OnTerminate message or

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

procedure TMainForm.ThreadOnTerminate(ASender: TObject);
begin
  ShowMessage('Job is done');
end;

>> Therefore I need to pass that content to the thread somehow

you can pass to the thread any parameters you like via its constructor. For example:

  TEmailThread = class(TThread)
  private
    FSubject: string;
  protected
    procedure Execute; override;
  public
    constructor Create( const ASubject: string );
  end;

constructor TEmailThread.Create( const ASubject: string );
begin
  inherited Create(True);
  FSubject:= ASubject;
end;

procedure TEmailThread.Execute;
...
        // using local var
        IdMsg.Subject:= FSubject; // was IdMsg.Subject:= edit2.text;
0
Free Tool: Site Down Detector

Helpful to verify reports of your own downtime, or to double check a downed website you are trying to access.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

 
LVL 37

Expert Comment

by:Geert Gruwez
ID: 33490688
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
0
 

Author Comment

by:Delphiwizard
ID: 33490734
Oke guys, give me some time to do some testing, but it looks very promising.
Thank you for now.
0
 

Author Comment

by:Delphiwizard
ID: 33491346
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?
0
 
LVL 13

Expert Comment

by:aflarin
ID: 33491360
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;
0
 
LVL 13

Assisted Solution

by:aflarin
aflarin earned 500 total points
ID: 33491404
or you can move this var into Thread class:

  TEmailThread = class(TThread)
  private
    FIsThreadRunning: Boolean;
    ....
  public
    property IsThreadRunning: Boolean read FIsThreadRunning;
  end;


procedure TEmailThread.Execute;
...
begin
  FIsThreadRunning:= True;
  try

  except
    on E: Exception do
      Showmessage('Error when sending E-mail: ' + E.Message);
  end;
  FIsRunning:= False;
end;
0
 

Author Comment

by:Delphiwizard
ID: 33491434
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

0
 

Author Comment

by:Delphiwizard
ID: 33491471
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?
0
 

Author Comment

by:Delphiwizard
ID: 33491479
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

0
 
LVL 13

Expert Comment

by:aflarin
ID: 33491480
>> 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

0
 
LVL 13

Expert Comment

by:aflarin
ID: 33491483
Well done, you was faster :)
0
 

Author Comment

by:Delphiwizard
ID: 33491495
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

0
 
LVL 13

Expert Comment

by:aflarin
ID: 33491516
yes, that's ok

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


0
 

Author Comment

by:Delphiwizard
ID: 33491544
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
0
 
LVL 13

Assisted Solution

by:aflarin
aflarin earned 500 total points
ID: 33491564
>> Somewhere I need to assign content to vVan, vOnderwerp, vBody

but you already assign them, don't you?
1.

procedure TFMailingUnit.btnSendMyEmailClick(Sender: TObject);
begin
    TFHoofdscherm.StartMailingPerEmailThread(EditVan.Text, EditOnderwerp.Text, MemoBody.Lines.Text);
end;
2.
procedure TFHoofdscherm.StartMailingPerEmailThread(vVan, vOnderwerp, vBody);
begin
    with TMailingPerEmailThread.Create(vVan, vOnderwerp, vBody) do
...

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

end;



Now you can use them into Execute proc:
procedure TMailingPerEmailThread.Execute;
...
    IdMsg:=TIdMessage.Create(nil);
            IdMsg.From.Address:= FVan;
            IdMsg.Subject:= FOnderwerp;
    IdMsg.Body.Text := ABody;

0
 

Author Comment

by:Delphiwizard
ID: 33491589
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.

0
 
LVL 13

Assisted Solution

by:aflarin
aflarin earned 500 total points
ID: 33491625
The user can close your mailing form (TFMailingUnit), but sending will continue.

Do you want to check if the sending is still running? Use IsThreadRunning thread var, like this:

TFHoofdscherm = class(TForm)

private
  FMailingThread: TMailingPerEmailThread;

end;

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

function TFHoofdscherm.IsThreadRunning: Boolean;
begin
  Result:= Assigned( FMailingThread ) and FMailingThread.IsThreadRunning;
end;
0
 

Author Comment

by:Delphiwizard
ID: 33491654
I believe we have a winner...Thank you very much for all your time and effort. I'm very happy with the result.
0
 
LVL 13

Expert Comment

by:aflarin
ID: 33491669
You're welcome
0

Featured Post

Announcing the Most Valuable Experts of 2016

MVEs are more concerned with the satisfaction of those they help than with the considerable points they can earn. They are the types of people you feel privileged to call colleagues. Join us in honoring this amazing group of Experts.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Creating an auto free TStringList The TStringList is a basic and frequently used object in Delphi. On many occasions, you may want to create a temporary list, process some items in the list and be done with the list. In such cases, you have to…
Hello everybody This Article will show you how to validate number with TEdit control, What's the TEdit control? TEdit is a standard Windows edit control on a form, it allows to user to write, read and copy/paste single line of text. Usua…
Finds all prime numbers in a range requested and places them in a public primes() array. I've demostrated a template size of 30 (2 * 3 * 5) but larger templates can be built such 210  (2 * 3 * 5 * 7) or 2310  (2 * 3 * 5 * 7 * 11). The larger templa…
I've attached the XLSM Excel spreadsheet I used in the video and also text files containing the macros used below. https://filedb.experts-exchange.com/incoming/2017/03_w12/1151775/Permutations.txt https://filedb.experts-exchange.com/incoming/201…

856 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question