Doubts on sending more than one email asynchronously in MVC3 - c#

In my application I have a functionality to save and publish articles. So when I click on "Save and Publish" button three things happened:
Published articles get saved in database.
A Notification email goes to a group of users that a new articles is available.
After sending emails page get redirect to "Article Listing" page without showing any success or failure message for an email.
Now Number of users who will receive emails can vary say for e.g 10, 30 50 and so on. I want to send notification emails asynchronously so that page won't get block until all the mails doesn't go to their receptionists.
Given below is a piece of code from "PublishArticle" action method
foreach (string to in emailIds)
{
ArticleNotificationDelegate proc = Email.Send;
IAsyncResult asyncResult = proc.BeginInvoke(subject, body, to, from, cc, null, null, null);
}
Below I have defined a delegate to invoke Send method
private delegate bool ArticleNotificationDelegate (string subject, string body, string to, string from, string cc, string bcc = null);
and this is the code to send an email:
public static bool Send(string subject, string body, string to, string from, string cc, string bcc = null)
{
bool response;
MailMessage mail = new MailMessage();
MailAddress fromAddress = new MailAddress(from);
mail.To.Add(to);
if (!string.IsNullOrWhiteSpace(cc))
{
mail.CC.Add(cc);
}
if (!string.IsNullOrWhiteSpace(bcc))
{
mail.Bcc.Add(bcc);
}
mail.From = fromAddress;
mail.Subject = subject;
mail.Body = body;
mail.IsBodyHtml = true;
mail.Priority = MailPriority.High;
SmtpClient client = new SmtpClient();
try
{
client.Send(mail);
response = true;
}
catch (Exception)
{
response = false;
}
finally
{
client.Dispose();
mail.Dispose();
}
return response;
}
Although this code is working fine but still I want to know that whether my this approach is fine and will not cause any problem in future.
If there is a better approach to accomplish my objective then please suggest me.
Note: As I am using .net framework 4.0 so cannot use the new features of Asyn and await available in 4.5.
Also I had used method client.SendAsync by simply replacing the client.Send and rest of my code in above Send method was same. After this change NO mails were being send and also it did not throw any exception. So I did not go with this change.

I want to send notification emails asynchronously so that page won't get block until all the mails doesn't go to their receptionists.
That's a very dangerous approach, because ASP.NET will feel free to tear down your app domain if there are no active requests. ASP.NET doesn't know that you've queued work to its thread pool (via BeginInvoke). I recommend that you use a solution like HangFire to reliably send email.

Related

What is the best way to create a dynamic email template that can be modified without changing code in C# .net MVC?

I'm creating a web application that needs to send the user an email when an action is completed. In the email body, I'm required to inform the user the transaction details in their last action. I'm aware of how to send emails to a person and how to I could possibly add in the transaction details by hardcoding the message in a string. What I would like to know is, how can I make this email subject without hardcoding it by either making it in a JSON file or in my database so that changing the message in the email body does not require me to publish the application again.
Currently, my idea is to create a JSON file with the mail body in several keys and combine the key values into a string in the application. This is the JSON structure that I'm currently considering:
{
"NewTransaction":{
"EN":{
"Greeting":"Dear ",
"Introduction":"We found that a transaction was completed against your name on ",
"Body1":". The following are some of your transaction details:",
"Body2":"Kindly let us know if there is any discrepancy with this transaction"
},
"MY":""
}
}
While trying to figure this out, I've found this package that seems like it would be pretty helpful in creating the mail body.
Is there any other better way that perhaps I'm overlooking?
Thanks in advance!
You should look into Postal for the sending of the email.
To get the data, why don't you just use SQL, you can store the subject and content in fields that you can update on the fly.
Every question that starts with "What is the best way to" is probably too broad or opinion-based.
In my vision, you could create a table (field name on the right, sample data on left)
id: 1
from: ##sysadress##
to: ##useremail##
subject: Trnasaction completed
body: Here goes your html body filled with ##data##
... more fields as needed ...
see ##these things## - these would be your placeholders for dynamic text.
In your action table you will add a column, something like actionMessage and link it to Message table. Even better is to create Action_Message table.
Id, ActionId, MessageId, ActionStatus
This will allow to send different messages depending if your action failed, completed or postponed, or whatever.
So, this could be one way. But you will not likely to find "the best way". Every best way can be improved upon. But this will definitely achieve your main goal - change messages without recompilation.
EmailTemplateHelper templateHelper = new EmailTemplateHelper( HttpContext.Current.Request.MapPath("~/template_path/EMAIL_TEMPLATE.template"));
Dictionary<string, string> replaceMapper = new Dictionary<string, string>()
{
{ "TAG1", emailInfo.name },
{ "TAG2", emailInfo.lastname}
};
mailMessage.Subject = templateHelper.GetSubject(replaceMapper);
mailMessage.Body = templateHelper.GetBody(replaceMapper);
mailMessage.IsBodyHtml = true;
In your template file you should have %TAG1% where you want your data to go.
public async Task SendTaskStatusMail(string name, string adminMail)
{
string path = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Template\\adminmail.html");
MailAddress to = new(adminMail);
MailAddress from = new("xyz#gmail.com");
MailMessage mailMessage = new(from, to)
{
Subject = "Task Status Update"
};
StreamReader reader = new(path);
string body;
{
body = reader.ReadToEnd();
}
body = body.Replace("{{Name}}",name);
);
mailMessage.Body = body;
mailMessage.IsBodyHtml = true;
SmtpClient smtp = new();
smtp.Host = "smtp.gmail.com";
smtp.Port = 587;
smtp.Credentials = new NetworkCredential("abc#gmail.com", apppassword);
smtp.EnableSsl = true;
smtp.Send(mailMessage);
}

