In my work I had a problem with sending large number of emails through application. I had an idea of parallel sending, because I thought: it is not good for application to be on hold while one mail is processed, and sending through SMTP client is time consuming process – it will take at least few seconds.
I found a solution which you can use to send large number of emails, and application will not be frozen.
Our class MailSender will look something like this. It will have some private members:

private static int clientcount = MailSettings.MailClients; // vrednost iz appconfig
    private SmtpClient[] smtpClients = new SmtpClient[clientcount];
    private CancellationTokenSource cancelToken;
    public int currentClient = -1;

Also, this method is meant to be started in constructor:

    private void setupSMTPClients()
    {
        for (int i = 0; i <= clientcount - 1; i++)
        { 
                SmtpClient client = new SmtpClient();
                client.Host = MailSettings.SmtpHost;
                client.Port = MailSettings.SmtpPort;
                client.EnableSsl = true;
                client.Credentials = new NetworkCredential(MailSettings.UserName[i], 				MailSettings.Password); // iz appconfig-a
                smtpClients[i] = client;
 
        }
    }

This method will create as much clients as we put in appconfig.
My suggestion is to use several different email accounts. Why? Because if we use for e.g. gmail, in that case, we have limit of 100 emails per day with one account. If we have 5 accounts, than we can send 500 emails per day. Also, there exists size limit per day for each account. In my case I use same password for all emails.

Next method is for parallel sending emails, but be aware that it is not optimized, it is getting random SMTP client.

This is main method for parallel sending emails:

    public void SendEmailsParallel(List data)
    {
        try
        {
            ParallelOptions po = new ParallelOptions();
            //Create a cancellation token so you can cancel the task.
            cancelToken = new CancellationTokenSource();
            po.CancellationToken = cancelToken.Token;
            // Odredimo broj paralelnih taskova za slanje mailova
            po.MaxDegreeOfParallelism = System.Environment.ProcessorCount * 2;
            try
            { 
                Parallel.ForEach(data, po, (CommunicationEmail dataobject) =>
                {
                     SendEmailParallel(dataobject);
                });
            }
            catch (OperationCanceledException e)
            {
                //User has cancelled this request.
            }
        }
        finally
        {
            disposeSMTPClients();
        }
    }

Disposing clients, of course..

    private void disposeSMTPClients()
    {
        for (int i = 0; i <= clientcount; i++)
        {
            try
            {
                smtpClients[i].Dispose();
            }
            catch 
            {
                throw;
            }
        }
    }
}

And finally, method that sends mails.

    private void SendEmailParallel(CommunicationEmail msg)
    {
        try
        {
            bool _gotlocked = false;
            while (!_gotlocked)
            {
                //Keep looping through all smtp client connections until one becomes available
                for (int i = 0; i <= clientcount; i++)
                {
                    if (System.Threading.Monitor.TryEnter(smtpClients[i]))
                    {
                        try
                        {
                            smtpClients[i].Send(msg.MailMessage);
                            Console.WriteLine("Sent with client: " + i);
                        }
                        catch (SmtpException smtpex)
                        {
                            Console.WriteLine("Error sending :" + smtpex.Message);
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine("Error sending :" + ex.Message);
                        }
                        finally
                        {
                            System.Threading.Monitor.Exit(smtpClients[i]);
                        }
                        _gotlocked = true;
                        Break;
                    }
                }
Thread.Sleep(1);//Do this to make sure CPU doesn't ramp up to 100%
            }
        }
        finally
        {
            msg.MailMessage.Dispose();
        }
    }

As you can see, this method will not call next SMTP client, but first one that is available. That would be ok, if we would not have sending limits. Also, in this case, we must prepare all emails, with all attachments, and I am not sure how memory it will take. That’s why I am not using parallel mailer, but sending one-by-one.

In this case, sending emails will be one by one, but SMTP clients will be taken by order. Every time different SMTP client will be used: 1,2,3,4,5,1,2,3....

public void SendEmail(CommunicationEmail msg)
    {
        try
        {
            SmtpClient smtpClient = GetSmtpClient();
            smtpClient.Send(msg.MailMessage);
            Console.WriteLine("Sent with client: " + currentClient);
        }
        catch (SmtpException smtpex)
        {
            Console.WriteLine("Error sending :" + smtpex.Message);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error sending :" + ex.Message);
        }
        finally
        {
            msg.MailMessage.Dispose();
        }
    }



     
    private SmtpClient GetSmtpClient()
    {
       currentClient++;

       if (currentClient >= clientcount) currentClient = 0;

       return smtpClients[currentClient];
    }

If you have smaller numer of emails, with known number and size, maybe parallel sending is correct way to do it. But if not, you may consider sending one-by-one.

Vladimir Starčević
Senior Developer