Using Mailgun in ASP.NET Core on Linux Mint 17

The Problem

How can I set up a simple ASP.NET Core web application (running on Linux Mint 17) that sends out email?  This is mostly so that the application can send out email on new account to confirm the user’s registration, and also to support password reset via email.

The Background

Some of you may know that I have hurt my left foot.  It is just so happened that .NET Core was released right around the same time.  To take a break from my usual routine and keep me distracted from my injury, I have decided to try something new: to learn C# and check out .NET Core on my Linux box (running Linux Mint 17.2 Rafaela / Ubuntu 14.04).

I started with the basic console application tutorial, then moved onto Web API, then Web Application.  The last one I tried was the tutorial on setting up email service with the web application.

I found parts of the email tutorial a bit tricky.  The reason being that not all email services out there support ASP.NET Core yet, and part of it being that plugging in credentials to their corresponding part in the API can be a bit confusing.

Because of that, I have decided to write up my own little tutorial so that later on, should I find the need to actually set up a web application with email service myself, I could follow these steps along and not having to figure these out all over again.

The Problem Breakdown

After much trial and error with several tutorials out there, and a bit of fiddling of my own, I have decided to use Mailgun as the external mail server.  You can check out Mailgun at https://mailgun.com/.  When I write this blog, google hasn’t updated its API to support .NET Core yet.  But I have structured the code so that if they decide to support .NET Core, I can easily put in corresponding code and try it out.

As far as API is concerned, I am going to use MailKit for handling SMTP requests and straight up System.Net.Http and System.Net.Http.Headers for sending via Mailgun HTTP REST interface.  You can find out more about MailKit at http://www.mimekit.net/.  System.Net.Http documentation is available at https://docs.microsoft.com/en-us/dotnet/core/api/system.net.http, and System.Net.Http.Headers documentation is available at https://docs.microsoft.com/en-us/dotnet/core/api/system.net.http.headers.

As I mentioned before, I am trying out C# and ASP.NET Core on my Linux box, so I am not using Visual Studio.  Most of the tutorials available on the web is on Visual Studio, so in order to for me follow them, I need some help to set up the basic framework.  To that end, I utilized Yeoman scaffolding.  You can find more about Yeoman scaffolding at http://yeoman.io/.  You can find out about installing .NET Core onto Linux on Microsoft .NET Core site (https://www.microsoft.com/net/core#ubuntu).

Once the scaffolding was set up, with a little bit of fiddling, I could follow along many of the existing tutorials out there.

So, for this tutorial, I will start with setting up the project with Yeoman scaffolding.  Then, I will follow the steps of some of the existing tutorials out there to set up a web application for user registration and password recovery.  I will save the best for the last.  The real fun starts at the last part with configuring the application with Mailgun and actually sending out email for user account registration and password recovery.

  1. Set up project via Yeoman scaffolding.
  2. Set up the web application for user registration.
  3. Enable email confirmation on account registration and password recovery.

 

The Solution

Set Up Yeoman Scaffolding for the Project

Set up a web application using yeoman scaffolding.  For simplicity’s sake, I am calling the directory “MailGun”:

 [512][vrw.myrna: /home/vrw] $ ## Setting up web application via Yeoman scaffolding  
 [513][vrw.myrna: /home/vrw] $ yo aspnet web MailGun bootstrap

