This question already has answers here:
Asynchronously sending Emails in C#?
(11 answers)
Closed 9 years ago.
namespace Binarios.admin
{
public class SendEmailGeral
{
public SmtpClient client = new SmtpClient("smtp.gmail.com", 587);
public MailMessage msg = new MailMessage();
public void Enviar(string sendFrom, string sendTo, string subject, string body)
{
string pass = "12345";
System.Net.NetworkCredential smtpCreds = new System.Net.NetworkCredential(sendFrom, pass);
//setup SMTP Host Here
client.UseDefaultCredentials = false;
client.Credentials = smtpCreds;
client.EnableSsl = true;
MailAddress to = new MailAddress(sendTo);
MailAddress from = new MailAddress(sendFrom);
msg.IsBodyHtml = true;
msg.Subject = subject;
msg.Body = body;
msg.From = from;
msg.To.Add(to);
client.Send(msg);
}
}
}
I've this code, but i'd like to improve it in way that i could send mails asynchronous.
Could you suggest any idea to improve this piece of code or other way to do it.
I've tried asynchronous properties that visual studio suggested but couldn't use them.
SmtpClient allows you to send asynchronously, and uses events to notify you when the send completes. This can be unweildy to use, so you can create an extension method to return a Task instead:
public static Task SendAsync(this SmtpClient client, MailMessage message)
{
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
Guid sendGuid = Guid.NewGuid();
SendCompletedEventHandler handler = null;
handler = (o, ea) =>
{
if (ea.UserState is Guid && ((Guid)ea.UserState) == sendGuid)
{
client.SendCompleted -= handler;
if (ea.Cancelled)
{
tcs.SetCanceled();
}
else if (ea.Error != null)
{
tcs.SetException(ea.Error);
}
else
{
tcs.SetResult(null);
}
}
};
client.SendCompleted += handler;
client.SendAsync(message, sendGuid);
return tcs.Task;
}
To get the result of the send task you can use ContinueWith:
Task sendTask = client.SendAsync(message);
sendTask.ContinueWith(task => {
if(task.IsFaulted) {
Exception ex = task.InnerExceptions.First();
//handle error
}
else if(task.IsCanceled) {
//handle cancellation
}
else {
//task completed successfully
}
});
Wild guess, but SendAsync might do the job!
Change your code from:
client.Send(msg);
To:
client.SendAsync(msg);
more details
link1
link2
Related
Try to send a mail, but the task is cancelled. Any idea why?
public static Task SendAsync(this SmtpClient client, MailMessage message)
{
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
Guid sendGuid = Guid.NewGuid();
SendCompletedEventHandler handler = null;
handler = (o, ea) =>
{
if (ea.UserState is Guid && ((Guid)ea.UserState) == sendGuid)
{
client.SendCompleted -= handler;
if (ea.Cancelled)
{
tcs.SetCanceled(); // TASK CANCELLED: Why?
}
else if (ea.Error != null)
{
tcs.SetException(ea.Error);
}
else
{
tcs.SetResult(null);
}
}
};
client.SendCompleted += handler;
client.SendAsync(message, sendGuid);
return tcs.Task;
}
Called by:
using( SmtpClient smtpClient = new SmtpClient() )
{
return smtpClient.SendAsync(msg);
}
Thanks in advance for any help!
Gerard
A using statement will call an objects Dispose method when finishing the execution block. Calling smtpClient.SendAsync without using await on the async method will cause the execution block to end and Dispose will be called on SmtpClient, even though the SendAsync method is still executing, which explains why some mails finish as some may complete before disposing the objects and others dont.
Do this:
using (SmtpClient smtpClient = new SmtpClient())
{
await smtpClient.SendAsync(msg);
}
I have a strange problem with C# and sendmail async.
When I use the normal send method all works fine and my E-Mail is sending to the right SMTP-Server. But when I use the SendAsync-Method it doesn't work.
mailClient.SendCompleted += SendCompleted;
private static void SendCompleted (object sender, AsyncCompletedEventArgs e)
{
string token = (string) e.UserState;
if (e.Cancelled)
{
MessageBox.Show (string.Format ("{0}\n\"{1}\"", Application.Current.FindResource ("MailCannotSend"), token), LogFile.MSGBOX_ERROR, MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
MessageBox.Show (e.Error != null ? string.Format ("{0}\n\"{1}\"", e.Error, token) : "Complete!", LogFile.MSGBOX_ERROR, MessageBoxButton.OK, MessageBoxImage.Error);
}
The SendAsync-Method runs always in the e.Cancelled stuff without any exceptions or errors. I really don't know why.
Has anyone a tip, clue? Thanks!
Update
Here is the full code:
public void InitClient ()
{
try
{
mailClient = new SmtpClient (SettingsHelper.TailSettings.AlertSettings.SmtpSettings.SmtpServerName, SettingsHelper.TailSettings.AlertSettings.SmtpSettings.SmtpPort)
{
UseDefaultCredentials = false,
Timeout = 30000,
DeliveryMethod = SmtpDeliveryMethod.Network
};
string decryptPassword = StringEncryption.Decrypt (SettingsHelper.TailSettings.AlertSettings.SmtpSettings.Password, LogFile.ENCRYPT_PASSPHRASE);
NetworkCredential authInfo = new NetworkCredential (SettingsHelper.TailSettings.AlertSettings.SmtpSettings.LoginName, decryptPassword);
mailClient.Credentials = authInfo;
mailClient.SendCompleted += SendCompleted;
if (SettingsHelper.TailSettings.AlertSettings.SmtpSettings.SSL)
mailClient.EnableSsl = true;
if (SettingsHelper.TailSettings.AlertSettings.SmtpSettings.TLS)
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
MailAddress from = new MailAddress (SettingsHelper.TailSettings.AlertSettings.SmtpSettings.FromAddress);
MailAddress to = new MailAddress (SettingsHelper.TailSettings.AlertSettings.EMailAddress);
mailMessage = new MailMessage (from, to)
{
Subject = SettingsHelper.TailSettings.AlertSettings.SmtpSettings.Subject,
SubjectEncoding = System.Text.Encoding.UTF8,
BodyEncoding = System.Text.Encoding.UTF8,
IsBodyHtml = false
};
}
catch (Exception ex)
{
ErrorLog.WriteLog (ErrorFlags.Error, GetType ( ).Name, string.Format ("{1}, exception: {0}", ex, System.Reflection.MethodBase.GetCurrentMethod ( ).Name));
}
}
/// <summary>
/// Send E-Mail
/// </summary>
/// <param name="userToken">User token</param>
/// <param name="bodyMessage">Message to be send</param>
public void SendMail (string userToken, string bodyMessage = null)
{
try
{
string userState = userToken;
if (bodyMessage != null)
mailMessage.Body = bodyMessage;
if (String.Compare (userState, "testMessage", StringComparison.Ordinal) == 0)
mailMessage.Body = string.Format ("Testmail from {0}", LogFile.APPLICATION_CAPTION);
mailClient.SendAsync (mailMessage, userToken);
if (String.Compare (userState, "testMessage", StringComparison.Ordinal) == 0)
return;
}
catch (Exception ex)
{
ErrorLog.WriteLog (ErrorFlags.Error, GetType ( ).Name, string.Format ("{1}, exception: {0}", ex, System.Reflection.MethodBase.GetCurrentMethod ( ).Name));
}
}
SendAsync is asynchronous, it completes in the future. Apparently, the scope of the method where your call SendMail ends before SendCompleted is fired. For example, a console application might simply end before you have a chance to handle this event and process the completion of the asynchronous operation.
Any further suggestion would depend on what the execution environment of your application is: ASP.NET, WCF, Console, WinForms, WPF, etc.
system.net.mail.smtpclient has two methods for which I am very confused.
1 . SendAsync(MailMessage, Object)
Sends the specified e-mail message to an SMTP server for delivery. This method does not block the calling thread and allows the caller to pass an object to the method that is invoked when the operation completes. -MSDN
2 . SendMailAsync(MailMessage)
Sends the specified message to an SMTP server for delivery as an asynchronous operation. -MSDN
Notice that the names of two methods are different so it is not an overload. What exactly is the difference here?
I am looking for very clear answer as the description given by MSDN for both methods is very ambiguous (at least for me it is.)
The difference is one SendMailAsync uses the new async/await technology and the other uses the old callback technology. And more importantly, the Object that's passed is simply passed into the event handler as the userState when the method completes.
Firstly, they both work asynchronously.
However, SendAsync has existed since .NET 2. In order to maintain backwards compatiblity whilst supporting the new Tasks system SendMailAsync was added.
SendMailAsync returns a Task rather than void and allows the SmtpClient to support the new async and await functionality if required.
//SendAsync
public class MailHelper
{
public void SendMail(string mailfrom, string mailto, string body, string subject)
{
MailMessage MyMail = new MailMessage();
MyMail.From = new MailAddress(mailfrom);
MyMail.To.Add(mailto);
MyMail.Subject = subject;
MyMail.IsBodyHtml = true;
MyMail.Body = body;
MyMail.Priority = MailPriority.Normal;
SmtpClient smtpMailObj = new SmtpClient();
/*Setting*/
object userState = MyMail;
smtpMailObj.SendCompleted += new SendCompletedEventHandler(SmtpClient_OnCompleted);
try
{
smtpMailObj.SendAsync(MyMail, userState);
}
catch (Exception ex) { /* exception handling code here */ }
}
public static void SmtpClient_OnCompleted(object sender, AsyncCompletedEventArgs e)
{
//Get the Original MailMessage object
MailMessage mail = (MailMessage)e.UserState;
//write out the subject
string subject = mail.Subject;
if (e.Cancelled)
{
Console.WriteLine("Send canceled for mail with subject [{0}].", subject);
}
if (e.Error != null)
{
Console.WriteLine("Error {1} occurred when sending mail [{0}] ", subject, e.Error.ToString());
}
else
{
Console.WriteLine("Message [{0}] sent.", subject);
}
}
//
}
//SendMailAsync
public class MailHelper
{
//
public async Task<bool> SendMailAsync(string mailfrom, string mailto, string body, string subject)
{
MailMessage MyMail = new MailMessage();
MyMail.From = new MailAddress(mailfrom);
MyMail.To.Add(mailto);
MyMail.Subject = subject;
MyMail.IsBodyHtml = true;
MyMail.Body = body;
MyMail.Priority = MailPriority.Normal;
using (SmtpClient smtpMailObj = new SmtpClient())
{
/*Setting*/
try
{
await smtpMailObj.SendMailAsync(MyMail);
return true;
}
catch (Exception ex) { /* exception handling code here */ return false; }
}
}
}
SendMailAsync a simple TAP wrapper for SendAsync
More info: Are the SmtpClient.SendMailAsync methods Thread Safe?
I had a web service to send email in C#
[WebMethod]
public string sendEmail()
{
MailMessage mail = new MailMessage();
SmtpClient smtpsvr = new SmtpClient("smtp.gmail.com");
// mail settings
// smtpsvr settings
smtpsvr.SendCompleted += new SendCompletedEventHandler(sentCompleteCallBack);
try
{
smtpsvr.SendAsync(mail, "Email 1");
return "sent"; //this return only indicate email has been sent out
}
catch (Exception)
{
return "failed";
}
}
private void sentCompleteCallBack(object sender, AsyncCompletedEventArgs e)
{
if (e.Error != null) //error
{
sendResult = "email failed " + (string)e.UserState;
}
else
{ //this result, indicate the process of sending email has been completed
sendResult = "email sent " + (string)e.UserState;
}
// ??? haw to pass sendResult value to client page
}
I tried using properties string, class string to get sendResult value; but at the end in client page (aspx), only got empty/null. I only can get string sendEmail() value.
How to pass back the sendResult value to client ?
Thanks so much for the help !
/**********/
edited
May be I have to change the code like this ? (still using sendAsync()
[WebMethod]
public string sendEmail()
{
MailMessage mail = new MailMessage();
SmtpClient smtpsvr = new SmtpClient("smtp.gmail.com");
//mail settings
//smtpsvr settings
smtpsvr.SendCompleted += new SendCompletedEventHandler(sentCompleteCallBack);
try
{
smtpsvr.SendAsync(mail, "email 1");
return "sent";
}
catch (Exception)
{
return "failed";
}
}
private void sentCompleteCallBack(object sender, AsyncCompletedEventArgs e)
{
if (e.Error != null) //error
{
//write to DB_email_status = "failed"
}
else
{
//write to DB_email_status = "success"
}
}
in client side page (aspx) :
(1) call web service to send email.
(2) get email sent string value from sendEmail() method.
(3) button_onclick : view/get data from DB_email_status.
(???) is this case achieveable ?
(!!!) Thanks so much.
If you are using stateless protocol as in HTTP , then the server(Web service) can give only one response to the client. So, there is no way for the WebService to send details of the Async results to the client unless the client probes it again for status.
IMO, your best bet is to use .Send() instead of .SendAsync(). You can use Timeout property to prevent the method from hanging.
Why dont you just pass in a bool as return value in your SendEmail method like below
bool sentEmail()
{
bool isValid = false;
try
{
//if email successful
isValid = true
}
catch(Exception ex)
{
//Email Fails
isValid = false;
}
return isValid;
}
or using send() and thread.
[WebMethod]
public bool sendEmail()
{
//mail settings
//smtp settings
try
{
smtp.Send(mail);
return true;
}
catch
{
return false;
}
client page (aspx)
private void send()
{
Thread.Sleep(100;
//call web service: sendEmail()
if true
{ label.Text = "sent"} else {label.Text = "fail"}
}
private void button_Onclick(...)
{
Thread t = new Thread (send);
t.Start();
t.Join();
}
will behave similiar like sendAsync(), but easier to get return value.
I use SendCompletedEventHandler of SmtpClient when sending a list of emails.
The SendCompletedEventHandler is only called when have already sent all emails in the list.
I expexted, that SendCompletedEventHandler is called when an email is sent.
Is there something wrong in my code?
public void SendAllNewsletters(List<string> recipients)
{
string mailText = "My Text";
foreach(string recipient in recipients)
{
//if this loop takes 10min then the first call to
//SendCompletedCallback is after 10min
SendNewsletter(mailText,recipient);
}
}
public bool SendNewsletter(string mailText , string emailaddress)
{
SmtpClient sc = new SmtpClient(_smtpServer, _smtpPort);
System.Net.NetworkCredential SMTPUserInfo = new System.Net.NetworkCredential(_smtpuser, _smtppassword);
sc.Credentials = SMTPUserInfo;
sc.SendCompleted += new SendCompletedEventHandler(SendCompletedCallback);
MailMessage mm = null;
mm = new MailMessage(_senderemail, emailaddress );
mm.IsBodyHtml = true;
mm.Priority = MailPriority.Normal;
mm.Subject = "Something";
mm.Body = mailText ;
mm.SubjectEncoding = Encoding.UTF8;
mm.BodyEncoding = Encoding.UTF8;
//Mail
string userState = emailaddress;
sc.SendAsync(mm, userState);
return true;
}
public void SendCompletedCallback(object sender, AsyncCompletedEventArgs e)
{
// Get the unique identifier for this asynchronous operation.
String token = (string)e.UserState;
if (e.Error != null)
{
_news.SetNewsletterEmailsisSent(e.UserState.ToString(), _newslettername, false, e.Error.Message);
}
else
{
_news.SetNewsletterEmailsisSent(e.UserState.ToString(), _newslettername, true, string.Empty);
}
}
You are creating a new instance of SmtpClient each and every time (and then re-assigning the handler). Use a static variable with a bigger scope.
It works as expected on my machine except that the constructor of MailMessage throw a format exception because "My Tex" is not a valid email address. The first argument is the email address of the sender.
As Josh Stodola points out, you should cache the SmtpClient for the life of this class rather than creating another one for each call. If you don't cache the SmtpClient, then you should add the following line to the end of your SendCompletedCallback (preferably in a finally block):
((SmtpClient)sender).SendCompleted -= SendCompletedCallback;
If this doesn't help you, perhaps you could post more details - such as what is the data in the event args that do get called?