Go Premium for a chance to win a PS4. Enter to Win

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1224
  • Last Modified:

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

0
Stef Merlijn
Asked:
Stef Merlijn
  • 10
  • 10
4 Solutions
 
aflarinCommented:
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
 
Stef MerlijnDeveloperAuthor Commented:
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
 
aflarinCommented:
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.

 
Geert GruwezOracle dbaCommented:
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
 
Stef MerlijnDeveloperAuthor Commented:
Oke guys, give me some time to do some testing, but it looks very promising.
Thank you for now.
0
 
Stef MerlijnDeveloperAuthor Commented:
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
 
aflarinCommented:
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
 
aflarinCommented:
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
 
Stef MerlijnDeveloperAuthor Commented:
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
 
Stef MerlijnDeveloperAuthor Commented:
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
 
Stef MerlijnDeveloperAuthor Commented:
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
 
aflarinCommented:
>> 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
 
aflarinCommented:
Well done, you was faster :)
0
 
Stef MerlijnDeveloperAuthor Commented:
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
 
aflarinCommented:
yes, that's ok

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


0
 
Stef MerlijnDeveloperAuthor Commented:
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
 
aflarinCommented:
>> 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
 
Stef MerlijnDeveloperAuthor Commented:
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
 
aflarinCommented:
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
 
Stef MerlijnDeveloperAuthor Commented:
I believe we have a winner...Thank you very much for all your time and effort. I'm very happy with the result.
0
 
aflarinCommented:
You're welcome
0

Featured Post

Important Lessons on Recovering from Petya

In their most recent webinar, Skyport Systems explores ways to isolate and protect critical databases to keep the core of your company safe from harm.

  • 10
  • 10
Tackle projects and never again get stuck behind a technical roadblock.
Join Now