The details on syntax for setting up ASP.NET Core project can be found at Yeoman scaffolding generator readme at GitHub (https://github.com/omnisharp/generator-aspnet#readme).

Now, go into the MailGun directory that yeoman scaffolding has set up:

 [514][vrw.myrna: /home/vrw] $ cd MailGun

You should see the following content:

 [515][vrw.myrna: /home/vrw] $ ls
 appsettings.json    Data/          project.json    Startup.cs
 bower.json          Dockerfile     Properties/     Views/
 bundleconfig.json   Models/        README.md       web.config
 Controllers/        Program.cs     Services/       wwwroot/

Before doing anything, it is a good idea to restore the project and make sure that everything compiles. So, run the following command to restore the packages for the project according to project.json.

 [516][vrw.myrna: /home/vrw] $ ## Restore the web application from its container  
 [517][vrw.myrna: /home/vrw] $ dotnet restore

Once it is done restoring, the directory may look something like below:

 [518][vrw.myrna: /home/vrw] $ ls
 appsettings.json     Data/          project.json      Services/     wwwroot/
 bower.json           Dockerfile     project.lock.json Startup.cs
 bundleconfig.json    Models/        Properties/       Views/
 Controllers/         Program.cs     README.md         web.config

Now, run the following command to make sure that the project compiles:

 [519][vrw.myrna: /home/vrw] $ dotnet build

Once the project successfully compiles, start up the web application by running the following command:

 [520][vrw.myrna: /home/vrw] $ dotnet run

Now, bring up a web browser.  Look at the console where the web application is running, you should see something similar to below:

 info: Microsoft.Extensions.DependencyInjection.DataProtectionServices[0]
       User profile is available. Using '/home/vrw/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest.
 Hosting environment: Production
 Content root path: /home/vrw/MailGun
 Now listening on: http://localhost:5000 
 Application started. Press Ctrl+C to shut down.

The highlighted entry will let you know the URL and port that the web application is running on.

Bring up a web browser and type in the URL as indicated by the “Now listening on:” line on the console. In this case, the URL would be http://localhost:5000.

When you hit enter, the web browser should bring up the sample C# web application.

Congratulations!      You now have successfully compiled a ASP.NET Core web application on Linux.

To exit the web application, simply hit Ctrl-C on the console and the web server will terminate.

Set Up Web Application for User Registration

From the section above, we already know that within the web application directory, MailGun, there are quite a few sub-directories. Setting up the web application for user registration would require editing files in different sub-directories under the MailGun folder. To make navigation easier, I have set up an environment variable that would allow me to quickly navigate myself back to the starting point:

 [537][vrw.myrna: /home/vrw] $ export WORKPAD="/home/vrw/MailGun"

(Be sure to substitue the highlighted portion, “home/vrw”, with your own path to the MailGun directory.)

Now, whenever I need to navigate back to the MailGun directory, I can simply type:

 [539][vrw.myrna: /home/vrw] $ cd $WORKPAD

Now that we have the scaffolding set up, we can follow the steps in the Microsoft tutorial to set the application up for user registration and password recovery.  The tutorial I am referring to is called Account Confirmation and Password Recovery.  It is accessible via https://docs.asp.net/en/latest/security/authentication/accconfirm.html.

If we follow the tutorial, we can set up the web application to create new account via user registration.  User information will be managed by the EntityFramework and stored in SQL Lite database.  While the EntityFramework abstracts away a lot of the nitty gritty details around user account management, in order for the whole scheme to work properly, tables related to user identity needs to be set up in SQLite first.

To do so, do the following:

 [545][vrw.myrna: /home/vrw] $ ## Set up the user identity tables in the SQL Lite database. 
 [546][vrw.myrna: /home/vrw] $ cd $WORKPAD; dotnet ef database update

Once the database is set up, start up the web application again:

 [547][vrw.myrna: /home/vrw] $ cd $WORKPAD; dotnet run

In a web browser, type in the URL for the web application.  When the web page comes up, click on the Register link on the upper-right-hand corner.

In the registration page that comes up, type in your email address, your password, and confirmation of your password, then hit Register.

When you hit Register and the application successfully registers you as a new user, you will be automatically logged in when the web page refreshes.  On the application home page, you should see your email address on the upper-right-hand corner.  In my case, since I have registered as ValleyWulf@example.com, I can see “Hello ValleyWulf@example.com!” in the upper-right-hand corner.

To confirm that the user “ValleyWulf@example.com” has indeed successfully registered with the web application, bring up SQLite Brwoser:

 [552][vrw.myrna: /home/vrw] $ sqlitebrowser&

(If you don’t have an SQLite Browser installed, check out the GitHub page for SQLite Browser.  It has instructions on how to get it installed on different OSes.  The SQLite Browser GitHub page is located at: https://github.com/sqlitebrowser/sqlitebrowser).

In the SQLite browser, click on the Open Database button under the top menu bar (or hit Ctrl-O).  In the window that pops up, navigate to your application directory, then select bin/Debug/netcoreapp1.0/MailGun.db and click Open.

Once the SQLite Browser loads the scheme from the MailGun.db, navigates to the Browse Data tab.

In the Table dropdown menu, select AspNetUsers.  At this point, you should be able to see an entry in the AspNetUsers table that correspond to the user you have just registered via the web browser.

At this point, since the web application does not have any validation on the user’s email address on registration, you can pretty much put any addresses in the form.  The web application will automatically accept the registration and logs you in.

This makes for a good case for having a email confirmation process on user registration.  This can prevent people from accidentally putting in the wrong email, or maliciously trying to pretend to be someone else.

So, this leads us to the fun part of the exercise…

Enable Email Confirmation on Account Registration and Password Recovery

The web application sample as provided by Yeoman scaffolding already contains the code needed for email confirmation on account registration as well as for password recovery. It is just a matter of uncommenting the corresponding code fragments, setting up the function to actually send out the email via a mail server based on certain credentials.

The steps involved can be summarized as:

  1. Enable the front end to support email sending,
  2. Set up Mailgun configuration as a service,
  3. Set up send mail function to access Mailgun.

Enable the Front End to Support Email Sending

To enable front end to allow email confirmation on user account registration and password recovery, the following areas need to be updated:

  1. The web link on the front end: in the web page (ForgotPassword.cshtml) that provides end user with the link to request password reset,
  2. The routes in the Account controller: in the function (or route) that handles user registration as well as in the function (route) that handles password recovery,
  3. Enable email sending service: the function that the controller calls to send out the email.

Should a user forgets his/herpassword, he/she can request for password reset via a form on the web site.  In the sample web application, this form is accessible via the ForgotPassword.cshtml.  By default, the segment of coded that provides the user with access to the link to request password recovery is commented out.

Instead of providing a form where user can put in his/her email address to request a password reset, the form is disabled, and the page looks something like below:

To enable password reset for the user, go to the Views/Account directory:

 [545][vrw.myrna: /home/vrw] $ ## Going to the "Views" portion of the MVC model. 
 [546][vrw.myrna: /home/vrw] $ cd $WORKPAD/Views/Account

Edit the file ForgotPassword.cshtml and uncomment the section under form asp-controller="Account":

@*<form asp-controller="Account" asp-action="ForgotPassword" method="post" class="form-horizontal">
      <h4>Enter your email.</h4>
      <hr />
      <div asp-validation-summary="All" class="text-danger"></div>
      <div class="form-group">
          <label asp-for="Email" class="col-md-2 control-label"></label>
          <div class="col-md-10">
              <input asp-for="Email" class="form-control" />
              <span asp-validation-for="Email" class="text-danger"></span>
          </div>
      </div>
      <div class="form-group">
          <div class="col-md-offset-2 col-md-10">
              <button type="submit" class="btn btn-default">Submit</button>
          </div>
      </div>
</form>*@

Just remove the leading @* and the trailing *@ and the segment of code would be uncomemented.  This should allow a user to access the web form to submit a request to have his/her password reset via email.

Right now, this is a pure front end change.  While the form is accessible to the end user and the user can interact with the form and click on the submit button, the submission will yet to produce any email action on the web server side.  (Outside of the code to send out email not being in place to begin with, the web applicaiton also checks against its database to see if the user is listed in the AspNetUsers table.  If it is, it also checks to see if the user’s email address has been confirmed, before calling the function that is responsible for sending out emails.)

To get the web application to process the submission, we need to enable the controller that handles the requests from the corresponding routes.

Enable Routes in the Account Controller

When a user requests his/her password reset from the web front end, the request corresponds to a route in the controller.  Similarly, when a user registers with the web application, the registration action corresponds to another route in the controller.  In this sample web application, both of these routes reside in the Account Controller.

To enable these routes, first go to the directory where the account controller is:

 [548][vrw.myrna: /home/vrw] $ ## Going to the "Controllers" portion of the MVC model. 
 [549][vrw.myrna: /home/vrw] $ cd $WORKPAD/Controllers

In the file, AccountController.cs, locate the functions that correspond to the route that allows new user to register with the application and the route that allow forgetful users to request password reset:

User Registration

The Register() function in the AccountController.cs file should have signature and routes similar to below:

 //
 // POST: /Account/Register
 [HttpPost]
 [AllowAnonymous]
 [ValidateAntiForgeryToken]
 public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)

Inside the function, locate the code block listed below:

//var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
//var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
//await _emailSender.SendEmailAsync(model.Email, "Confirm your account",
// $"Please confirm your account by clicking this link: <a href='{callbackUrl}'>link</a>");

and uncomment them.

In addition, to prevent user from being able to log in to the web application prior to having the user confirming his/her email, comment out the following line within the same function:

await _signInManager.SignInAsync(user, isPersistent: false);

After the edit, the function should look similar to below:

 //
 // POST: /Account/Register
 [HttpPost]
 [AllowAnonymous]
 [ValidateAntiForgeryToken]
 public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
 {
     ViewData["ReturnUrl"] = returnUrl;
     if (ModelState.IsValid)
     {
         var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
         var result = await _userManager.CreateAsync(user, model.Password);
         if (result.Succeeded)
         {
             // For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=532713
             // Send an email with this link
             var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
             var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
             await _emailSender.SendEmailAsync(model.Email, 
                 "Confirm your account",
                 $"Please confirm your account by clicking this link: <a href='{callbackUrl}'>link</a>");
             // await _signInManager.SignInAsync(user, isPersistent: false);
             _logger.LogInformation(3, "User created a new account with password.");
             return RedirectToLocal(returnUrl);
         }
         AddErrors(result);
     }

     // If we got this far, something failed, redisplay form
     return View(model);
 }

This will allow the Controller to send out a email confirmation via _emailSender.SendEmailAsync() function after the user successfully registers with the web application.

Password Recovery

Similarly, when user request password reset, it access a function called ForgotPassword() in the AccountController.cs.  The ForgotPassword() function in the AccountController.cs file should have signature and routes similar to below:

 //
 // POST: /Account/ForgotPassword
 [HttpPost]
 [AllowAnonymous]
 [ValidateAntiForgeryToken]
 public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)

