128

I'm a bit perplexed on how to manage SmtpClient now that it is disposable, especially if I make calls using SendAsync. Presumably I should not call Dispose until SendAsync completes. But should I ever call it (e.g., using "using"). The scenario is a WCF service which mails out email periodically when calls are made. Most of the computation is fast, but the sending of email can take a second or so, so Async would be preferable.

Should I create a new SmtpClient each time I send mail? Should I create one for the entire WCF? Help!

Update In case it makes a difference, each email is always customized to the user. The WCF is hosted on Azure and Gmail is used as the mailer.

Suman Banerjee
  • 1,923
  • 4
  • 24
  • 40
tofutim
  • 22,664
  • 20
  • 87
  • 148
  • 1
    See this post about the bigger picture on how to handle IDisposable and async: http://stackoverflow.com/questions/974945/how-to-dispose-objects-having-asynchronous-methods-called – Chris Haas Sep 01 '11 at 21:02

6 Answers6

188

The original question was asked for .NET 4, but if it helps as of .NET 4.5 SmtpClient implements async awaitable method SendMailAsync.

As a result, to send email asynchronously is as the following:

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

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

        using (var smtpClient = new SmtpClient())
        {
            await smtpClient.SendMailAsync(message);
        }
    }
}

It's better to avoid using SendAsync method.

user276648
  • 6,018
  • 6
  • 60
  • 86
Boris Lipschitz
  • 9,236
  • 5
  • 53
  • 63
  • Why is it better to avoid it? I think it depends on the requirements. – Jowen May 12 '14 at 08:45
  • 14
    SendMailAsync() is a wrapper around SendAsync() method anyway. async/await is way neater and more elegant. It would achieve exactly the same requirements. – Boris Lipschitz May 14 '14 at 04:27
  • @BorisLipschitz Is there a way to pass values to the callback method on SendMailAsync like there was on SendAsync? UserState is now of type TaskCompletionSource. – Rod Hartzell Nov 13 '14 at 17:59
  • 2
    @RodHartzell you can always use .ContinueWith() – Boris Lipschitz Nov 13 '14 at 22:57
  • 2
    Is it better to use using - or dispose - or no practical difference? Isn't it possible in that last 'using' block that smtpClient could be disposed before SendMailAsync has executed? – niico Jan 10 '15 at 05:22
  • 7
    `MailMessage` should also be Disposed. – TheCodeKing Jan 10 '15 at 11:08
  • Note that if you just send one email then using SendAsync is fine, if you want to send multiple emails one after the other then you MUST wait for the first send email to complete... A load of pants right?... go multi-threaded for bulk emails. – Paul Zahra Apr 09 '18 at 14:27
  • Can I still subscribe to SendCompleted event, since I want to know if the sending was cancelled, ended with error or successfully completed? – Tiger Galo Jul 14 '20 at 13:57
149

Note: .NET 4.5 SmtpClient implements async awaitable method SendMailAsync. For lower versions, use SendAsync as described below.


You should always dispose of IDisposable instances at the earliest possibility. In the case of async calls, this is on the callback after the message is sent.

var message = new MailMessage("from", "to", "subject", "body"))
var client = new SmtpClient("host");
client.SendCompleted += (s, e) => {
                           client.Dispose();
                           message.Dispose();
                        };
client.SendAsync(message, null);

It's a bit annoying the SendAsync doesn't accept a callback.

user276648
  • 6,018
  • 6
  • 60
  • 86
TheCodeKing
  • 19,064
  • 3
  • 47
  • 70
  • 2
    shouldn't the last line have 'await'? – niico Jan 10 '15 at 07:04
  • 25
    No this code was written before `await` was available. This is a traditional callback using event handlers. `await` should be used if using the newer `SendMailAsync`. – TheCodeKing Jan 10 '15 at 11:05
  • 3
    SmtpException:Failure sending mail.-->System.InvalidOperationException: An asynchronous operation cannot be started at this time. Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle. If this exception occurred while executing a Page, ensure that the Page is marked <%@ Page Async="true" %>. This exception may also indicate an attempt to call an "async void" method, which is generally unsupported within ASP.NET request processing. Instead, the asynchronous method should return a Task, and the caller should await it. – Mrchief Sep 02 '15 at 03:42
  • 2
    Is it safe to provide `null` as the second parameter to `SendAsync(...)`? – jocull Jan 08 '18 at 20:05
16

In general, IDisposable objects should be disposed as soon as possible; implementing IDisposable on an object is intended to communicate the fact that the class in question holds expensive resources that should be deterministically released. However, if creating those resources is expensive and you need to construct a lot of these objects, it may be better (performance wise) to keep one instance in memory and reuse it. There's only one way to know if that makes any difference: profile it!

Re: disposing and Async: you can't use using obviously. Instead you typically dispose the object in the SendCompleted event:

var smtpClient = new SmtpClient();
smtpClient.SendCompleted += (s, e) => smtpClient.Dispose();
smtpClient.SendAsync(...);
John Saunders
  • 160,644
  • 26
  • 247
  • 397
jeroenh
  • 26,362
  • 10
  • 73
  • 104
6

Ok, old question I know. But I stumbled upon this myself when I was in need of implementing something similar. I just wanted to share some code.

