Adding Email

Summary

We are going to be sending reminders about birthdays by email, so it makes sense to set up the email functionality before moving on to the trickier problem of working out which birthdays are due.  In this post we will add the email function.

YouTube Video

Summary of Tasks

  • Install MailKit.
  • Create a Test page.
  • Add an EMailSender service.

Install MailKit

We will be using a NuGet package called MailKit to provide the email engine.  There are other Emailing packages, such as SendGrid, but MailKit seems to work well and has no obvious drawbacks.

In Visual Studio select

  • Tools > NuGet Package Manager > Manage NuGet Packages for Solution
  • Select Browse and enter 'MailKit'
  • Select Mailkit by Jeffrey Stedfast and install.

Create a Test Page

Before attempting to send emails automatically, we will test the basic email setup using a separate page, with hard-coded credentials.  

In the Pages folder add a new razor component called EmailTestPage and replace the default code with the following:

@page "/emailtest/"

@using MailKit.Net.Smtp;
@using MailKit.Security;
@using MimeKit;
@using MimeKit.Text;

<h3>Email Test Page</h3>

<hr />
<br/>
<div>
    <p class="alert-success">
        These are the settings for common email providers.  Substitute in the code as necessary.
         <br/>
    </p>
    <p>
        //GMail<br />
        smtp.Connect("smtp.gmail.com", 587, SecureSocketOptions.StartTls);
        <br/>
    </p>
    <p>
        //Outlook<br />
        smtp.Connect("smtp-mail.outlook.com", 587, SecureSocketOptions.StartTls);
        <br/>
    </p>
    <p>
        //Office 365<br />
        smtp.Connect("smtp.office365.com", 587, SecureSocketOptions.StartTls);
        <br/>
    </p>
    <p>
        //Generic<br />
        smtp.Connect("smtp.ServerNameOrDomainName", 587, SecureSocketOptions.StartTls);<br />
        or:<br />
        smtp.Connect("mail.ServerNameOrDomainName", 587, SecureSocketOptions.StartTls);
        <br/>
    </p>
</div>
<br/>
<hr />
<br/>
<div class="button-container">
    <button type="button" class="e-btn e-normal e-primary" @onclick="@SendEmail">Send Email</button>
</div>

@code {
    public MimeMessage email = new MimeMessage();

    public void SendEmail()
    {
        email.From.Add(MailboxAddress.Parse("FromEmailAddress"));
        email.To.Add(MailboxAddress.Parse("ToEmailAddress"));
        email.Subject = "Email Subject goes here";
        email.Body = new TextPart(TextFormat.Html) { Text = "This is the body of the email" };

        // send email
        using var smtp = new SmtpClient();        
        smtp.Connect("smtp-mail.outlook.com", 587, SecureSocketOptions.StartTls);
        smtp.Authenticate("FromUserName", "FromUserPassword");
        smtp.Send(email);
        smtp.Disconnect(true);
    }
}

This is the minimum code needed, and illustrates a form with a 'Send Email' button, using a Office365 account.

To add this to the menu, edit 'NavMenu.razor' in the Shared folder and add the following immediately after the NavLink to the 'Home' page:

        <div class="nav-item px-3">
            <NavLink class="nav-link" href="emailtest">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Email Test
            </NavLink>
        </div>

For common email providers substitute the smtp.Connect line (line 50 in the above) with one of the following:

//GMail
smtp.Connect("smtp.gmail.com", 587, SecureSocketOptions.StartTls);
//Outlook
smtp.Connect("smtp.live.com", 587, SecureSocketOptions.StartTls);
//Office 365
smtp.Connect("smtp.office365.com", 587, SecureSocketOptions.StartTls);
//Other
smtp.Connect("mail.yourDomain.com", 587, SecureSocketOptions.StartTls);

To test, substitute valid data for the placeholders in the above code, (FromEmailAddress, ToEmailAddress, FromUserName and FromUserPassword), run the application and select the 'Email Test' option from the menu. Click the 'Send Email'. Nothing will appear to happen, but an email should be received at the 'ToEmailAddress'.

GMail

If you are using a GMail account you will also need to set 'Less secure apps' to 'On'. (This is only available if you are not using 2-step verification.)  I do not recommend that you do this, but if you do want to, follow these steps: 

  • Log in to your GMail account
  • Click on your icon in the top right-hand corner of the screen
  • Select 'Manage your Google Account'
  • Select 'Security' from the left-hand menu
  • Navigate to the 'Less secure apps' page
  • Toggle to turn this feature 'ON'

Outlook.com

I have also discovered that some email providers will treat incoming emails from Outlook.com addresses as spam.  Outlook might therefore not be a suitable email provider for this application.