Inside the function, locate the code block listed below:

 //var code = await _userManager.GeneratePasswordResetTokenAsync(user);
 //var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
 //await _emailSender.SendEmailAsync(model.Email, "Reset Password",
 // $"Please reset your password by clicking here: <a href='{callbackUrl}'>link</a>");
 //return View("ForgotPasswordConfirmation");

and uncomment them.

After the edit, the function should look similar to below:

 //
 // POST: /Account/ForgotPassword
 [HttpPost]
 [AllowAnonymous]
 [ValidateAntiForgeryToken]
 public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
 {
     if (ModelState.IsValid)
     {
         var user = await _userManager.FindByNameAsync(model.Email);
         if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
         {
             // Don't reveal that the user does not exist or is not confirmed
             return View("ForgotPasswordConfirmation");
         }

         // For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=532713
         // Send an email with this link
         var code = await _userManager.GeneratePasswordResetTokenAsync(user);
         var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
         await _emailSender.SendEmailAsync(model.Email, 
             "Reset Password",
             $"Please reset your password by clicking here: <a href='{callbackUrl}'>link</a>");

         return View("ForgotPasswordConfirmation");
     }
     // If we got this far, something failed, redisplay form
     return View(model);
  }

This will allow the Controller to send out a email confirmation via _emailSender.SendEmailAsync() function after the user requested password reset via the web application.

At this point, if you go ahead and compile the codes, the project should compile.

When you run the web application, other than the fact that you can access the web form for requesting password reset, there would be no other observable changes from either the front end or from the console.  This is because the function _emailSender.SendEmailAsync() that both Register() and ForgotPassword() rely on does nothing at the moment.

So, to make sure that we did connect the front end all the way to the function that sends out email, we should locate the email sending function _emailSender.SendEmailAsync() and have it produce a simple observeable behaviour.

Enable Email Sending Service

The email sending function SendEmailAsync() is part of the EmailSender service.  Before we enable the function to send out actual email, we want to make sure that the “plumbing” is set up properly, and the function gets called from the front end.  To do so, go to the Services directory:

 [562][vrw.myrna: /home/vrw] $ ## Going to the Services directory. 
 [563][vrw.myrna: /home/vrw] $ cd $WORKPAD/Services

In the directory, look for the file MessageServices.cs. In MessageServices.cs, look for the function SendEmailAsync():

 public Task SendEmailAsync(string email, string subject, string message)

Save for a return statement, the function should be empty at the moment. Add the following to before the return statement:

Console.WriteLine("Calling function SendEmailAsync.");

So, the function may look something similar to below once you are done editing:

 public Task SendEmailAsync(string email, string subject, string message)
 {
     // Plug in your email service here to send an email.
     Console.WriteLine("Calling function SendEmailAsync.");
     return Task.FromResult(0);
 }

Once this is done, compile the code:

 [565][vrw.myrna: /home/vrw] $ ## Compile the project 
 [566][vrw.myrna: /home/vrw] $ cd $WORKPAD
 [567][vrw.myrna: /home/vrw] $ dotnet build

After the project successfully compiled, run the server:

 [568][vrw.myrna: /home/vrw] $ ## Start up the web application
 [569][vrw.myrna: /home/vrw] $ dotnet run

Now, in a web browser, type in the URL for the web application – in my case, it would be http://localhost:5000.

On the web page that comes up, click on Register on the upper-right-hand corner.

When the registration page comes up, type in an email address, a password, and the password confirmation.  Since we have yet to hook up the registration with a email server yet, we don’t have to worry about provide a valid, active email address at this time.

When you hit Register, look at the console where your web application is running.  Among other log entries, you should see the following:

 info: Microsoft.Extensions.DependencyInjection.DataProtectionServices[0]
       User profile is available. Using '/home/vrw/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest.
 Hosting environment: Production
 Content root path: /home/vrw/MailGun
 Now listening on: http://localhost:5000
 Application started. Press Ctrl+C to shut down.
 ... 
 Calling function SendEmailAsync. 
 ...

In addition, when the web page refreshes, you should no longer be automatically logged in as the user you just registered with the web application.

These are the expected behavior.

The console output shows that the changes we have made in the front end, the controller, successfully made it to the function that will eventually be responsible for sending out email.

Also, recall that we had previously commented out a line of code in the Controller that allows the _signInManager to automatically sign in a newly registered user. By commenting out that line, the web application no longer automatically signs a user in. (Eventually, the user would have to click on a registration confirmation link in his/her email before he/she can sign in to the application.)

We can conclude that we have made sure that the overall plumbings are in working order, so to speak.  At this point, we can just as easily put in the email credentials here in the SendEmailAsync() function and replace the Console.WriteLine() with code snippets that would allow the web application to connect to an external mail server to send out the registration confirmation email or the password reset email.

On the other hand, like I have pointed out at the beginning of this post, that this is my first time using C# and ASP.NET Core.  Because of that, I would really like to take this opportunity and set something flexible up so that I could re-use later on.  This way, later on, if there is a new mail api comes out (for instance, if Google decides to update its api to support .NET Core), or if there is a new email service becomes available, I would like to be able to quickly adjust the code I have here and try things out.