SmtpClient SendAsync error

BACKGROUND
I have written a little console application that monitors a RabbitMQ queue for emails. Whenever an email is pushed onto the queue, my application would pick up that email, process it and send it.
CODE
Below is the code for my email service, it is what actually sends out the email.
public class MailService : IMailService
{
private readonly SmtpClient _smtpClient = new SmtpClient();
public void SendEmail(string toEmail, string subject, string body, bool isHtml)
{
var emailMessage = BuildEmailMessage(toEmail.Trim(), subject.Trim(), body, isHtml);
_smtpClient.SendAsync(emailMessage, null);
}
#region Helpers
private static MailMessage BuildEmailMessage(string toEmail, string subject, string body, bool isHtml)
{
const string fromEmailAddress = "james.brown#world.com";
var emailMessage = new MailMessage(fromEmailAddress, toEmail, subject, body) { IsBodyHtml = isHtml };
return emailMessage;
}
#endregion
}
PROBLEM
I had 2 emails on the RabbitMQ queue, and when I fired up my console application consumer, it threw the following exception after sending the first email (I received that 1st email in my inbox).
An asynchronous call is already in progress. It must be completed or canceled before you can call this method.
I did some digging around, and saw this thread which explains why I got this. Apparently using SendAsync() isn't the way to go because:
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.
So what is the recommend way of going about this? I can create a new instance of the SmtpClient class for each email, but is that really a good idea?
Send the email synchronously, there's no need to use the older async syntax of SendAsync. Also, ensure your SmtpClient only gets hit from one thread at a time by wrapping it in a using statement. There is a slight performance penalty, but you probably won't notice it unless you're sending a ton of emails. And if you are sending a ton, then overload your MailService.SendEmail method to accept an IEnumerable<EmailModel> and send them all at once using a single SmtpClient.
public void SendEmail(string toEmail, string subject, string body, bool isHtml)
{
var emailMessage = BuildEmailMessage(toEmail.Trim(), subject.Trim(), body, isHtml);
using(var client = new SmtpClient())
{
_smtpClient.Send(emailMessage);
}
}
//this would be the right way to do async
public async Task SendEmailAsync(string toEmail, string subject, string body, bool isHtml)
{
var emailMessage = BuildEmailMessage(toEmail.Trim(), subject.Trim(), body, isHtml);
using(var client = new SmtpClient())
{
_smtpClient.SendMailAsync(emailMessage);
}
}
//this would be the right way to do multiple emails
//you'd need to create an EmailModel class to contain all the details for each email (similar to MailMessage, but it would prevent your own code from taking a dependency on System.Net.Mail
public void SendEmail(IEnumerable<EmailModel> emailModels)
{
var mailMessages = emailModels.Select(em => ConvertEmailModelToMailMessage(em));
using(var client = new SmtpClient())
{
foreach(var mailMessage in mailMessages)
{
//you may want some error handling on the below line depending on whether you want all emails to attempt to send even if one encounters an error
_smtpClient.Send(mailMessage);
}
}
}
private MailMessage ConvertEmailModelToMailMessage(EmailModel emailModel)
{
//do conversion here
}

