47

I'm developing an application where a user clicks/presses enter on a certain button in a window, the application does some checks and determines whether to send out a couple of emails or not, then show another window with a message.

My issue is, sending out the 2 emails slows the process noticeably, and for some (~8) seconds the first window looks frozen while it's doing the sending.

Is there any way I can have these emails sent on the background and display the next window right away?

Please don't limit your answer with "use X class" or "just use X method" as I am not all too familiarized with the language yet and some more information would be highly appreciated.

Thanks.

Eton B.
  • 6,121
  • 5
  • 31
  • 43
  • Does your application ever need to do anything with the result (success, failure, other) of sending the email? This can impact whether you ever need to wait for or look for the result of sending the mail (and thus *how* you do so), or if it's more of a "fire-and-forget" kind of thing. – JaredReisinger Aug 04 '10 at 18:16
  • Not really, it's more of a fire-and-forget thing as you call it. It's something pretty simple I think but obviously not so much for me. I'm looking at the different answers at the moment and testing results. – Eton B. Aug 04 '10 at 18:24
  • If you look at my answer I have given you a small (but should be working - not tested though!) example with how to send out the emails not only asynchronously, but how to handle the results *and* display feedback to the UI. – James Aug 04 '10 at 18:27
  • Times change and [Boris Lipschitz's answer](http://stackoverflow.com/a/22471481/62600) is absolutely the most preferred method in today's async/await world. I'd even urge the OP to switch the accepted answer to that one (or at least for others to +1 it out of the cellar). – Todd Menier Jun 06 '14 at 17:21

11 Answers11

81

As of .NET 4.5 SmtpClient implements async awaitable method SendMailAsync. As a result, to send email asynchronously is as following:

public async Task SendEmail(string toEmailAddress, string emailSubject, string emailMessage)
{
    var message = new MailMessage();
    message.To.Add(toEmailAddress);

    message.Subject = emailSubject;
    message.Body = emailMessage;

    using (var smtpClient = new SmtpClient())
    {
        await smtpClient.SendMailAsync(message);
    }
} 
Boris Lipschitz
  • 9,236
  • 5
  • 53
  • 63
26

As it's a small unit of work you should use ThreadPool.QueueUserWorkItem for the threading aspect of it. If you use the SmtpClient class to send your mail you could handle the SendCompleted event to give feedback to the user.

ThreadPool.QueueUserWorkItem(t =>
{
    SmtpClient client = new SmtpClient("MyMailServer");
    MailAddress from = new MailAddress("me@mydomain.com", "My Name", System.Text.Encoding.UTF8);
    MailAddress to = new MailAddress("someone@theirdomain.com");
    MailMessage message = new MailMessage(from, to);
    message.Body = "The message I want to send.";
    message.BodyEncoding =  System.Text.Encoding.UTF8;
    message.Subject = "The subject of the email";
    message.SubjectEncoding = System.Text.Encoding.UTF8;
    // Set the method that is called back when the send operation ends.
    client.SendCompleted += new SendCompletedEventHandler(SendCompletedCallback);
    // The userState can be any object that allows your callback 
    // method to identify this send operation.
    // For this example, I am passing the message itself
    client.SendAsync(message, message);
});

private static void SendCompletedCallback(object sender, AsyncCompletedEventArgs e)
{
        // Get the message we sent
        MailMessage msg = (MailMessage)e.UserState;

        if (e.Cancelled)
        {
            // prompt user with "send cancelled" message 
        }
        if (e.Error != null)
        {
            // prompt user with error message 
        }
        else
        {
            // prompt user with message sent!
            // as we have the message object we can also display who the message
            // was sent to etc 
        }

        // finally dispose of the message
        if (msg != null)
            msg.Dispose();
}

By creating a fresh SMTP client each time this will allow you to send out emails simultaneously.

James
  • 80,725
  • 18
  • 167
  • 237
  • 1
    So I'm trying to work on this answer, as it seems the most viable (simplicity/similar to what I'm using) for me. What exactly is used as "userState", though? Do I HAVE to use a thread? I simply changed my Send method for this: string someString = "Message"; smtp.SendAsync(message,someString); to no avail. I'll implement all your solution and see what I'm doing wrong. – Eton B. Aug 04 '10 at 18:38
  • 2
    @Eton if you look at the example I have indicated what the userState variable does. It is an object that is passed to the Callback method when the event is raised. In the example I am using it as a unique identifier but basically you can pass whatever you want into it. No it is not a necessity, if you don't need to use it in the callback method then simply pass in null. – James Aug 04 '10 at 19:11
  • 1
    @Eton - No you don't *have* to use a thread at all. If you take the email sending code out of the QueueUserWorkItem call and put it directly behind the click event of the button it should work all the same as you are creating a separate SmtpClient each time and the email is being sent using SendAsync (which won't block the UI). – James Aug 04 '10 at 19:13
  • 2
    This will not work since the message is disposed before it is sent. Either use Send or dispose the message in the callback. (note that the msdn example only disposes the message if send is cancelled. Can't find anything in the documentation supporting it) – adrianm Aug 04 '10 at 19:16
  • @James - thanks for your response(s). I want to test the solution in the simplest way (no thread) first then I can start making it more robust. The UI no longer blocks but for some reason the emails aren't being sent. I checked the call to the SendAsync method and the parameters(recipient,body,etc) are all correct at that point. The only thing I'm not doing is creating a SmtpClient every time (its created using a constructor once, receiving parameters from a config file). Could this be affecting? – Eton B. Aug 04 '10 at 19:21
  • @adrianm - right on point, I thought about that too. I commented out the dispose line and it worked. Pretty obvious now that I think about it.. – Eton B. Aug 04 '10 at 19:24
  • 1
    @adrianm: Good spot, the example for sending the email was taken straight from the MSDN example. I forgot to move the dispose of the message to the callback, will update. – James Aug 04 '10 at 19:32
  • @Eton - adrian already stated why it wasn't sending I have updated my answer to suit....however, heed my warning in my previous comment about only using 1 SmtpClient. SendAsync does **not** allow multiple emails to be sent out simultaneously, if there will be a case that 2 emails could be sent out at the same time at any point you would be safer to just recreate the SmtpClient everytime... the performance cost in doing this would be negligiable. – James Aug 04 '10 at 19:34
  • @James - Thanks a lot for all your feedback. I picked it as my answer for obvious reasons. If you could compliment it with a way to dispose the message once the email is sent that'd be awesome. – Eton B. Aug 04 '10 at 19:39
  • @James - Yep, that pretty much wraps this up. Thanks again, used my first vote up :) – Eton B. Aug 04 '10 at 19:46
  • I have 2 questions regarding this example. Why do you need to dispose the message, does it hold external resources? and 2 if you are already using another thread why also use SendAsync? what is the benefit at this point? – Sruly Aug 04 '10 at 19:56
  • 3
    @Sruly - It is best practise to always dispose objects that implement IDisposable (usually you would want to wrap it in a using statement, but in this case we can't). Also if your mail message had attachments then you would not be able to touch them, even on disk, until the message was destroyed as it locks them down. As for using SendAsync inside the thread. The benefit of using this is you get the callback and from there you can determine the result of the send. If we wanted to use the Send method instead we would need to wrap this in a try...catch block incase it failed. – James Aug 04 '10 at 20:07
  • Which are the -full- reasons using ThreadPool.QueueUserWorkItem and SendAsync , and not only SendAsync? Please, modify the answer if is required about it. – Kiquenet Sep 13 '13 at 08:00
  • @James my only question is that will this sample code work even if I **Add** **_attachements_** collection to this **message** object? Your reply will be highly appreciated. – vibs2006 Dec 29 '16 at 02:03
  • If you are putting it on a separate thread you don't need to use the Send Async. Perform a Send Instead. That will ensure the message object is used and then disposed. – Diceyus Oct 26 '20 at 16:54
14

It's not too complicated to simply send the message on a separate thread:

using System.Net.Mail;

Smtp.SendAsync(message);

Or if you want to construct the whole message on the separate thread instead of just sending it asynchronously:

using System.Threading;
using System.Net.Mail;

var sendMailThread = new Thread(() => {
    var message=new MailMessage();
    message.From="from e-mail";
    message.To="to e-mail";
    message.Subject="Message Subject";
    message.Body="Message Body";

    SmtpMail.SmtpServer="SMTP Server Address";
    SmtpMail.Send(message);
});

sendMailThread.Start();
EvilDr
  • 8,943
  • 14
  • 73
  • 133
Justin Niessner
  • 242,243
  • 40
  • 408
  • 536
  • 7
    [ObsoleteAttribute("The recommended alternative is System.Net.Mail.SmtpClient. http://go.microsoft.com/fwlink/?linkid=14202")] – Andrey Aug 04 '10 at 18:09
  • 7
    also thread creation is recommended for long running processes, not for async tasks – Andrey Aug 04 '10 at 18:10
  • Bro nice it worked for me. After using this my windows form application ui did not hang. – ranojan Dec 27 '18 at 11:42
9

SmtpClient.SendAsync Method

Sample

using System;
using System.Net;
using System.Net.Mail;
using System.Net.Mime;
using System.Threading;
using System.ComponentModel;
namespace Examples.SmptExamples.Async
{
    public class SimpleAsynchronousExample
    {
        static bool mailSent = false;
        private static void SendCompletedCallback(object sender, AsyncCompletedEventArgs e)
        {
            // Get the unique identifier for this asynchronous operation.
             String token = (string) e.UserState;

            if (e.Cancelled)
            {
                 Console.WriteLine("[{0}] Send canceled.", token);
            }
            if (e.Error != null)
            {
                 Console.WriteLine("[{0}] {1}", token, e.Error.ToString());
            } else
            {
                Console.WriteLine("Message sent.");
            }
            mailSent = true;
        }
        public static void Main(string[] args)
        {
            // Command line argument must the the SMTP host.
            SmtpClient client = new SmtpClient(args[0]);
            // Specify the e-mail sender. 
            // Create a mailing address that includes a UTF8 character 
            // in the display name.
            MailAddress from = new MailAddress("jane@contoso.com", 
               "Jane " + (char)0xD8+ " Clayton", 
            System.Text.Encoding.UTF8);
            // Set destinations for the e-mail message.
            MailAddress to = new MailAddress("ben@contoso.com");
            // Specify the message content.
            MailMessage message = new MailMessage(from, to);
            message.Body = "This is a test e-mail message sent by an application. ";
            // Include some non-ASCII characters in body and subject. 
            string someArrows = new string(new char[] {'\u2190', '\u2191', '\u2192', '\u2193'});
            message.Body += Environment.NewLine + someArrows;
            message.BodyEncoding =  System.Text.Encoding.UTF8;
            message.Subject = "test message 1" + someArrows;
            message.SubjectEncoding = System.Text.Encoding.UTF8;
            // Set the method that is called back when the send operation ends.
            client.SendCompleted += new 
            SendCompletedEventHandler(SendCompletedCallback);
            // The userState can be any object that allows your callback  
            // method to identify this send operation. 
            // For this example, the userToken is a string constant. 
            string userState = "test message1";
            client.SendAsync(message, userState);
            Console.WriteLine("Sending message... press c to cancel mail. Press any other key to exit.");
            string answer = Console.ReadLine();
            // If the user canceled the send, and mail hasn't been sent yet, 
            // then cancel the pending operation. 
            if (answer.StartsWith("c") && mailSent == false)
            {
                client.SendAsyncCancel();
            }
            // Clean up.
            message.Dispose();
            Console.WriteLine("Goodbye.");
        }
    }
}
Kiquenet
  • 14,494
  • 35
  • 148
  • 243
Andrey
  • 59,039
  • 12
  • 119
  • 163
  • 2
    -1 OP specifically asked if you would not just specify what methods they needed to use, they wanted an example. – James Aug 04 '10 at 18:24
  • 4
    @James go to link, there is example there. " Examples [+] The following code example demonstrates calling this method." – Andrey Aug 04 '10 at 18:27
  • Look closely. The **message.Dispose()** is the real culprit here which was corrected by James in his reply. – vibs2006 Dec 29 '16 at 02:04
6

Here is a fire and forget approach together with async using .Net 4.5.2+:

BackgroundTaskRunner.FireAndForgetTaskAsync(async () =>
{
    SmtpClient smtpClient = new SmtpClient(); // using configuration file settings
    MailMessage message = new MailMessage(); // TODO: Initialize appropriately
    await smtpClient.SendMailAsync(message);
});

where BackgroundTaskRunner is:

public static class BackgroundTaskRunner
{     
    public static void FireAndForgetTask(Action action)
    {
        HostingEnvironment.QueueBackgroundWorkItem(cancellationToken => // .Net 4.5.2+ required
        {
            try
            {
                action();
            }
            catch (Exception e)
            {
                // TODO: handle exception
            }
        });
    }

    /// <summary>
    /// Using async
    /// </summary>
    public static void FireAndForgetTaskAsync(Func<Task> action)
    {
        HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken => // .Net 4.5.2+ required
        {
            try
            {
                await action();
            }
            catch (Exception e)
            {
                // TODO: handle exception
            }
        });
    }
}