As a result of that, I opted for the set up that Eric L. Anderson has in his blog: Emails using Mailgun in ASP.NET Core (http://www.elanderson.net/2016/02/emails-using-mailgun-in-asp-net-core/).

Now, with that in mind, let’s move onto setting up Mailgun configuration as a service so that the EmailSender can access it via dependency injection for sending out registration confirmation emails and password reset emails.

Set Up Mailgun Configuration as a Service

To set up Mailgun configuration as a service requires the following 3 items:

  1. A Mailgun account so that we can have the configuration settings to send email out via Mailgun,
  2. Creation of a class that takes advantage of the Option pattern for the Mailgun configuration,
  3. Set up a service that provides Mailgun email settings via depednency injection to dependent classes.

Mailgun Configurations for Sending Email

Before we go further, you need to have an account set up with Mailgun.  If not, go to https://mailgun.com and set up an account.  At the time when this blog is written, Mailgun allows free first 10,000 emails on a monthly basis.  So, it is worth trying it out.

Once you have your Mailgun account set up, log into your Mailgun account and click on the Domains tab in your Mailgun home page.  Select one of your active sites.  Here, I  simply picked the Mailgun sandbox domain.


In the page that comes up, it shows all the relevant information for configuring the routing of your outbound email through MailGun Rest API or its SMTP server.


Let’s jot these configurations down so that our web application knows how to talk to Mailgun when it sends out the emails.

There are many ways to do that.   You can put them in your code directly (remember the SendMailAsync() function that we had added the Console.WriteLine()?), or put them in appsetting.json, or put them in your user secrets.  I like to put all my configurations all together in one place.  So, I am going to create a separate directory called Config.  This will be where I would put most of these configurations.  To do so:

 [578][vrw.myrna: /home/vrw] $ ## Create a separate Config directory for configurations.
 [579][vrw.myrna: /home/vrw] $ cd $WORKPAD
 [580][vrw.myrna: /home/vrw] $ mkdir Config
 [581][vrw.myrna: /home/vrw] $ cd Config

In the Config directory, create a new file called MailGunEmailSettings.json. The file would look something similar to below:

{

 "SMTPSettings": 
 {
   "SmtpHost": "smtp.mailgun.org",
   "SmtpPort": "587",
   "SmtpLogin": "postmaster@sandbox888AStringOfAlphanumerics888abc.mailgun.org",
   "SmtpPassword": "888Alphanumerics4SandboxPassword",
   "SenderName": "Postmaster",
   "From": "postmaster@sandbox888AStringOfAlphanumerics888abc.mailgun.org"
 },

 "RestAPISettings": 
 {
   "ApiKey": "key-22SandboxApiKeyInAlphanumerics22",
   "BaseUri": "https://api.mailgun.net/v3/sandbox888AStringOfAlphanumerics888abc.mailgun.org/",
   "RequestUri": "messages",
   "From": "Non-Reply <postmaster@sandbox888AStringOfAlphanumerics888abc.mailgun.org>"
 }

}

The first section, SMTPSettings contains the configuration for routing outbound email via Mailgun using SMTP. The second section, RestAPISettings contains the configuration for routing outbound email via Mailgun Rest API.

Let’s take a look at the configuration section one by one.

Mailgun SMTP Settings

Under the SMTPSettingssection in MailGunEmailSettings.json, the SmtpHost option corresponds to the SMTP Hostname field in Mailgun Domains page.

The SmtpLogin option in the JSON file corresponds to the Default SMTP Login in  Mailgun Domains page.

The SmtpPassword option in the file corresponds to the Default Password in Mailgun Domains page.

The SenderName option in the file can be any text string. It is a field that provides the sender name in the “From” field in the email. Out of convenience, I put “Postmaster” here.

The From option in the file can be any valid email address. It corresponds to the “From” field in an email and unless otherwise specified, it is also the “Reply-To” field in the email. Out of convenience, I put the same email address as the default Mailgun SMTP login.

The SmtpPort option in the file is the only field I haven’t mentioned yet. Its value isn’t listed in Mailgun Domain Information. It is actually from Mailgun Quickstart Guide on Sending Email. The port is listed under the sample code in the section for Send via SMTP.

While Port 587 is a commonly used port for SMTP, there is no guarantee that a mail server would always configure its SMTP to listen on port 587 for traffic.  Because of that, it is usually best to look up a mail server’s documentation to get a clear indication on which port its SMTP is listening on.

The Rest API Settings are actually a bit easier to navigate, if you know what you are looking for.

Mailgun REST API Settings

Under the RestAPISettingssection in MailGunEmailSettings.json, the ApiKey option corresponds to the API Key field in Mailgun Domains page.

The BaseUri option in the JSON file corresponds to the API Base URL in Mailgun Domains page.  (Don’t forget the trailing / in the base URI.  Without it, you may get an 404 error when you attempt to send mail via Mailgun REST API.)

The RequestUri option in the JSON file is a bit trickier.  It is not explicitly listed under the Mailgun Domain Information.  To look it up, look under Mailgun Quickstart Guide on Sending Email, coding sample preference set to curl.  Under section Send via API, you will see the following code sample:

The request URI is what comes after the base URI.  Comparing the base URI in the sample code with the API Base URL in  Mailgun Domains page, we can tell that the request URI is messages, in this case.

The From option in the file can be any valid email address. It corresponds to the “From” field in an email and unless otherwise specified, it is also the “Reply-To” field in the email.  Here, I just use the same default SMTP login as the email address for the field.

Now that we have the Mailgun configurations stored as a JSON file locally on the web application server, we can begin create a configuration class that corresponds to this set of configurations.

Mailgun Configuration Class Creation

In .NET Core, there is an options pattern that allows custom options classes to represent a group of related settings.  A class only needs to have a public read-write property for each setting (or option) and a constructor that takes no argument (for instance, the default constructor) to be an option class.  (For more details, please see Using Options and configuration objects.)

To take advantage of what I termed the “.NET Core Magic”, I am going to set up the corresponding classes for Mailgun SMTP settings and its REST API configurations.

To do so, I first go to the Services directory:

 [599][vrw.myrna: /home/vrw] $ ## Set up configuration classes in Services
 [600][vrw.myrna: /home/vrw] $ cd $WORKPAD/Services

In the Services directory, create a file called MailGunEmailSettings.cs.  The file will look something similar to below:

namespace MailGun.Services
{

    public class MailGunSmtpEmailSettings
    {

        public string SmtpHost { get; set; }

        public int SmtpPort { get; set; }

        public string SmtpLogin { get; set; }

        public string SmtpPassword { get; set; }

        public string SenderName { get; set; }

        public string From { get; set; }

    }

    public class MailGunApiEmailSettings
    {

        public string ApiKey { get; set; }

        public string BaseUri { get; set; }

        public string RequestUri { get; set; }

        public string From { get; set; }

    }

}

Even though the application will produce no visible changes in its behavior at this point, It might not be a bad idea to compile the project anyway.  This is just to make sure that we any typos or missing libraries before moving onto the next section.

Mailgun Configuration Service Set Up

Like I have pointed out before, since I am still feeling my way around C# and .NET Core, I would like to re-use the code later to try out other email services or other email API.  To facilitate that, I have decided to have appsettings.json, the default configuration file for the web application, to specify which email service and which configuration I am going to use.

Below is what I have added the $WORKPAD/appsettings.json file:

  "EmailProvider": {
    "Provider": "MailGun",
    "Description": "Mail service that sends out registration confirmation and account recovery emails.",
    "ConfigFile": "Config/MailGunEmailSettings.json",
    "ConnectionType": "api"
  },

Now, when Startup loads in appsettings.json, the configuration will contain a section called EmailProvider. The configuration in the EmailProvider section will provide Startup the flow control it needs to determine which configuration (smtp vs rest or mailgun vs gmail) and which Email Sender to use.

So, in the Startup.cs file, under function ConfigureServices(), add in the highlighted portion from below:

 public void ConfigureServices(IServiceCollection services)
 {
     // Add framework services.
     services.AddDbContext<ApplicationDbContext>(options =>
         options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));

     services.AddIdentity<ApplicationUser, IdentityRole>()
         .AddEntityFrameworkStores<ApplicationDbContext>()
         .AddDefaultTokenProviders();

     services.AddMvc();

     // Add application services.
     services.AddTransient<IEmailSender, AuthMessageSender>();
     services.AddTransient<ISmsSender, AuthMessageSender>();

     // Set up mechanism for sending email.
     IConfigurationSection sectionEmailProvider = 
         Configuration.GetSection("EmailProvider");

     string emailProvider = sectionEmailProvider["Provider"];

     string emailConnectionType = sectionEmailProvider["ConnectionType"];

     if (null == emailProvider)
     {

         throw new ArgumentNullException(
             "EmailProvider",
             "Missing email service provider configuration"
         );

     }

     IConfiguration emailConfig = LoadEmailConfig();

     switch (emailProvider.ToLower())
     {

         case "mailgun":

             Console.WriteLine(
                 "Starting send mail via MailGun email service...");

             if ("api" == emailConnectionType.ToLower())
             {

                 services.Configure<MailGunApiEmailSettings>(
                     emailConfig);

             }

             if ("smtp" == emailConnectionType.ToLower())
             {

                 services.Configure<MailGunSmtpEmailSettings>(
                     emailConfig);

             }

             break;

         case "gmail":

             Console.WriteLine(
                 "[Placeholder] Starting send mail via Google email service...");

             break;

         default:

             Console.WriteLine("Error: Unknown email service.");

             throw new ArgumentException(
                 "Unknown email service provider in configuration.",
                 "EmailProvider:Provider"
             );

             // break;

         }

 }