General

It seems to me that email is fraught with potential problems.  It would be wise to test your email options using the above simple hard-coded test data as a first step; test by sending from and to a variety of email providers if you can.  Be careful to ensure you type the credentials correctly!  Typos here don't help!

Setup an Email Service

We will eventually be adding the ability to send an email automatically, so it makes sense to create an email service that contains the bulk of the code that can be called from any number of different sources, passing the required parameters to the service. To create an email service we need to take the following steps:

  • We will need to record some information, such as email user name and password, however we don't want to 'hardcode' this type of data into the actual C# code - both from a security perspective (uploading the code to GitHub, for example) and also if user names and passwords are changed we don't need to edit and recompile the application. Instead we will save it in appsettings.json and read from that file when we need to. Add the following to appsettings.json, anywhere will do, but I suggest at the top, before "ConnectionStrings":
  "SmtpHost": "smtp.hostname.com",
  "SmtpPort": 587,
  "SmtpUserFriendlyName": "Your name here",  //E.g John Smith
  "SmtpUserEmailAddress": "SmtpUserEmailAddress",
  "SmtpPass": "EmailPassword",

Leave SmtpPort as '587', but you will need to substitute your own information for the other settings.

  • Open the Data folder and add a new class, call it 'EmailSender.cs'. Copy and paste the following code, overwriting the default code.
using MailKit.Net.Smtp;
using MailKit.Security;
using Microsoft.Extensions.Configuration;
using MimeKit;
using MimeKit.Text;
using System;


namespace BlazorBirthdayReminders.Data;

    public interface IEmailService
    {
        bool Send(string to, string subject, string body);
    }

    public class EmailService : IEmailService
    {
        private readonly IConfiguration configuration;

        public EmailService(IConfiguration Configuration)
        {
            configuration = Configuration;
        }

        public bool Send(string to, string subject, string body)
        {
            //Get settings from appsettings
            var SmtpHost = configuration["SmtpHost"];
            var SmtpPort = configuration["SmtpPort"];
            var SmtpUserFriendlyName = configuration["SmtpUserFriendlyName"];
            var SmtpUserEmailAddress = configuration["SmtpUserEmailAddress"];
            var SmtpPass = configuration["SmtpPass"];
            // create message
            var email = new MimeMessage();
            email.From.Add(new MailboxAddress(SmtpUserFriendlyName, SmtpUserEmailAddress));
            email.To.Add(new MailboxAddress(to, to));
            email.Subject = subject;
            email.Body = new TextPart(TextFormat.Html) { Text = body };

            try
            {
                // send email
                using var smtp = new SmtpClient();

                smtp.Connect(SmtpHost, Int32.Parse(SmtpPort), SecureSocketOptions.StartTls);
                smtp.Authenticate(SmtpUserEmailAddress, SmtpPass);
                smtp.Send(email);
                smtp.Disconnect(true);

                return true;
            }
            catch
            {
                return false;
            }

        }
    }


I won't try to explain the 'configuration' code, other than to say it gets the data from appsettings.json. The IEmailService interface and Send method are declared as a boolean (to indicate whether they succeeded or not) and have passed to them variables for the 'To' address, the email subject and the email body. The 'Send' method then collects the data from the configuration and creates the MailKit 'MimeMessage'. There is then an error-trapping 'try-catch' statement, the Try block attempts to send the email and returns 'true' if it succeeds; if the Send fails the code proceeds to the Catch block, where the return is set to false. We will use this true/false status later to notify the user of success or failure. In my experience failure is normally caused by a mistyped to or from email address, or password.

  • Because we are using a service we also need to edit 'Program.cs' and add the following line to the 'Builder' method.
builder.Services.AddScoped<IEmailService, EmailService>();

We will continue to modify our EmailTestPage to use the EmailService.  Insert the following in _Imports.razor (by placing it in _Imports.razor it will ensure it is available everywhere.)

@using BlazorBirthdayReminders.Data

And add the following to EmailTestPage.razor, under the @using statements:

@inject IEmailService EmailService

In EmailTestPage.razor, comment out all the code in the 'SendEmail' method and insert the following after the commented-out code:

        if (EmailService.Send("To Email Address", "Email Subject goes here", "This is the body of the email"))
        {
            //do something
        }
        else
        {
            //do something else
        };

Save and Test

If you substitute an actual email address for "To Email Address" and run the application and click the 'Send Email' button it should send an email.  As we haven't got anything in the 'do something' or 'do something else' sections nothing will appear to have happened.  We will use this later to notify the user of success or failure of manually sending birthday reminders.

References

Code changes for this article : Adding Email

Jason Watmote blog post using .Net 6.0