2

I am trying to send 500 e-mail messages to our customers from an ASP.NET MVC 4 web application. I simply use a foreach loop like the one below. After ~50 cycles I get a generic exception saying that the mail message cannot be send. I believe I am dealing with throttling and MS Exchange settings that limit my web application. How do I simply implement throttling to bypass these limits?

Thanks.

foreach (var toAddress in addresses)
{
   var message = new MailMessage(fromAddress, toAddress)
   {
      Subject = subject,
      Body = body
   };

   message.IsBodyHtml = isHtml;

   try
   {
      using (var client = new SmtpClient())
      {
         client.Send(message);
      }
   }
   catch (Exception ex)
   {
      Debug.WriteLine("Cannot send e-mail to " + message.To + Environment.NewLine +
                      "Subject: " + subject + Environment.NewLine +
                      "Body: " + body + Environment.NewLine + 
                      "Exception: " + ex.Message);
   }
}
Soner Gönül
  • 97,193
  • 102
  • 206
  • 364
abenci
  • 8,422
  • 19
  • 69
  • 134
  • 1
    One issue might be the opening/closing of SmtpClient for every single e-mail. Have you tried opening the connection just once and closing after the foreach loop? – sfuqua Dec 18 '14 at 15:09
  • IMHO, think long, hard, and do pay attention to Brad's comments below. There are aspects of this that go beyond technical matters and is why in real world business, this type of task is usually done with appropriate services/providers (e.g. marketing/mass email providers), instead of internally. Hth... – EdSF Dec 18 '14 at 16:15
  • Since all of the messages have the same body and subject, you could use BCC to send each email to multiple clients instead of sending a separate message to each one. The resulting email would not display the To address but you could efficiently send it to your email server quickly. – Grax32 Dec 18 '14 at 16:16
  • @Grax: this is not my case, sorry. Every message has a different body. – abenci Dec 19 '14 at 10:51

3 Answers3

2

I would have a look at Quartz.NET and use a service to send the emails off. As far as queuing, maybe look into either a simple file storage or a database.

If you use something like Thread.Sleep, keep in mind you'll be holding up the server from rendering the final output (e.g. if this is triggered via form submit, the resulting view won't be displayed until all emails are sent). By using a library like quartz, you can keep the server responsive to requests while off-loading the email send-off.

Also, for reference, there's another SO post about performing expensive operations without dragging the UX. This can be found here, with (IMHO) the most relevant answer by TheCodeKing.

Community
  • 1
  • 1
Brad Christie
  • 100,477
  • 16
  • 156
  • 200
  • Holding the rendering would not be a problem. The problem is that now even using `Thread.Sleep(1000)` I can send all of them... – abenci Dec 18 '14 at 15:47
  • I meant I can't send them waiting 1000 ms between each send, I am trying with 5000 ms now... – abenci Dec 18 '14 at 15:57
  • 1
    Before you start guessing, I would either go online or call the email provider and find out how many you can send within an hour. Two reasons: You'll know what delay to use, and you'll also want to know because after you send out 500 emails you may not have any means to send out normal emails (and if this is a business, i doubt company will be happy). – Brad Christie Dec 18 '14 at 16:01
  • Now I have another doubt, is it possible that if the @domain.com does not exist anymore I get the following exception? `A first chance exception of type 'System.Net.Mail.SmtpException' occurred in System.dll.` I see that after some errors it's able to continue sending... – abenci Dec 18 '14 at 16:06
  • What's the exception's message? – Brad Christie Dec 18 '14 at 16:19
  • We are the email provider, we have the Exchange Server 2010 in house but don't know what to change to allow the _noreply@domain.com_ account to send 500 mail at time... – abenci Dec 19 '14 at 10:50
1

If you using Exchange 2010 or above there is a RecipientRateLimit throttling policy parameter that specifies the limit on the number of recipients that a user can address in a 24-hour period. If you want get around this limit you will need to change the throttling policy for the user that is sending those messages see http://www.slipstick.com/exchange/limit-number-of-internet-messages-user-can-send/

Cheers Glen

Glen Scales
  • 20,495
  • 1
  • 20
  • 23
  • Can you help me with this syntax `Set-Mailbox -Identity user_alias -ThrottlingPolicy LimitMessagesSent`? My account name is _noreply@domain.com_ and I would like to know what are it's limits before changing them... – abenci Dec 19 '14 at 10:44
  • To get what throttling policy is assoicated to a user you use Get-ThrottlingPolicyAssociation user@domain | format-list This will tell you if the default policy is applied (eg a blank policyId) or a custom policy has been applied. You would normal expect the default policy to have been applied. What you will need to do is create a new policy with the required setting and then apply that to your user eg http://blogs.technet.com/b/samdrey/archive/2013/03/27/exchange-2010-and-bes-server-throttling-policy-settings.aspx – Glen Scales Dec 21 '14 at 22:38
0

Our scenario is a remote Web Application using a local MS Exchange Server 2010 and the only working solution so far is the following. Using chunks of 20-30 messages every 10 seconds I can send the number of customized e-mail messages I want to our customers without the need to touch the MS Exchange Sever 2010 configuration (it requires many of hours of training). In addition, everything is logged in a TXT file to be sure that the messages were actually sent. I also recommend the MS Exchange Tracking Log Explorer tool to inspect if and what messages were sent.

private void SendBulkEmail(List<KeyValuePair<Customer, Order>> customerList, MailAddress sender, string subject, string body, bool isHtml, string fileNamePrefix)
{
    const int chunkSize = 20;

    string rootPath = Server.MapPath("~");

    string path = Path.GetFullPath(Path.Combine(rootPath, "Logs"));

    string fileName = CommonUtils.GetLogFileName(path, fileNamePrefix);

    int len = customerList.Count;

    TextWriter tw = new StreamWriter(fileName);

    int chunkCount = (len / chunkSize) + 1;
    int remainder = len % chunkSize;

    for (int j = 0; j < chunkCount; j++)
    {
       int start = j * chunkSize;
       int end = start + chunkSize;

       if (j == chunkCount - 1)
       {
           end = start + remainder;
       }

       SmtpClient client = new SmtpClient();

       for (int i = start; i < end; i++)
       {
           Customer customer = customerList[i].Key;

           Guid userGuid = new Guid(customer.UserId.ToString());
           MembershipUser membershipUser = Membership.GetUser(userGuid);

           string memberUsername = membershipUser.UserName;
           string memberEmail = membershipUser.Email;

           SendMessage(sender, recipient, subject, body, tw, client, context);

       }

       client.Dispose();

       Thread.Sleep(10000); // 10 seconds

       tw.WriteLine("Completed chunk #" + j);
   }

   tw.Close();
 }

public static void SendEmail(MailAddress fromAddress, MailAddress toAddress, string subject, string body, TextWriter tw, SmtpClient smtpClient)
{
    MailMessage message = new MailMessage(fromAddress, toAddress)
    {
       Subject = subject,
       Body = body
    };

    try
    {
       smtpClient.Send(message);

       if (tw != null)
       {
          tw.WriteLine("\"" + toAddress + "\",");
       }
    }
    catch (Exception ex)
    {
       if (tw != null)
       {
          tw.WriteLine(toAddress + ": " + ex.Message + " " + ex.InnerException.Message);
       }

       Debug.WriteLine("Cannot send e-mail to " + toAddress + ", " + "Exception: " + ex.Message + (ex.InnerException != null ? ", " + ex.InnerException.Message : string.Empty));
    }
}
abenci
  • 8,422
  • 19
  • 69
  • 134