The highlight portion of the code essentially does the following:

  • Extract the EmailProvider section of the appsettings.json , into the variable sectionEmailProvider.
  • Load the specific email service provider configurations.
  • Depending on the Provider and the ConnectionType settings, set up the configuration as a service for other components to use via dependency injection.

The part of the code that load the configuation from specific email service provider is the function LoadEmailConfig().

The function LoadEmailConfig() looks something like below:

 protected IConfiguration LoadEmailConfig()
 {

     IConfigurationSection sectionEmailProvider = 
         Configuration.GetSection("EmailProvider");

     string emailProvider = sectionEmailProvider["Provider"];

     if (null == emailProvider)
     {

         throw new ArgumentException(
             "Empty email provider.",
                 "EmailProvider:Provider"
         );

     }

     IConfiguration emailConfig = null;

     switch (emailProvider.ToLower())
     {

         case "mailgun":

             Console.WriteLine("Loading MailGun configuration...");

             emailConfig = LoadMailGunEmailConfig();

             break;

         case "gmail":

             // Place holder for gmail configuration. 
             break;

         default:

             throw new ArgumentException(
                 "Unknown email provider.",
                 "EmailProvider:Provider"
             );

             // break;

     }

     return emailConfig;

 }

Depending on the email service provider, setting Provider specified under EmailProvider section of appsettings.json, function LoadEmailConfig() loads in the configuration for the provider.  If for some reason, the email provider specified does not match any recognizable email provider in the function, LoadEmailConfig() throws an exception.

In other words, LoadEmailConfig() simply does the following:

  • Extract the configuration section labeled EmailProvider under from the appsettings.json that was loaded into the class property Configuration.
  • Based on the email provider the configuration specifies, load the respective configuration by calling the corresponding loading function.

The configurations for each of the service provider could reside in a flat file, like the JSON file we have here, or in the database, or be hard coded within individual functions themselves, or in user secrets. By having LoadEmailConfig() loads in based on email provider will allow me to also try out different ways to load in configurations depending on the email service provider.

So, to load Mailgun’s configurations, LoadEmailConfig() calls LoadMailGunEmailConfig().  The LoadMailGunEmailConfig() function looks like the following:

 protected IConfiguration LoadMailGunEmailConfig()
 {

     IConfigurationSection sectionEmailProvider = 
         Configuration.GetSection("EmailProvider");

     string emailConnectionType = sectionEmailProvider["ConnectionType"];

     IConfiguration emailConfig = null;

     if (null == emailConnectionType)
     {

         throw new ArgumentException(
             "Invalid email connection type.",
             "EmailProvider:ConnectionType"
         );

     }

     switch (emailConnectionType.ToLower())
     {

         case "api":

             emailConfig = LoadMailGunApiEmailSettings();

             break;

         case "smtp":

             emailConfig = LoadMailGunSmtpEmailSettings();

             break;

         default:

             throw new ArgumentException(
                 "Unknown email connection type.",
                 "EmailProvider:ConnectionType"
             );

             // break;

     }

     return emailConfig;

 }

Mailgun has two ways that the web application can connect to: one via its SMTP, the other REST API.  To support this, we are going to create two separate functions – one for each connection mechanism, for LoadMailGunEmailConfig() to load in Mailgun’s configuration.

For connecting via SMTP, LoadMailGunEmailConfig() will call function LoadMailGunSmtpEmailSettings(). For connecting via REST API, LoadMailGunEmailConfig() will call function LoadMailGunApiEmailSettings().