Works like a charm on Azure App Services.

Augusto Barreto
  • 3,637
  • 4
  • 29
  • 39
6

Just because this is a little vague...I will be brief...

There are a lot of ways to do asynchronous or parallel work in c#/.net etc.

The fastest way to do what you want is to use a background worker thread which will avoid locking up your UI.

A tip with background worker threads : you cannot directly update the UI from them (thread affinity and Marshalling is just something you learn to deal with...)

Another thing to consider...if you use the standard System.Net.Mail type stuff to send the emails...be careful how you craft your logic. If you isolate it all in some method and call it over and over, it will likely have to tear down and rebuild the connection to the mail server each time and the latency involved in authentication etc will still slow the whole thing down unnecessarily. Send multiple e-mails through a single open connection to the mail server when possible.

fdfrye
  • 1,099
  • 7
  • 14
4

Try this:

var client = new System.Net.Mail.SmtpClient("smtp.server");
var message = new System.Net.Mail.MailMessage() { /* provide its properties */ };
client.SendAsync(message, null);
kbrimington
  • 25,142
  • 5
  • 62
  • 74
  • 3
    I tried this solution, however SendAsync takes at least 2 parameters. I used a correct overload but the emails are not being sent, I've tried debugging and in the SendAsync line the recipient address, subject, body, etc. are correct. I've no idea why it's not sending them when Send does. – Eton B. Aug 04 '10 at 18:56