I'm iterating over several SmtpClients to send several mail asynchronously. My solution is similar to TheCodeKing, but I'm disposing the callback object instead. I'm also passing MailMessage as userToken to get it in the SendCompleted event so I can call dispose on that as well. Like this:

foreach (Customer customer in Customers)
{
    SmtpClient smtpClient = new SmtpClient(); //SmtpClient configuration out of this scope
    MailMessage message = new MailMessage(); //MailMessage configuration out of this scope

    smtpClient.SendCompleted += (s, e) =>
    {
        SmtpClient callbackClient = s as SmtpClient;
        MailMessage callbackMailMessage = e.UserState as MailMessage;
        callbackClient.Dispose();
        callbackMailMessage.Dispose();
    };

    smtpClient.SendAsync(message, message);
}
jmelhus
  • 1,130
  • 2
  • 12
  • 28
  • 2
    Is it the best practice to create a new SmtpClient for each email to send? – Martín Coll Sep 12 '14 at 13:39
  • 1
    Yes, for asynchronous sending, as long as you dispose of the client in the callback... – jmelhus Sep 15 '14 at 09:41
  • 1
    thanks! and just for the sake of a brief explanation: www.codefrenzy.net/2012/01/30/how-asynchronous-is-smtpclient-sendasync – Martín Coll Sep 15 '14 at 12:29
  • 1
    This is one of the most simple and **accurate** answers that I found on stackoverflow for the **smtpclient.sendAsync** function and its related dispose handling. I wrote an asynchronous bulk mail sending library. As I send 50+ message every few minutes therefore executing dispose method was a very important step for me. This code exactly helped me to achieve that. I'll reply in case I found some bugs in this code during the multi threading environments. – vibs2006 Jan 08 '17 at 15:45
  • 1
    I can say that it is not a good approach when you are sending 100+ emails in a loop unless you have ability to configure the exchange server (if you use). Server might throw exception like `4.3.2 The maximum number of concurrent connections has exceeded a limit, closing trasmission channel` . Instead try to use only one instance of `SmtpClient` – ibubi Mar 29 '17 at 12:05
  • 1
    Le function, SendAsync = poop ... "After calling SendAsync, you must wait for the e-mail transmission to complete before attempting to send another e-mail message using Send or SendAsync." – Paul Zahra Apr 10 '18 at 11:38
  • @vibs2006 "50+ .. every few minutes"? That's not a useful load test.. try 50+ per second as a more realistic starting place :} – user2864740 May 17 '19 at 02:26
  • @user2864740 sorry my typo. I meant 50 emails per second only. – vibs2006 May 17 '19 at 06:04
6

You can see why it is particularly important to dispose of SmtpClient by the following comment:

public class SmtpClient : IDisposable
   // Summary:
    //     Sends a QUIT message to the SMTP server, gracefully ends the TCP connection,
    //     and releases all resources used by the current instance of the System.Net.Mail.SmtpClient
    //     class.
    public void Dispose();

In my scenario sending multiple mails using Gmail without disposing the client, I used to get:

Message: Service not available, closing transmission channel. The server response was: 4.7.0 Temporary System Problem. Try again later (WS). oo3sm17830090pdb.64 - gsmtp

Anton Skovorodko
  • 615
  • 1
  • 10
  • 20
  • 1
    Thanks for sharing your exception here as I was sending SMTP Clients without disposing so far. Though I'm using my own SMTP Server but a good programming practice should be always considered. Looking from your error I have now got cautions and will rectify my code to include dispose functions to ensure platform reliability. – vibs2006 Jan 08 '17 at 15:57
1

I used this way in asp.net 5.0 core.

public async Task EmailSend(MessageModel messageModel)
    {
        using (MailMessage mailMessage = new MailMessage())
        {
            mailMessage.From = new MailAddress(_configuration.GetSection("EmailConfiguration").GetSection("FromEmail").Value.ToString(), _configuration.GetSection("EmailConfiguration").GetSection("FromName").Value.ToString(), Encoding.UTF8);
            mailMessage.Subject = messageModel.Subject;
            mailMessage.SubjectEncoding = Encoding.UTF8;
            mailMessage.Body = messageModel.Content;
            mailMessage.BodyEncoding = Encoding.UTF8;
            mailMessage.IsBodyHtml = true;
            mailMessage.BodyTransferEncoding = TransferEncoding.Base64;
            mailMessage.To.Add(new MailAddress(messageModel.To));
            NetworkCredential networkCredential = new NetworkCredential(_configuration.GetSection("EmailConfiguration").GetSection("Username").Value.ToString(), _configuration.GetSection("EmailConfiguration").GetSection("Password").Value.ToString());
            SmtpClient smtpClient = new SmtpClient();
            smtpClient.Host = _configuration.GetSection("EmailConfiguration").GetSection("SmtpServer").Value.ToString();
            smtpClient.EnableSsl = Convert.ToBoolean(_configuration.GetSection("EmailConfiguration").GetSection("SSL").Value);
            smtpClient.UseDefaultCredentials = Convert.ToBoolean(_configuration.GetSection("EmailConfiguration").GetSection("UseDefaultCredentials").Value);
            smtpClient.Port = Convert.ToInt32(_configuration.GetSection("EmailConfiguration").GetSection("Port").Value);
            smtpClient.Credentials = networkCredential;
            smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
            await smtpClient.SendMailAsync(mailMessage);
        }
    }