Function LoadMailGunSmtpEmailSettings() will look similar to below:

 protected IConfiguration LoadMailGunSmtpEmailSettings()
 {

     IConfigurationSection sectionEmailProvider = 
         Configuration.GetSection("EmailProvider");

     string configFile = sectionEmailProvider["ConfigFile"];

     var builder = new ConfigurationBuilder()
         .SetBasePath(Directory.GetCurrentDirectory())
         .AddJsonFile(configFile, optional: false, reloadOnChange: true)
         .AddEnvironmentVariables();

     IConfiguration config = builder.Build();

     IConfigurationSection sectionSmtp = 
         config.GetSection("SMTPSettings");

     return sectionSmtp;

 }

The function gets the settings from the EmailProvider section of the appsettings.json that was previously loaded into the class property Configuration.

Based on the file specified in the setting option ConfigFile,

     string configFile = sectionEmailProvider["ConfigFile"];

the function loads the settings as specified by the file. The function assumes that the configuration file specified has a path relative to the working directory of the web application:

         .SetBasePath(Directory.GetCurrentDirectory())

In order for the compiler to find Directory, you may need to add in the following at the beginning part of Startup.cs:

using System.IO;

In addition, the function considers the file specified not being an optional file:

         .AddJsonFile(configFile, optional: false, reloadOnChange: true)

Should the file specified is missing for any reason, the function will throw an exception.

The function does provide the option of allow the caller to override the specified parameters for EmailProvider with environment variable:

         .AddEnvironmentVariables();

Once everything is loaded, the function build the configuration based on the settings as dictated by the file as specified by ConfigFile setting in the EmailProvider section of appsettings.json.

     var builder = new ConfigurationBuilder()
         .SetBasePath(Directory.GetCurrentDirectory())
         .AddJsonFile(configFile, optional: false, reloadOnChange: true)
         .AddEnvironmentVariables();

     IConfiguration config = builder.Build();

Now, remember that in our file Config/MailGunEmailSettings.json, it contains configurations for both SMTP settings, “SMTPSettings”, and Rest API, “RestAPISettings”. So, in our function, we need to differentiate which section we want to load in:

     IConfigurationSection sectionSmtp = 
         config.GetSection("SMTPSettings");

As indicated by the function name, the function LoadGmailSmtpEmailSettings() is for loading SMTP settings.  As such, the function is only loading in the section for SMTPSettings and return that part of the configuration to the caller.

The definition of LoadMailGunApiEmailSettings() is smiliar to the definition LoadGmailSmtpEmailSettings() show above:

 protected IConfiguration LoadMailGunApiEmailSettings()
 {

     IConfigurationSection sectionEmailProvider = 
         Configuration.GetSection("EmailProvider");

     string configFile = sectionEmailProvider["ConfigFile"];

     var builder = new ConfigurationBuilder()
         .SetBasePath(Directory.GetCurrentDirectory())
         .AddJsonFile(configFile, optional: false, reloadOnChange: true)
         .AddEnvironmentVariables();

     IConfiguration config = builder.Build();

     IConfigurationSection sectionRestApi = 
         config.GetSection("RestAPISettings");

     return sectionRestApi;

 }

The only difference is the loading of loading the RestAPISettings section instead of the SMTPSettings section.

Now, both Mailgun configurations, SMTP and REST API, should be available via dependency injection as IOptions<MailGunSmtpEmailSettings> and as IOptions<MailGunApiEmailSettings>.

This can be verified by introducing these two options via the constructor in the AuthMessageSender class and update the Console.WriteLine() in the SendEmailAsync() function to reflect the configuration settings for each of the configuration settings.

Since I plan to re-use most of the codes to test out new email APIs and new email services, I prefer to have separate distinct class to handle each email connection.  So, I am going to set up separate message sender classes for the two connection configurations.

Mailgun Email Sender Set Up

Since we have two configurations for Mailgun, one for SMTP and one for REST API, I am going to set up two separate email senders to handle sending emails via Mailgun.

SMTP Mailgun Email Sender

