I need to send about 5000 emails as newsletter to my website users asynchronously.. The problem is i don't know the best way to send them asynchronously ..Please help me updating my code to make it asynchronously
My Code:
public string SendEmail()
{
foreach (var emailAddress in EmailList)
{
var message = new MailMessage("myemail#gmail.com", emailAddress);
message.Subject = "hi";
SmtpClient client = new SmtpClient("smtp.gmail.com", 587);
client.Credentials = new NetworkCredential("myemail#gmail.com", "*****");
client.EnableSsl = true;
client.Send(message);
}
return "done";
}
Thank you , Lucy
Take a look at the async and await keywords.
https://msdn.microsoft.com/en-us/library/mt674882.aspx
The async and await keywords in C# are the heart of async programming. By using those two keywords, you can use resources in the .NET Framework or the Windows Runtime to create an asynchronous method almost as easily as you create a synchronous method. Asynchronous methods that you define by using async and await are referred to as async methods.
MSDN explains the syntax side of things. The bigger concern is error handling and reliably. Dumping 5,000 emails into a list and hitting the "send" button on them is a little optimistic. Do these emails need to be reliably delivered? What happens if 3,000 of them send, and a network error suddenly causes temporary connectivity loss to the outgoing mail server? Are you going to resend all 5,000 when it starts working again? Just forget about the last 2,000? Are the recipients going to be mad because they got duplicates, or didn't get the message at all? How are you going to troubleshoot errors?
A pattern that I've found that has worked really well (whether you are sending synchronously or asynchronously), is to generate the messages and store each in a database table, and then use something like the following:
public void SendAllEmails()
{
var emails = SomeClass.GetAllUnsentEmails();
foreach(Email message in Emails)
{
var success = SendEmail(message);
if (!success)
{
// Do you want to do something if it fails?
}
}
}
public bool SendEmail(Email message)
{
try
{
// 1. Send the email message
// 2. Update the "SentOn" date in the database
// 3. return true
}
catch(Exception ex)
{
SomeClass.CreateEmailErrorEntry(message, ex); // store error in a table or log
return false;
}
}
edit: if it's being used inside of action you should mark your action async
public async Task<ActionResult> MyAction()
for a start, don't create SmtpClient for every message so as follows
and also this, should send the mails asynchronously but wait for all of them to be sent
public string SendEmail()
{
var tasks = new List<Task>();
var client = new SmtpClient("smtp.gmail.com", 587);
client.Credentials = new NetworkCredential("myemail#gmail.com", "*****");
client.EnableSsl = true;
foreach (var emailAddress in EmailList)
{
var message = new MailMessage("myemail#gmail.com", emailAddress);
message.Subject = "hi";
tasks.Add(client.SendMailAsync(message));
}
while(tasks.Count > 0)
{
var idx = Task.WaitAny(tasks.ToArray());
tasks.RemoveAt(idx);
}
return "done";
}
Related
I have a Contact page where users are able to complete a form which is sent to a Controller.
This Controller generates an email based on form data and returns a success view to the user.
The problem is that the success view is not displayed until email sending is completed, but this takes too much time (around 30 seconds), which is not acceptable.
First, I tried synchronous sending:
public ActionResult Contact(string TestMessage)
{
// Prepare SMTP client
SmtpClient Client = new SmtpClient()
{
DeliveryMethod = SmtpDeliveryMethod.Network,
EnableSsl = true,
Host = "smtp.test.com",
Port = 587,
UseDefaultCredentials = false,
Credentials = new System.Net.NetworkCredential("smtpuser", "smtppass")
};
// Prepare message
MailMessage MailMessage = new MailMessage()
{
From = new MailAddress("sender#mydomain.com"),
Subject = "Test",
BodyEncoding = System.Text.Encoding.UTF8,
SubjectEncoding = System.Text.Encoding.UTF8
};
MailMessage.To.Add(new MailAddress("recipient#mydomain.com"));
MailMessage.Body = TestMessage;
// Send mail
Client.Send(MailMessage);
// Return success view
return View("ContactSuccess");
}
After that I tried asynchonous sending. I have spent hours reading StackOverflow questions to find the miracle solution. But the result is always the same, page is still waiting for email sending to be completed.
Asynchronous attempt 1
// Send mail
Client.SendCompleted += (s, e) =>
{
Client.Dispose();
MailMessage.Dispose();
};
System.Threading.ThreadPool.QueueUserWorkItem(o =>
Client.SendAsync(MailMessage, Tuple.Create(Client, MailMessage)));
Asynchronous attempt 2
System.Threading.Tasks.Task.Run(() => SendViaAsync(MailMessage));
Asynchronous attempt 3
System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(
cancellationToken => Client.Send(MailMessage)
);
Asynchronous attempt 4
// Send mail
System.Threading.ThreadPool.QueueUserWorkItem(SendViaAsync, MailMessage);
With added methods:
private void SendViaAsync(object MailMessageObject)
{
MailMessage MailMessage = (MailMessage)MailMessageObject;
SmtpClient Client = Utilities.CreateEmailClient();
Client.SendCompleted += new SendCompletedEventHandler(SmtpClient_SendCompleted);
Client.Send(MailMessage);
}
void SmtpClient_SendCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
MailMessage mail = e.UserState as MailMessage;
if (!e.Cancelled && e.Error != null)
{
// error handling
}
}
Asynchronous attempt 5
public async System.Threading.Tasks.Task<ActionResult> Contact(string TestMessage)
// ...
// Send mail
await SendEmail(MailMessage);
With added method:
public async System.Threading.Tasks.Task SendEmail(MailMessage MailMessage)
{
SmtpClient Client = new SmtpClient()
{
DeliveryMethod = SmtpDeliveryMethod.Network,
EnableSsl = true,
Host = "smtp.test.com",
Port = 587,
UseDefaultCredentials = false,
Credentials = new System.Net.NetworkCredential("smtpuser", "smtppass")
};
await Client.SendMailAsync(MailMessage);
}
Please note that when locally testing, email is sent immediately (in both synchronous or asynchronous modes). In production only, it takes around 30 seconds. So maybe there is something like an antivirus scan in my hosting provider, but anyway I can't figure out why the success view is not sent until mail sending is completed.
I am desperate. Any help would be much appreciated.
Any attempts with await will definitely fail to accomplish what you want by definition. But in general I don't think it's wise to have an asp.net page starting up processes which may keep running beyond the lifetime of the request. And perhaps that's exactly what's happening internally: ASP.Net not finalising the request until all its child processes have finished.
Personally, I would de-couple the process altogether. First because each part will be simpler to maintain, but also because it's more secure.
Have your application dump the email, or the data to build the email, into a queue of sorts. This could be a database, a file, a folder or whatever, but some temporary storage.
Then develop a background service or daemon, which reads this queue and processes the emails. It will end up being more flexible, more scalable and safer.
I think I have understood the problem. I can't believe it.
I didn't mention it in my bug reproduction, but I am using Google reCAPTCHA in the contact form.
It seems that reCAPTCHA is very slow in production environment!
After deactivating it, it appears that email is immediately sent.
Now, I just have to replace reCAPTCHA with a faster checking mecanism.
Sorry for disturbance, I hope this will help some of us later. At least some asynchronous programming examples are listed here.
There is a builtin functionality in asp net you can use it as easy as written below
public ActionResult SendEmail(User user)
{
HostingEnvironment.QueueBackgroundWorkItem(ct =>
{
// Prepare SMTP client
SmtpClient Client = new SmtpClient()
{
DeliveryMethod = SmtpDeliveryMethod.Network,
EnableSsl = true,
Host = "smtp.test.com",
Port = 587,
UseDefaultCredentials = false,
Credentials = new System.Net.NetworkCredential("smtpuser", "smtppass")
};
// Prepare message
MailMessage MailMessage = new MailMessage()
{
From = new MailAddress("sender#mydomain.com"),
Subject = "Test",
BodyEncoding = System.Text.Encoding.UTF8,
SubjectEncoding = System.Text.Encoding.UTF8
};
MailMessage.To.Add(new MailAddress("recipient#mydomain.com"));
MailMessage.Body = TestMessage;
// Send mail
Client.Send(MailMessage);
});
// Return success view
return View("ContactSuccess");
}
if you want to go over advance scenarios , like handling many background jobs you can use Hangfire library
this blog post will also help you solve your problem
this is how when I need to send email gives me error. But the mistake that since gives me is this:
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.
I have accumulated since the MVC and have used class to keep track of ie areas of the page. The reason I have used SendAsync is precisely that it goes a little faster to send email, etc..
This error only happens when I try to send email to users.
public static void NewPassword(string mail, string name, string password)
{
MailDefinition oMailDefinition = new MailDefinition();
oMailDefinition.BodyFileName = "~/MailList/emailskabelon/NewPassword.html";
oMailDefinition.From = FromMail;
Dictionary<string, string> oReplacements = new Dictionary<string, string>();
oReplacements.Add("<<navn>>", name);
oReplacements.Add("<<password>>", password);
System.Net.Mail.MailMessage oMailMessage = oMailDefinition.CreateMailMessage(mail, oReplacements, new LiteralControl());
oMailMessage.Subject = NewpasswordTitle + WebsiteName;
oMailMessage.IsBodyHtml = true;
SmtpClient smtp = new SmtpClient(AzureApi);
System.Net.NetworkCredential netcred = new System.Net.NetworkCredential(AzureName, AzurePassword);
smtp.UseDefaultCredentials = false;
smtp.EnableSsl = true;
smtp.Credentials = netcred;
smtp.Port = Convert.ToInt32("25");
smtp.DeliveryMethod = SmtpDeliveryMethod.Network;
using (var smtpClient = new SmtpClient())
{
smtp.SendAsync(oMailMessage, null);
}
}
I've tried to do like this:
public static async NewPassword(string mail, string name, string password)
{
....
using (var smtpClient = new SmtpClient())
{
await smtp.SendAsync(oMailMessage, null);
}
i have see here: https://stackoverflow.com/a/35212320/7391454
Change your method to:
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);
}
}
And call it like:
var task = SendEmail(toEmailAddress, emailSubject, emailMessage);
var result = task.WaitAndUnwrapException();
Have a look here Asynchronously sending Emails in C#?
and here How to call asynchronous method from synchronous method in C#?
You can also try to define your async option inside a separate thread.
I believe that you already have inserted the async tag in your page.
And if everything is okay then try to put your code in below block.
this.Page.RegisterAsyncTask(new PageAsyncTask(async ctoken => {
var result = await SomeOperationAsync(ctoken);
// result operations.
}));
Not really replying your original question, but just wanted to stress that you are better off calling an email sending code without keeping the calling thread waiting. Although you are using async/await, in user's perspective, you are still waiting in the browser while the server is done sending emails. It may be few milliseconds, but still it's better to let this handled by a background worker.
So IMO, using HostingEnvironment.QueueBackgroundWorkItem(x=> SendEmail()); would be a better approach.
Having said that, you still have a slight risk of the asynchronous task being terminated if app domain recycles in the middle. But that's highly unlikely in your case I would say. Even if that happens, you can use a cancellation token and work your way around it.
I'm trying to understand why I am not being able to send async mails (maybe it is related that I'm running it from NUnit?
When I do simple smtp.Send(mail);is it working well
// Nunit test
{
[TestFixture]
public class Tester
{
[Test]
public void TestSendTestMail()
{
// Arrange
// Act
await EbayProxy.Instance.SendTestMail();
// Assert
}
}
public async Task SendTestMail()
{
MailMessage mail = new MailMessage();
mail.From = new MailAddress(_mailFrom);
mail.To.Add(_mailTo);
mail.Subject = "Test Mail Subject Async";
mail.Body = "Test Mail Body";
mail.IsBodyHtml = true;
SmtpClient smtp = new SmtpClient(_smtpClient, Convert.ToInt32(_smtpPort));
smtp.EnableSsl = true;
smtp.Credentials = new NetworkCredential(_mailFrom, _mailFromPassword);
await smtp.SendMailAsync(mail); // Not sending mail :(
}
Actually before you learn about Async/Await (which is a great topic!), I would question what it is that you are trying to achieve, full stop.
Why would you want to test the email facility itself, you should give the email client some element of trust but furthermore as mail isn't guaranteed you can only hope that the mail arrives at is destination. Actual acknowledgement would involve the recipient.
The email client seems to be a singleton but could potentially be passed in as a collaborator to another orchestrating class and you could use a mock and verify that the send mail method is called, trusting that the mail client does what it supposed to. I'm pretty certain that the mailing facility is part of some other process....
Furthermore, I'd use an integration type test for this external type command, to verify that when everything is plumbed in properly an actual email gets sent. But I suppose It could be argued what you have already is an integration test, and maybe that's your intention.
As explained many times before...
How to call asynchronous method from synchronous method in C#?
Calling async method synchronously
http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
I think you'll find this code is causing a dead lock. Try...
[TestFixture]
public class Tester
{
[Test]
public async Task TestSendTestMail()
{
// Arrange
// Act
await EbayProxy.Instance.SendTestMail();
// Assert
}
}
public async Task SendTestMail()
{
MailMessage mail = new MailMessage();
mail.From = new MailAddress(_mailFrom);
mail.To.Add(_mailTo);
mail.Subject = "Test Mail Subject Async";
mail.Body = "Test Mail Body";
mail.IsBodyHtml = true;
SmtpClient smtp = new SmtpClient(_smtpClient, Convert.ToInt32(_smtpPort));
smtp.EnableSsl = true;
smtp.Credentials = new NetworkCredential(_mailFrom, _mailFromPassword);
await smtp.SendMailAsync(mail); // Not sending mail :(
}
As discussed here...
How do I test an async method with NUnit, eventually with another framework?
Using C#/Asp.Net
I have an application that goes out to a web service. On return there's a couple of things that happen:
void Cleanup(Response response)
{
// My web service takes up to 30 seconds
// then this method is called
// I send this email
var email = SaleEmail.Create(
response.ID
DateTime.Now,
"A sale was made!");
email.Send();
// Then redirect
Response.Redirect(response.RedirectUrl, false);
Context.ApplicationInstance.CompleteRequest();
}
The idea is, on completion of the web service an email is sent, then the page is redirected.
Previously, I used a normal redirect - the result was that 90% of the emails were never sent.
I've changed the redirect pattern, however it's still not perfect - I'm guessing 25% of emails are still not coming through.
Anyone advise any improvements to the pattern I have?
Email code:
public static void Send(MailMessage message)
{
Guard.Argument.NotNull(() => message);
var c = new SmtpClient();
try
{
c.Send(message);
}
catch (Exception ex)
{
}
finally
{
c.Dispose();
message.Dispose();
}
}
Maybe
Try to implement async task method with sendAsync and await
this await will help you to wait how much needed to send email before jump to redirect
//async Task
public async Task Cleanup(Response response)
{
using (var smtpClient = new SmtpClient())
{
await smtpClient.SendAsync();...//await
}
}
you should rewrite your initialization somehow, make it look like this:
smtpClient.SendAsync();
smtpClient.SendCompleted += new SendCompletedEventHandler(smtpClient_SendCompleted);
on smtpClient_SendCompleted function write your redirection code
MVC 5.1, .NET 4.5
In our simple web site we need to send an email. Our email service is called by a Post back event and it sends the email, then builds a result page and returns it.
This slows the page since the page has to wait for the synchronous email to be sent.
If we run the email in a different thread, can the site continue on to create the page and return it to the user as the email is being processed, or will a long running task cause the page to be not rendered.
Basically, can I send am email non blocking for performance reasons.
Note: We can't send the email request to a service or other system.
Assuming that you you use built in SmtpClient you can use SmtpClient.SendMailAsync method
smtpClient.SendMailAsync(new MailMessage{ ... });
Sends the specified message to an SMTP server for delivery as an asynchronous operation
Calling StartNew is functionally equivalent to creating a task by using one of its constructors, and then calling the Task.Start method to schedule the task for execution.
Task.Factory.StartNew(() => {
//do somethings
});
Example:
Task.Factory.StartNew(() => {
SendMail("Hello world", "email#yahoo.com", "TEST");
});
public static bool SendMail(string subject, string to, string body)
{
string fromMailAddress = ConfigurationManager.AppSettings["MailAddress"];
string fromMailPassword = ConfigurationManager.AppSettings["MailPassword"];
string fromMailName = ConfigurationManager.AppSettings["MailName"];
var networkConfig = new NetworkCredential(fromMailAddress, fromMailPassword);
var mailServer = new SmtpClient()
{
Host = ConfigurationManager.AppSettings["SmtpHost"],
UseDefaultCredentials = false,
Credentials = networkConfig
};
if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings["SmtpPort"]))
mailServer.Port = Convert.ToInt32(ConfigurationManager.AppSettings["SmtpPort"]);
var message = new MailMessage()
{
Subject = subject,
SubjectEncoding = Encoding.UTF8,
IsBodyHtml = true,
BodyEncoding = Encoding.UTF8,
};
//message send config
message.To.Add(new MailAddress(to));
message.From = new MailAddress(fromMailAddress, fromMailName);
message.Body = body;
try
{
mailServer.SendAsync(message, null);
}
catch (Exception ex)
{
return false;
}
return true;
}