How to resolve "The operation has timed out."?

I'm trying to send email using C# code, email was sent when I send it to single person but it is not getting sent when I send it to multiple persons. and getting an error "The operation has timed out." I'm not getting the reason behind it. Please help to find the reason.
Code:
public string SendEmail(List<string> ToEmailAddresses,string body, string emailSubject)
{
var smtp = new SmtpClient { DeliveryMethod = SmtpDeliveryMethod.Network };
smtp.Host = "xyz-host-name";
smtp.Port = 25;
smtp.EnableSsl = false;
var fromAddress = new MailAddress(ConfigurationManager.AppSettings["MailUserName"], "Rewards and Recognition Team");
using (var message = new MailMessage() { Subject = emailSubject, Body = body })
{
message.From = fromAddress;
foreach (string email in ToEmailAddresses)
{
message.To.Add(email);
}
message.IsBodyHtml = true;
try
{
_logger.Log("EmailService-SendEmail-try");
smtp.Send(message);
return "Success";
}
catch (Exception ex)
{
_logger.Log("EmailService-SendEmail-" + ex.Message);
return "Error";
}
}
}
Whenever you're attempting to do anything which may take some time, it's always best practice to run it in a separate thread or use an asynchronous method.My recommendation would be to use the SmtpClient.SendAsync method. To do this, change:
public string SendEmail(List<string> ToEmailAddresses, string body, string emailSubject)
to:
public async string SendEmail(List<string> ToEmailAddresses, string body, string emailSubject)
and include await smtp.SendAsync(...) rather than smtp.Send(...). This will allow further execution of the UI thread whilst sending the mail and not make the application grey out with the "not responding" message.To read more about smtp.SendAsync(...) including parameters and remarks, take a look at the MSDN documentation regarding the method.

SendMail with Link Received with Error

I have the following function which sends mail. The body of the mail has a link (href) in some cases.
public static void SendMail(string from, string to, string subject, string message, bool isHTML = true)
{
if (!String.IsNullOrEmpty(to) && ConfigurationManager.AppSettings["no_mail"] == null)
{
MailMessage mailMessage = new MailMessage(from,
to,
subject,
message);
string bccAddr = ConfigurationManager.AppSettings["managementMailAddress"];
if (!String.IsNullOrEmpty(bccAddr))
mailMessage.Bcc.Add(bccAddr);
bccAddr = ConfigurationManager.AppSettings["debugMailAddress"];
if (!String.IsNullOrEmpty(bccAddr))
mailMessage.Bcc.Add(bccAddr);
if (isHTML)
mailMessage.IsBodyHtml = true;
//mailMessage.BodyEncoding = System.Text.Encoding.UTF8; // avoid 3D?
SmtpClient smtpClient = new SmtpClient(ConfigurationManager.AppSettings["mailHost"]);
smtpClient.Send(mailMessage);
}
}
Yesterday the mail clients started receiving mails with 3D inserted after the equals sign, and so ceased to work properly.
For example, I see "...href=3Dhttp://..." instead of "...href=http://...".
I made a small change in the code with reference to the BCC list, but nothing that should have a serious effect. The web.config file has not changed, other than putting in various mail addresses in configuration variables.
Why are my links broken?

Send email from ASMX web service

Recently somebody answered me on this site, that this method can send email from .net application:
public static void SendEmail(bool isHTML, string toEmail, string fromEmail, string subject, string message)
{
var sm = new SmtpClient("smtp.mail.ru");
sm.Credentials = new NetworkCredential("MyLogin", "MyPass");
var m = new MailMessage(fromEmail, toEmail) { Subject = subject, Body = message };
if (isHTML)
{
m.IsBodyHtml = true;
}
sm.Send(m); // SmtpException
}
It is true. But now I want to use this method from Asp.Net WebService, but I have SmtpException at last string. Why? And do I send email from web service.
So the problem is not with your code, rather the transaction with the SMTP server is failing for some reason. If you have access to the SMTP server, check its logs. Otherwise you might have to use a sniffer like WireShark to figure it out.
To verify this, you can try using a different mail server, assuming you have proper access to that server it should send the mail properly.

Categories