To send email via SMTP, we are going to make use of a third party .NET library called MailKit.  MailKit is available via NuGet (https://www.nuget.org/packages/MailKit/).

Before using MailKit, we need to add the package to the project via project.json. To edit project.json, go to the base path of the current project:

 [612][vrw.myrna: /home/vrw] $ ## Return to base path of the current project
 [613][vrw.myrna: /home/vrw] $ cd $WORKPAD

Locate the project.json file, under the dependencies section, add in the MailKit like the highlighted portion below:

...
  "dependencies": {
    "Microsoft.NETCore.App": {
      "version": "1.0.0",
      "type": "platform"
    },
    "Microsoft.AspNetCore.Authentication.Cookies": "1.0.0",
...
    "MailKit": "1.4.0"
  },

  "tools": {
...

After the project.json has been updated, update the libraries for the project by:

 [614][vrw.myrna: /home/vrw] $ ## Update project libraries
 [615][vrw.myrna: /home/vrw] $ dotnet restore

 

Once the project has been restored, MailKit API should be available for use.

Now, go to the Services directory:

 [616][vrw.myrna: /home/vrw] $ ## Switching working directory to Services
 [617][vrw.myrna: /home/vrw] $ cd $WORKPAD/Services

and create a new file, called MailGunSmtpEmailSender.cs:

using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using System.Security.Authentication;
using MailKit.Net.Smtp;
using MimeKit;

namespace MailGun.Services
{

     public class MailGunSmtpEmailSender: IEmailSender
     {
     
         //
         // Identify the connection type to the MailGun server
         //
         public const string ConnectionType = "smtp";

         // 
         // Identify the external mail service provider
         // 
         public const string Provider = "MailGun";

         // 
         // Configuration setting for routing email through MailGun SMTP.
         //
         private readonly MailGunSmtpEmailSettings _smtpEmailConfig;

         //
         // Accessor for the configuration to send mail via MailGun SMTP
         // 
         public MailGunSmtpEmailSettings EmailSettings
         {

             get
             {

                 return _smtpEmailConfig;

             }

         }

         // 
         // Default constructor with email configuration initialized via
         // IOption.
         // 
         public MailGunSmtpEmailSender(
             IOptions<MailGunSmtpEmailSettings> smtpEmailConfig
         )
         {

             _smtpEmailConfig = smtpEmailConfig.Value;

         }

         // 
         // Sends email via MailGun SMTP, given email recipient, email
         // subject, and email body.
         // 
         public async Task SendEmailAsync(
             string email,
             string subject,
             string message
         )
         {

             string host = EmailSettings.SmtpHost;

             int port = EmailSettings.SmtpPort;

             string login = EmailSettings.SmtpLogin;

             string code = EmailSettings.SmtpPassword;

             string senderName = EmailSettings.SenderName;

             string sender = EmailSettings.From;

             var emailMsg = new MimeMessage();

             emailMsg.From.Add(new MailboxAddress(senderName, sender));

             emailMsg.To.Add(new MailboxAddress("", email));

             emailMsg.Subject = subject;

             emailMsg.Body = new TextPart("plain") { Text = message };

             using (SmtpClient client = new SmtpClient()) 
             {

                 try 
                 {

                     await client.ConnectAsync(host, port, false)
                         .ConfigureAwait(false);

                     client.AuthenticationMechanisms.Remove ("XOAUTH2");

                     await client.AuthenticateAsync(login, code);

                     await client.SendAsync(emailMsg).ConfigureAwait(false);

                 }
                 catch (AuthenticationException ex)
                 {
                 
                     Console.WriteLine("Error: Authentication exception.");

                     Console.WriteLine("\tException message: {0}", ex.Message);

                     throw ex;

                 }
                 catch (SmtpCommandException ex)
                 {

                     Console.WriteLine("Error: SMTP exception.");

                     Console.WriteLine("\tException message: {0}", ex.Message);

                     Console.WriteLine ("\tStatus code: {0}", ex.StatusCode);

                     switch (ex.ErrorCode) 
                     {

                         case SmtpErrorCode.RecipientNotAccepted:

                             Console.WriteLine(
                                 "Error: Recipient not accepted: {0}", 
                                 ex.Mailbox
                             );

                             break;

                         case SmtpErrorCode.SenderNotAccepted:

                             Console.WriteLine(
                                 "Error: Sender not accepted: {0}", 
                                 ex.Mailbox
                             );

                             break;

                         case SmtpErrorCode.MessageNotAccepted:

                             Console.WriteLine("Error: Message not accepted.");

                             break;

                         default:
                         
                             Console.WriteLine("Error: {0}.", ex.Message);

                             break;

                     }

                     throw ex;

                 }
                 catch (SmtpProtocolException ex)
                 {

                     Console.WriteLine("Error: SMTP protocol exception.");

                     Console.WriteLine("\tException message: {0}", ex.Message);
 
                     throw ex;

                 }
                 catch (Exception ex)
                 {

                     Console.WriteLine("Error: Failed to send mail via {0} {1}.",
                         Provider,
                         ConnectionType
                     );

                     throw ex;

                 }
                 finally
                 {

                     if (true == client.IsConnected)
                     {

                         await client.DisconnectAsync(true)
                             .ConfigureAwait(false);

                     }

                 }


                 if (true == client.IsConnected)
                 {

                     await client.DisconnectAsync(true).ConfigureAwait(false);

                 }

             }

         }

     }

}

The class has a constant member variable called ConnectionType.  This is for identifying the connection type that an instance of this class corresponds to.

It also has a constant member variable called Provider.  This is for identifying the email service provider.

It has a private member variable called _smtpEmailConfig.  This is used to hold the configuration settings once the constructor obtains the settings via dependency injection.  The property, EmailSettings, allows caller convenient access to the configuration setting stored in _smtpEmailConfig.

The constructor MailGunSmtpEmailSender() takes in an argument, IOptions<MailGunSmtpEmailSettings>.  This allows an instance of the class to obtain the configuration settings via dependency injection.  Remember that MailGunSmtpEmailSettings is the class that we have created earlier. It complies with the Option pattern so that it can be used to capture the configuration settings for MailGun’s SMTP settings in the file $WORKPAD/Config/MailGunEmailSettings.json.

The function SendEmailAsync() is where the actual sending of email takes place.

Because all of the parameters for routing via SMTP has already been loaded into the private member variable _smtpEmailConfig, it is now simple to retrieve the parameters in the function.

The function SendEmailAsync() makes use of the MailKit and the parameters to send email via Mailgun to the designated recipient.  The MailKit API documentation is available at http://www.mimekit.net/docs/html/R_Project_Documentation.htm.

In order for MailKit to initiate an SMTP connection, it would require the SMTP hostname, the SMTP port:

             string host = EmailSettings.SmtpHost;
             int port = EmailSettings.SmtpPort;

It would require the SMTP login and password for authentication.

             string login = EmailSettings.SmtpLogin;
             string code = EmailSettings.SmtpPassword;

For the purpose our web application, it would need to pass along meaningful content to the designated recipient:

             string senderName = EmailSettings.SenderName;
             string sender = EmailSettings.From;
             var emailMsg = new MimeMessage();
			 
             emailMsg.From.Add(new MailboxAddress(senderName, sender));
             emailMsg.To.Add(new MailboxAddress("", email));
             emailMsg.Subject = subject;
             emailMsg.Body = new TextPart("plain") { Text = message };

Once the details are in place, it is just a matter of utilizing the MailKit API to connect, authenticate, and send the email message:

                     await client.ConnectAsync(host, port, false)
                         .ConfigureAwait(false);
                     client.AuthenticationMechanisms.Remove ("XOAUTH2");
                     await client.AuthenticateAsync(login, code);
                     await client.SendAsync(emailMsg).ConfigureAwait(false);

The rest is just error and exception handling.

REST API MAILGUN EMAIL SENDER

To send email via HTTP REST API, we are going to make use of the System.Net.Http, and System.Net.Http.Headers libraries.

Since these should already be part of project, there is no need to edit project.json.  I just need to make sure that these libraries are included in the MailGunApiEmailSender.cs file.

using System;
using System.Text;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using System.Net.Http;
using System.Net.Http.Headers;

namespace MailGun.Services
{

     public class MailGunApiEmailSender: IEmailSender
     {

         // 
         // Identify the connection type to the MailGun server
         // 
         public const string ConnectionType = "api";

         // 
         // Identify the external mail service provider.
         // 
         public const string Provider = "MailGun";

         // 
         // Configuration setting for routing email through MailGun API.
         // 
         private readonly MailGunApiEmailSettings _apiEmailConfig;

         // 
         // Accessor for the configuration to send mail via MailGun API.
         // 
         public MailGunApiEmailSettings EmailSettings
         {

             get 
             {

                 return _apiEmailConfig;

             }

         }

         // 
         // Default constructor with email configuration initialized via
         // option configuration.
         // 
         public MailGunApiEmailSender(
             IOptions<MailGunApiEmailSettings> apiEmailConfig)
         {

             _apiEmailConfig = apiEmailConfig.Value;

         }

         //
         // Sends email via MailGun REST api, given email recipient, email
         // subject, and email body.
         // 
         public async Task SendEmailAsync(
             string email, 
             string subject, 
             string message
         ) 
         {

             string apiKey = EmailSettings.ApiKey;

             string baseUri = EmailSettings.BaseUri;

             string requestUri = EmailSettings.RequestUri;

             string token = HttpBasicAuthHeader("api", apiKey);

             FormUrlEncodedContent emailContent = HttpContent(email,
                 subject,
                 message
             );

             using (var client = new HttpClient())
             {

                 client.BaseAddress = new Uri(baseUri);

                 client.DefaultRequestHeaders.Authorization = 
                     new AuthenticationHeaderValue("Basic", token);

                 HttpResponseMessage response = await client
                     .PostAsync(requestUri, emailContent)
                     .ConfigureAwait(false);

                 if (false == response.IsSuccessStatusCode)
                 {

                     string errMsg = String.Format(
                         "Failed to send mail to {0} via {1} {2} due to\n{3}.",
                         email,
                         Provider,
                         ConnectionType,
                         response.ToString()
                     );

                     throw new HttpRequestException(errMsg);

                 }

             }

         }

         // 
         // Put together the basic authentication header for MailGun 
         // Rest API (see https://documentation.mailgun.com/quickstart-sending.html#send-via-api).
         // 
         protected string HttpBasicAuthHeader(
             string tokenName, 
             string tokenValue
         )
         {

             string tokenString = string.Format(
                 "{0}:{1}",
                 tokenName,
                 tokenValue
             );

             byte[] bytes = Encoding.UTF8.GetBytes(tokenString);

             string authHeader = Convert.ToBase64String(bytes);

             return authHeader;

         }

         // 
         // Put together the core elements for the email for MailGun.
         // 
         // For a list of valid fields, please refer to MailGun document 
         // (see https://documentation.mailgun.com/api-sending.html#sending).
         //
         protected FormUrlEncodedContent HttpContent(
             string recipient,
             string subject,
             string message
         )
         {

             string sender = EmailSettings.From;

             var emailSender = new KeyValuePair<string, string>("from", sender);

             var emailRecipient = 
                 new KeyValuePair<string, string>("to", recipient);

             var emailSubject = 
                 new KeyValuePair<string, string>("subject", subject);

             var emailBody = 
                 new KeyValuePair<string, string>("text", message);

             List<KeyValuePair<string, string>> content = 
                 new List<KeyValuePair<string, string>>();

             content.Add(emailSender);

             content.Add(emailRecipient);

             content.Add(emailSubject);

             content.Add(emailBody);

             FormUrlEncodedContent urlEncodedContent = 
                 new FormUrlEncodedContent(content);

             return urlEncodedContent;

         }

     }

}

For the most part, MailGunApiEmailSender is very similar to MailGunSmtpEmailSender.  The major difference, of course, is how the email being sent.

When being sent via HTTP REST API, the function requires a request header that contains the authentication credentials:

...
             string apiKey = EmailSettings.ApiKey;
...
             string token = HttpBasicAuthHeader("api", apiKey);
...			 
                 client.DefaultRequestHeaders.Authorization = 
                     new AuthenticationHeaderValue("Basic", token);

The function HttpBasicAuthHeader takes care of the details of putting together the authentication header for the http request.

Once authentication is taken care of, we would need the specific destination for the request:

             string baseUri = EmailSettings.BaseUri;
             string requestUri = EmailSettings.RequestUri;

Of course, we would also need the email content. After all, this is the whole point of the exercise.    And since this is going through http, the email content needs to be encoded for http:

             FormUrlEncodedContent emailContent = HttpContent(email,
                 subject,
                 message
             );

The function HttpContent() takes care of the details of encoding the email content for http.

Once both MailGunSmtpEmailSender.cs and MailGunApiEmailSender.cs are set up, we have to go back to the <code>$WORKPAD/Startup.cs</code> file and make sure that we add in the corresponding transient service for both of these email senders.

     IConfiguration emailConfig = LoadEmailConfig();

     switch (emailProvider.ToLower())
     {

         case "mailgun":

             Console.WriteLine(
                 "Starting send mail via MailGun email service...");

             if ("api" == emailConnectionType.ToLower())
             {

                 services.AddTransient<IEmailSender, 
                     MailGunApiEmailSender>();
                 services.Configure<MailGunApiEmailSettings>(
                     emailConfig);

             }

             if ("smtp" == emailConnectionType.ToLower())
             {

                 services.AddTransient<IEmailSender, 
                     MailGunSmtpEmailSender>();
                 services.Configure<MailGunSmtpEmailSettings>(
                     emailConfig);

             }

             break;

         case "gmail":

             Console.WriteLine(
                 "[Placeholder] Starting send mail via Google email service...");

             break;

         default:

             Console.WriteLine("Error: Unknown email service.");

             throw new ArgumentException(
                 "Unknown email service provider in configuration.",
                 "EmailProvider:Provider"
             );

             // break;

         }

 }

You can also add in exception handling in the corresponding routes in the Account controller.  It can be a simple Console.WriteLine() or a view showing the end user that email had failed to send out.

Once all the code changes have been made, it is time to recompile the code:

 [625][vrw.myrna: /home/vrw] $ ## Recompiling after code changes
 [626][vrw.myrna: /home/vrw] $ cd $WORKPAD; dotnet build

After the project has successfully compiled, bring up a web browser and go to URL localhost:5000.

In the web page that comes up, click on Register link on the upper-right-hand corner.

Before registering a new user, if you are using the Sandbox in MailGun, make sure that the email address you are going to use is registered with MailGun sandbox domain.  When you put in the email address in MailGun, makes sure that the email address is in all lower-case.  (There is a quirk in MailGun Sandbox where it would only validate the email address in the authorized list if it is all in lower cases.)

 

Now, in the registration page, put in a valid email address, as well as the password and confirmation password.  When you hit submit, you should receive an email notification, providing you with a link to confirm your registration with the web application.

When you receive the confirmation request email, go ahead and click on the link and confirm your registration.

When the page comes up, instead of logging in, click on the Forgot your password? link at the end of the page.

In the form that comes up, type in the email address you have just confirmed your registration with.  When you hit submit, you should receive an email from the web application that provides you with a link to reset your password.  (This will not happen unless user registration has been successfully confirmed.  Of course, if you are familiar with SQL Lite, you can simply update the flag in the database table.)

Yay!!!     You now have successfully hooked up your web application with a emailer!


Reference

Leave a Reply

Your email address will not be published. Required fields are marked *