Solved

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

Posted on 2010-08-20
21
1,120 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
Comment Utility
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
Comment Utility
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
Comment Utility
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
 
LVL 36

Expert Comment

by:Geert Gruwez
Comment Utility
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
Comment Utility
Oke guys, give me some time to do some testing, but it looks very promising.
Thank you for now.
0
 

Author Comment

by:Delphiwizard
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

 

Author Comment

by:Delphiwizard
Comment Utility
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
Comment Utility
>> 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
Comment Utility
Well done, you was faster :)
0
 

Author Comment

by:Delphiwizard
Comment Utility
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
Comment Utility
yes, that's ok

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


0
 

Author Comment

by:Delphiwizard
Comment Utility
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
Comment Utility
>> 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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
You're welcome
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

The uses clause is one of those things that just tends to grow and grow. Most of the time this is in the main form, as it's from this form that all others are called. If you have a big application (including many forms), the uses clause in the in…
Have you ever had your Delphi form/application just hanging while waiting for data to load? This is the article to read if you want to learn some things about adding threads for data loading in the background. First, I'll setup a general applica…
In this seventh video of the Xpdf series, we discuss and demonstrate the PDFfonts utility, which lists all the fonts used in a PDF file. It does this via a command line interface, making it suitable for use in programs, scripts, batch files — any pl…
Illustrator's Shape Builder tool will let you combine shapes visually and interactively. This video shows the Mac version, but the tool works the same way in Windows. To follow along with this video, you can draw your own shapes or download the file…

772 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

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now