2

What you want to do is run the e-mail task on a separate thread so the main code can continue processing while the other thread does the e-mail work.

Here is a tutorial on how to do that: Threading Tutorial C#

JohnFx
  • 34,542
  • 18
  • 104
  • 162
2

Use the SmtpClient class and use the method SendAsync in the System.Net.Mail namespace.

CubanX
  • 5,176
  • 2
  • 29
  • 44
1

Using the Task Parallel Library in .NET 4.0, you can do:

Parllel.Invoke(() => { YourSendMailMethod(); });

Also, see cristina manu's blog post about Parallel.Invoke() vs. explicit task management.

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
JaredReisinger
  • 6,955
  • 1
  • 22
  • 21
-1

i think this is the best way :

public async Task Send(string to, string subject, string body)
    {
        MailMessage mail = new MailMessage();
        SmtpClient SmtpServer = new SmtpClient("smtp.mail.yahoo.com");
        mail.From = new MailAddress("yourEmail@email.com", "sender name");
        mail.To.Add(to);
        mail.Subject = subject;
        mail.Body = body;
        mail.IsBodyHtml = true;
        SmtpServer.Port = 587;
        SmtpServer.Credentials = new System.Net.NetworkCredential("yourEmail@email.com", "your key");
        SmtpServer.EnableSsl = true;

       await Task.Run(() =>
       {
           SmtpServer.SendAsync(mail, null);
       });
    }
  • Why `SendAsync` wrapped in `Task.Run` and not [`SendMailAsync`](https://learn.microsoft.com/en-us/dotnet/api/system.net.mail.smtpclient.sendmailasync) directly? – Theodor Zoulias Mar 10 '22 at 17:12
  • 1
    @Theodor Zoulias I have a list of emails that contains 760 email , when i use task.run sending emails is in background and is so faster than when i use SendMailAsync . please try this and share result with me . – Alireza Amini Apr 10 '22 at 22:41
  • Alireza you can also use the `SendMailAsync` to send emails in the background: just don't `await` the `Task`: `_ = SmtpServer.SendMailAsync(mail);`. This way everyone will be able to understand that the asynchronous `Send` method does not represent the whole procedure of sending an email. It just represents the initialization of the send procedure. And that no one knows if the email will be eventually sent successfully or not. – Theodor Zoulias Apr 10 '22 at 22:59