29

I've implemented a simple EmailService for Asp.Net Identity 2.0 (via the IIdentityMessageService interface.

    public class EmailService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        // convert IdentityMessage to a MailMessage
        var email = 
           new MailMessage(new MailAddress("noreply@mydomain.com", "(do not reply)"), 
           new MailAddress(message.Destination))
        {
            Subject = message.Subject,
            Body = message.Body,
            IsBodyHtml = true
        };

        using (var client = new SmtpClient()) // SmtpClient configuration comes from config file
        {
            return client.SendMailAsync(email);
        }
    }
}

To send an email, I go through UserManager:

await _userManager.SendEmailAsync(user.Id, "Confirm your account","Please confirm your account by clicking this link: <a href=\"" + callbackUrl + "\">link</a>");

The problem is that I get a System.Threading.Tasks.TaskCanceledException when I call SendEmailAsync() and it's not clear why.

If I send email synchronously (client.Send(email)), everything works fine.

So my questions are:

  • How do I prevent the TaskCanceledException from getting thrown? and (assuming I can overcome this error),

  • How should I go about communicating errors during email sending back to the client (i.e., "no such user here" type responses from the SmtpClient?

Mr. T
  • 3,892
  • 3
  • 29
  • 48

2 Answers2

31

Your problem is that SmtpClient is disposed before the email is sent.

Two ways:

  • Await the SendMailAsync result

    using (var client = new SmtpClient())
    {
        await client.SendMailAsync(email);
    }
    
  • Register the SendCompleted event and dispose the SmtpClient only after the message is sent

    var client = new SmtpClient();
    client.SendCompleted += (s, e) => {
        client.Dispose();
    };
    return client.SendMailAsync(message);
    
meziantou
  • 20,589
  • 7
  • 64
  • 83
  • I can't await the SendMailAsync() (at least not with out some more gymnastics) because the IIdentityMessageService.SendAsync() method has a return type of Task. But the second suggestion looks like it's going to work. Thanks. – Mr. T Apr 02 '14 at 14:15
  • 7
    You can await the `SendMailAsync` method. Add `async`/`await` and do not return anything. The compiler will do his job. – meziantou Apr 02 '14 at 14:27
  • 4
    I faced this same issue and wrote up a complete description here: http://blog.falafel.com/avoid-taskcanceledexception-sending-email-async/ – ssmith Sep 25 '14 at 13:34
  • what if I make `client` static? – Wolfrevok Cats Oct 15 '18 at 17:53
  • In this case, you need to make sure all `SendMailAsync` has completed before disposing the client. You can ask a new question with your code, so we can help you. – meziantou Oct 15 '18 at 18:36
  • My realization is very simple. It works without any errors (noticeable, at least): `public class EmailService : IIdentityMessageService { static SmtpClient Sender =new SmtpClient(); public Task SendAsync(IdentityMessage m) { return Task.Factory.StartNew(()=>{ Sender.Send(new MailMessage("robot@service.com", m.Destination,m.Subject,""+m.Body+""){IsBodyHtml=true}); }); } }` But I now I'm in doubt if it is really correct. Especially with that approach to `async` method. – Wolfrevok Cats Oct 19 '18 at 21:16
9

You must put async on method.

    public async Task SendAsync(IdentityMessage message)
    {
        using (SmtpClient client = new SmtpClient())
        {
                using (var mailMessage = new MailMessage("your@email.com", message.Destination, message.Subject, message.Body))
                {
                    await client.SendMailAsync(mailMessage);
                }
            }
        }
    }
Marcelo Gondim
  • 304
  • 3
  • 9