This is the second part of an article demonstrating how to build out an application for sending personalized email to recipients selected from a list.
In the first part, we put together the basic structure of our ASP.NET MVC application, according to a simple list of requirements.
Now, we will add the email functionality, such that the user may select one or more recipients from a list using check boxes, then generate and send a personalize email to each.
Image by Sergio Quesada | Some Rights Reserved
Review Part I --> Send Email to Selected Recipients from your ASP.NET MVC Web Application
In the previous post, we created a stub for the SendMail
method and threw some code in there to emulate a long-running process such as sending a list of emails:
The Send Mail Method Stub:
[HttpPost]
[Authorize]
public ActionResult SendMail(MailRecipientsViewModel recipients)
{
System.Threading.Thread.Sleep(2000);
return RedirectToAction("Index");
}
We did this so that we could run our application, and all the front end functionality would work as expected. Now let's see what we need to do in order to actually send some mail.
Let's take a look at what we need to accomplish here by breaking the problem into steps. In order to send a personalized message to each of the recipients selected by the user, we need to:
- Retrieve the recipient data for each of the recipients selected by the user
- Compose a message personalized for each recipient by inserting the recipient's name into some sort of message template, and addressed to the recipient's email.
- Retrieve the current User's email address to use as the "From" email address, and the current user's name to use in the signature
- Represent the above as a "Message" which can be aggregated into a list of messages to be sent, addressed to each recipient.
- Add a record to the
SentMail
table representing the key points for each email sent (basically a log)
- When sending is complete, redirect to the Index page, and refresh, displaying updated records which include a filed for the date mail was most recently sent to each recipient.
- Pass the list to some sort of Mail Sender, which can iterate over the list and send each message.
ABOUT THE EXAMPLE PROJECT: The solutions and code examples here represent fairly generic starting points for the problems posed. The code and application would more than likely evolve, and be restructured/refactored depending upon the specifics of the application.
Given the above, it looks like we might want our SendMail method to do something like this:
Pseudo-Code for Steps in Sending Mail:
[HttpPost]
[Authorize]
public ActionResult SendMail(MailRecipientsViewModel recipients)
{
}
So, lets make that happen!
In order to keep our Action method clean and simple, we are going to make each of these steps a call to a locally defined method. The code for each step could then also be easily moved out of the controller into another class or classes, depending on the needs of your application and/or fussiness about how much work should be done within the controller itself. We aren't going to get all pedantic about it here.
We can start by thinking about what we actually receive in the recipients argument passed to SendMail
from the HTTP request body. We will get back an instance of MailRecipientsViewModel
, which provides a method getSelectedRecipientIds()
. This returns an IEnumerable<int>
representing the Ids of the recipients selected by the user on our form.
Reviewing our MailRecipientsViewModel
class:
The Get Selected Recipient Ids Method:
public class MailRecipientsViewModel
{
public List<SelectRecipientEditorViewModel> MailRecipients { get; set; }
public MailRecipientsViewModel()
{
this.MailRecipients = new List<SelectRecipientEditorViewModel>();
}
public IEnumerable<int> getSelectedRecipientIds()
{
return (from r in this.MailRecipients
where r.Selected
select r.MailRecipientId).ToList();
}
}
Now That we have our Ids, lets fill in the rest of our private helper methods. Add the following code the the controller after the SendMail
stub:
Adding Code to the Controller to Implement the Send Mail Method:
IEnumerable<MailRecipient> LoadRecipientsFromIds(IEnumerable<int> selectedIds)
{
var selectedMailRecipients = from r in db.MailRecipients
where selectedIds.Contains(r.MailRecipientId)
select r;
return selectedMailRecipients;
}
IEnumerable<Message> createRecipientMailMessages(
IEnumerable<MailRecipient> selectedMailRecipients)
{
var messageContainers = new List<Message>();
var currentUser = db.Users.Find(User.Identity.GetUserId());
foreach (var recipient in selectedMailRecipients)
{
var msg = new Message()
{
Recipient = recipient,
User = currentUser,
Subject = string.Format("Welcome, {0}", recipient.FullName),
MessageBody = this.getMessageText(recipient, currentUser)
};
messageContainers.Add(msg);
}
return messageContainers;
}
void SaveSentMail(IEnumerable<SentMail> sentMessages)
{
foreach (var sent in sentMessages)
{
db.SentMails.Add(sent);
db.SaveChanges();
}
}
string getMessageText(MailRecipient recipient, ApplicationUser user)
{
return ""
+ string.Format("Dear {0}, ", recipient.FullName) + Environment.NewLine
+ "Thank you for your interest in our latest product. "
+ "Please feel free to contact me for more information!"
+ Environment.NewLine
+ Environment.NewLine
+ "Sincerely, "
+ Environment.NewLine
+ string.Format("{0} {1}", user.FirstName, user.LastName);
}
In the code above, we see we create an instance of a class Message
. This is another Model we need to add to our Models folder. We are using the Message
class to represent everything needed to send an email:
Add the following class to the Models folder:
The Message Class:
public class Message
{
public MailRecipient Recipient { get; set; }
public ApplicationUser User { get; set; }
public string Subject { get; set; }
public string MessageBody { get; set; }
}
Also, in the createRecipientMailMessages
method, we grab the current logged-in User with the following call:
Get the Current Logged-in User:
var currentUser = db.Users.Find(User.Identity.GetUserId());
In order for this to work we need to add a reference to the Microsoft.AspNet.Identity
namespace in the usings
at the top of our code file, or this code won't work.
Now that we have broken out each of our steps into discrete private method calls, we can call these from within the SendMail method:
[HttpPost]
[Authorize]
public ActionResult SendMail(MailRecipientsViewModel recipients)
{
var selectedIds = recipients.getSelectedRecipientIds();
var selectedMailRecipients = this.LoadRecipientsFromIds(selectedIds);
var messageContainers = this.createRecipientMailMessages(selectedMailRecipients);
var sender = new MailSender();
var sent = sender.SendMail(messageContainers);
this.SaveSentMail(sent);
return RedirectToAction("Index");
}
In the above, we have working code for everything except step 4, in which we initialize an instance of MailSender
, and then actually send the mail. Now we get to the nitty-gritty of our application.
In our SendMail
code, we build up a list of Message
instances, which we then pass to a new class we haven't looked at yet - the MailSender
class.
Add a new class to the project, name it MailSender
, and paste in the following code:
The Mail Sender Class:
public class MailSender
{
public IEnumerable<SentMail> SendMail(IEnumerable<Message> mailMessages)
{
var output = new List<SentMail>();
string mailUser = "youremail@outlook.com";
string mailUserPwd = "password";
SmtpClient client = new SmtpClient("smtp.host.com");
client.Port = 587;
client.DeliveryMethod = SmtpDeliveryMethod.Network;
client.UseDefaultCredentials = false;
System.Net.NetworkCredential credentials =
new System.Net.NetworkCredential(mailUser, mailUserPwd);
client.EnableSsl = true;
client.Credentials = credentials;
foreach (var msg in mailMessages)
{
var mail = new MailMessage(msg.User.Email.Trim(), msg.Recipient.Email.Trim());
mail.Subject = msg.Subject;
mail.Body = msg.MessageBody;
try
{
client.Send(mail);
var sentMessage = new SentMail()
{
MailRecipientId = msg.Recipient.MailRecipientId,
SentToMail = msg.Recipient.Email,
SentFromMail = msg.User.Email,
SentDate = DateTime.Now
};
output.Add(sentMessage);
}
catch (Exception ex)
{
throw ex;
}
}
return output;
}
}
You will need to make sure you import the following namespaces for the code to work:
Required Namespaces for the Mail Sender Class:
using AspNetEmailExample.Models;
using System;
using System.Collections.Generic;
using System.Net.Mail;
I discuss the details of setting up the mail client for Outlook.com or Gmail in another post. For most mail hosts, the client configuration should resemble the above. However, pay attention. For one, as discussed in the post linked above, if you have some sort of two-step authorization
in place on your mail host, you will likely need to use an Application-Specific Password
for this to work. Also note, you can send mail using your Outlook.com account as a host, but unlike most other mail hosting accounts, the Outlook.com host name for SMTP is:
smtp-mail.outlook.com
Whereas Gmail is simply:
smtp.gmail.com
For other mail hosts, you may have to experiment a little, or consult the provider documentation.
With all of our pieces in place, we can now walk through the execution of SendMail()
and take an high-level look at what is going on in all these small, refactored methods, and how they align with the steps we defined to send mail to each recipient,
First, we use our list of selected Ids to retrieve a corresponding list of fully instantiated recipient instances. This list is then returned to the call in SentMail
, whereupon it is passed to the createMailRecipientMessages()
method.
This next method iterates the list of recipients, and creates a new Message
instance for each, supplying the property values needed to send an email. Two of these, the User
and MessageBody
properties, involve additional calls. Retrieving the current user requires a call into the Microsoft.AspNet.Identity
library.
The getMessageText
method, from which we retrieve the actual text for each mail message, represents a crude, "just make it work" implementation of what, in a real application, should probably be a template-based system. I have kept things simple here, but in reality we would probably like to be able to retrieve a message template from some resource or another, and populate the template properly from code without having to re-write and recompile.
How you implement this would depend significantly on your application requirements and is beyond the scope of this article (this article is already long, considering the topic is not all that advanced!). If you have either questions, or brilliant ideas for implementing such a system in your own application, I would love to hear either. This might become the topic of another article.
Once we have constructed our list of Message objects, we pass that to the MailSender.SendMail
method, and, well, send the damn mail. We can see that each Message
object is used to create a System.Net.Mail.MailMessage
object, which is then sent using our properly configured mail client.
Once each Message
is sent, we create a SentMail
object, and then return the list of List<SentMail>
back to the SendMail
controller method, at which point we persist the SentMail
objects, and redirect back to the Index
method.
Now, we likely have our test data from before, when we entered some examples to test out our front-end. You may want to go ahead and change the example.com email addresses to an actual mail account you can access, to ensure all is working properly. Then, run the application, log in, and try the "Email Selected" button again. you may want to deselect one or two of the potential recipients in the list, just to see the difference:
Try Sending Some Mail:
This time, we should see our "Busy" spinner for a moment, and then be redirected back to a refreshed Index view, now updated with the last date we sent mail to the selected recipients:
The Updated Index View After Sending Mail:
As we can see, the two items selected for sending email have now been updated with a Last Sent date.
If you have been following along, building this out as you go, and something doesn't work, I strongly recommend cloning the example project from source and trying to run that. For what appears to be a simple application, there are actually a lot of places where I may have missed some small but critical item in posting the code here on the blog. I've tried to balance providing everything you need to build this out yourself with keeping the article length manageable (and still semi-failed on the length part!).
If you clone from source, and still have an issue, please do describe it in the comments section and/or shoot me an email. Also, if you see somewhere I have made a mistake, or taken the "dumb way" to doing something, I am ALL EARS.
I've tried to combine providing a useful tutorial on the mechanics of sending mail from an application, with my own steps in thinking through the problem. Obviously, some of the content here is aimed at folks new to ASP.NET and/or Web Development in general.
Thanks for reading, and your feedback is always appreciated.
CodeProjectJohn on Google