ASP.Net

Send Email to Selected Recipients from your ASP.NET MVC Web Application Part II


Image by Sergio Quesada | Some Rights Reserved

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 checkboxes, then generate and send a personalize email to each.

Review Part I –> Send Email to Selected Recipients from your ASP.NET MVC Web Application

The Send Mail Method

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)
{
    // Mail-sending code will happen here . . .
    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.

The Problem to Solve

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.

Pseudo-Code the New and Improved SendMail Method

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)
{
    // Retrieve the ids of the recipients selected:
  
    // Grab the recipient records:
  
    // Build the message container for each:
  
    // Send the mail:
  
    // Save a record of each mail sent:
  
    // Reload the index form:
}

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.

Retrieve the Selected Recipients

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();
    }
}

Private Implementation Code

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);
}

Abstracting an Email Message – the Message Class

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.

Call Implementation Code from Send Mail Method

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)
{
    // Retrieve the ids of the recipients selected:
    var selectedIds = recipients.getSelectedRecipientIds();
  
    // Grab the recipient records:
    var selectedMailRecipients = this.LoadRecipientsFromIds(selectedIds);
  
    // Build the message container for each:
    var messageContainers = this.createRecipientMailMessages(selectedMailRecipients);
  
    // Send the mail:
    var sender = new MailSender();
    var sent = sender.SendMail(messageContainers);
  
    // Save a record of each mail sent:
    this.SaveSentMail(sent);
  
    // Reload the index form:
    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.

The Mail Sender Class

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>();
  
        // Modify this to suit your business case:
        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;
                // Or, more likely, do some logging or something
            }
        }
        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;

Mail Client Configuration Settings

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.

Walking Through the Execution of the Send Mail Method

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.

Running the Project and Sending Mail

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:

try-sending-mail-before

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:

try-sending-mail-after

As we can see, the two items selected for sending email have now been updated with a Last Sent date.

What Went Wrong?

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.

Additional Resources and Items of Interest

ASP.Net
ASP.NET Identity 2.0: Extensible Template Projects
C#
Extending C# Listview with Collapsible Groups (Part I)
ASP.Net
ASP.NET MVC and Identity 2.0: Understanding the Basics
  • Soumik

    SoumikSoumik

    Author Reply

    Supposed I did pagination on that table that you created(Per page got 5 rows). First I selected 2 email from 1st page after move to next page from same table select another 3 email. So, can I send total 5 mail 2 from 1st page and 3 from second page(after move to 2nd page ,1st page rows still selected)?
    Example: Like google mail,I selected 3 of mail then I go to second page and also select another 3 mail. When I delete total 6 mail deleted..how to do that?


  • Arif

    ArifArif

    Author Reply

    Hi,
    I have a requirement where in a MVC application one user should be able to email another user and the data saved to the database. If its a read email the flag should be updated to read from unread. How do I do that, sorry I am new to VS.

    Thanks,
    Arif


  • T

    TT

    Author Reply

    Thank you so much,
    I was able to use your example to tailor it to my own use. Very useful.

    I greatly appreciate this article and your time.


  • T

    TT

    Author Reply

    Great post.

    This is exactly what I need.
    Thank yoU!


    • John Atten

      John AttenJohn Atten

      Author Reply

      Great! Thanks for taking a moment to comment! Cheers :-)