Asynchronously sending Emails in C#?
Asked Answered
R

11

48

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.

Rachitis answered 4/8, 2010 at 18:5 Comment(4)
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.Nephelinite
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.Rachitis
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.Responsible
Times change and Boris Lipschitz's answer 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).Nerti
M
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);
    }
} 
Menedez answered 18/3, 2014 at 5:59 Comment(6)
This is the best answer by today's standards as it is the only one supporting async/await.Nerti
This code will not work as no host and From address is defined.Emilia
The thing is that this code will still wait for the mail to be sent. It will be nice if you edited it to add a call to this method launching it in a separate thread.Atonic
@Boris how to call this method?Ayeshaayin
@abhishek, SendEmail("[email protected]", "emailSubject", "emailMessage").GetAwaiter().GetResult();Cadmann
GetAwaiter is not supposed to used in your code. Its a compiler support method.Gagliano
R
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("[email protected]", "My Name", System.Text.Encoding.UTF8);
    MailAddress to = new MailAddress("[email protected]");
    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.

Responsible answered 4/8, 2010 at 18:22 Comment(15)
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.Rachitis
@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.Responsible
@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).Responsible
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)Cassey
@Responsible - 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?Rachitis
@Cassey - 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..Rachitis
@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.Responsible
@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.Responsible
@Responsible - 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.Rachitis
@Responsible - Yep, that pretty much wraps this up. Thanks again, used my first vote up :)Rachitis
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?Participate
@Participate - 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.Responsible
Which are the -full- reasons using ThreadPool.QueueUserWorkItem and SendAsync , and not only SendAsync? Please, modify the answer if is required about it.Selfconfessed
@Responsible 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.Chev
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.Gagliano
I
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();
Inadvertent answered 4/8, 2010 at 18:8 Comment(3)
[ObsoleteAttribute("The recommended alternative is System.Net.Mail.SmtpClient. go.microsoft.com/fwlink/?linkid=14202")]Hagans
also thread creation is recommended for long running processes, not for async tasksHagans
Bro nice it worked for me. After using this my windows form application ui did not hang.Spirketing
H
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("[email protected]", 
               "Jane " + (char)0xD8+ " Clayton", 
            System.Text.Encoding.UTF8);
            // Set destinations for the e-mail message.
            MailAddress to = new MailAddress("[email protected]");
            // 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.");
        }
    }
}
Hagans answered 4/8, 2010 at 18:10 Comment(3)
-1 OP specifically asked if you would not just specify what methods they needed to use, they wanted an example.Responsible
@Responsible go to link, there is example there. " Examples [+] The following code example demonstrates calling this method."Hagans
Look closely. The message.Dispose() is the real culprit here which was corrected by James in his reply.Chev
K
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.

Komatik answered 4/8, 2010 at 18:11 Comment(0)
B
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.

Bleb answered 2/8, 2015 at 21:40 Comment(0)
P
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);
Photophobia answered 4/8, 2010 at 18:11 Comment(1)
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.Rachitis
V
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#

Volcano answered 4/8, 2010 at 18:10 Comment(0)
B
2

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

Bowie answered 4/8, 2010 at 18:10 Comment(0)
N
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.

Nephelinite answered 4/8, 2010 at 18:14 Comment(2)
That's not going to work. The Invoke function doesn't return until all actions inside are complete.Introversion
Crud... you're right, of course. I was thinking of Task.Factory.StartNew().Nephelinite
G
-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("[email protected]", "sender name");
        mail.To.Add(to);
        mail.Subject = subject;
        mail.Body = body;
        mail.IsBodyHtml = true;
        SmtpServer.Port = 587;
        SmtpServer.Credentials = new System.Net.NetworkCredential("[email protected]", "your key");
        SmtpServer.EnableSsl = true;

       await Task.Run(() =>
       {
           SmtpServer.SendAsync(mail, null);
       });
    }
Glaucous answered 10/3, 2022 at 15:13 Comment(3)
Why SendAsync wrapped in Task.Run and not SendMailAsync directly?Decompose
@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 .Glaucous
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.Decompose

© 2022 - 2024 — McMap. All rights reserved.