Expiring Today—Celebrate National IT Professionals Day with 3 months of free Premium Membership. Use Code ITDAY17

x
?
Solved

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

Posted on 2010-08-20
21
Medium Priority
?
1,212 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
[X]
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
  • 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 2000 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
VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

 
LVL 38

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 2000 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 2000 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 2000 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

Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

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

This article explains how to create forms/units independent of other forms/units object names in a delphi project. Have you ever created a form for user input in a Delphi project and then had the need to have that same form in a other Delphi proj…
Objective: - This article will help user in how to convert their numeric value become words. How to use 1. You can copy this code in your Unit as function 2. than you can perform your function by type this code The Code   (CODE) The Im…
In this brief tutorial Pawel from AdRem Software explains how you can quickly find out which services are running on your network, or what are the IP addresses of servers responsible for each service. Software used is freeware NetCrunch Tools (https…
In this video you will find out how to export Office 365 mailboxes using the built in eDiscovery tool. Bear in mind that although this method might be useful in some cases, using PST files as Office 365 backup is troublesome in a long run (more on t…
Suggested Courses

730 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