Capture emails during development using smtp4dev and UI Test with Selenium
Tags: dotnet, smtp, aspnet
Published: 2021-05-04 15:23:47

---
title: Capture emails during development using smtp4dev and UI Test with Selenium
published: true
date: 2021-04-21 00:00:00 UTC
tags: dotnet,smtp,aspnet
canonical_url: https://swimburger.net/blog/dotnet/capture-emails-during-development-using-smtp4dev-and-ui-test-with-selenium
cover_image: https://swimburger.net/media/iiih4i1k/smtp4dev-selenium-email-capture-social.png?anchor=center&mode=crop&width=1000&height=420
---

If you develop websites for a living, you probably have created a couple of forms that send out emails. You may have created dozens of them by now and your personal inbox is filled with spam from yourself and your peers testing. So much so that you've created automated Outlook rules to read & archive them automatically.

Maybe it's just me and I'm projecting, but if you find yourself in a similar situation, check out this tool called [smtp4dev](https://github.com/rnwood/smtp4dev). Here's how smtp4dev describes itself on the readme:

> A dummy SMTP server for Windows, Linux, Mac OS-X (and maybe elsewhere where .NET Core is available). Lets you test your application without spamming your real customers and without needing to set up a complicated real email server with a special configuration. Messages received in smtp4dev can be viewed and inspected.

Smpt4dev used to be a Windows GUI tool similar to [Papercut](https://github.com/ChangemakerStudios/Papercut-SMTP)—another excellent dev mail tool—, but now is a **cross-platform service** that you can host locally and remotely. Smtp4dev supports **SMTP** , basic **IMAP** , and offers a web interface to view the captured emails.  
You can also configure relay options so that emails to specific email addresses are relayed to a real SMTP server so that the emails are actually being sent out.

Let's install smtp4dev and try it out.

## Install smtp4dev as a .NET tool

To install smtp4dev, you need to have the .NET Core SDK 3.1 or up on your machine. When the SDK is installed, run the following command:

```powershell
dotnet tool install -g Rnwood.Smtp4dev
```

This will install smtp4dev as a global .NET tool which will make it available from the command line as `smtp4dev`.

Consult [the installation instructions](https://github.com/rnwood/smtp4dev/wiki/Installation) if you want to run smtp4dev as a docker container, as a Windows service, or run it inside IIS.

Now that smpt4dev is installed, execute the following command to run the tool:

```powershell
smtp4dev --urls=https://localhost:5002
```

This command will run smtp4dev locally using the following ports:

- port **5002** exposes the **webserver**
- port **25** exposes the **SMTP** server
- port **143** exposes the **IMAP** server

If you omit the `--urls` argument, the webserver will use '_http://localhost:5000_' and '_https://localhost:5001_'.  
I recommend specifying a different URL to host smtp4dev to avoid port collision with other ASP.NET Core applications using the same two default hosting URLs.

To quickly verify the SMTP functionality, open a PowerShell and run this command:

```powershell
Send-MailMessage -SmtpServer localhost -Subject "Hello World" -Body "Hello World" -From "niels@localhost" -To "niels@localhost"
```

This command will use the smtp4dev SMTP server hosted at _localhost:25_ to send an email. When you browse to the smtp4dev dashboard at _https://localhost:5002_, you will find the email:

 ![Screenshot of the smtp4dev dashboard showing 1 email saying &quot;Hello World&quot;](https://swimburger.net/media/xcopryxk/smpt4dev-send-mail-message.png)

## Send emails from a form in ASP.NET Core MVC

Sending an email from PowerShell is useful for verifying your SMTP server, but you likely want to send emails from your application.

Here's [a sample ASP.NET Core MVC application](https://github.com/Swimburger/Smtp4devSample) that accepts a form submission and sends two emails, one to the submitter of the form and one to the owner of the website.   
The emails are sent using the `SmtpClient` which comes with the .NET SDK. This is what the controller looks like for the form:

```csharp
using System.Diagnostics;
using System.Net.Mail;
using Microsoft.AspNetCore.Mvc;
using Smpt4devSample.WebApp.Models;

namespace Smpt4devSample.WebApp.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index() => View();

        [HttpPost]
        public IActionResult Index(ContactFormViewModel contactFormViewModel)
        {
            if(!ModelState.IsValid)
            {
                return View();
            }

            using var smtpClient = new SmtpClient()
            {
                Host = "localhost",
                Port = 25
            };
            var submitterFullName = $"{contactFormViewModel.FirstName} {contactFormViewModel.LastName}";
            
            // send email to website owner
            smtpClient.Send("website@localhost", "niels@localhost", $"Contact Us submission by {submitterFullName}", 
$@"New Contact Us submission:

    First Name: {contactFormViewModel.FirstName}
    Last Name: {contactFormViewModel.LastName}
    Email Address: {contactFormViewModel.EmailAddress}
    Question:
    {contactFormViewModel.Question}"
            );

            // send email to submitter
            smtpClient.Send("niels@localhost", contactFormViewModel.EmailAddress, "Thank you for contacting us", 
                $"Thank you {submitterFullName} for reaching out to us.\nWe have received your inquiry and will respond to you in 24 hours."
            );

            return RedirectToAction(nameof(ThankYou));
        }

        public IActionResult ThankYou() => View();
    }
}

```

The controller does the following:

- Serve the form using the `Index()` action
- Accept the form submission using the `Index(ContactFormViewModel)` action. This action does the following
  - validate form state
  - create a SmtpClient that connects to localhost:25
  - send an email to the site owner with all values provided by the form submitter
  - send an email to the email address provided by the form submitter
  - redirect to the `ThankYou()` action.   
(Always a good idea to have a dedicated thank you page for optimal analytics tracking 👍)

These are the emails you get when submitting the form:

 ![Screenshot of smtp4dev dashboard with 2 additional emails](https://swimburger.net/media/gj0phpl5/smpt4dev-aspmvc-submission.png)
## UI Test the form using Selenium

Up until now, the form and email functionality have been verified manually, but if this functionality is important to your website, you should automate this with an end-to-end (E2E) test.  
Here's how you can test the above form using Selenium and MSTest:

```csharp
using System;
using System.Linq;
using System.Threading.Tasks;
using MailKit;
using MailKit.Net.Imap;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium.Chrome;

namespace Smpt4devSample.UiTests
{
    [TestClass]
    public class ContactUsFormTests
    {
        [ClassInitialize]
        public static async Task SetupTests(TestContext testContext)
        {
            var chromeDriverInstaller = new ChromeDriverInstaller();
            await chromeDriverInstaller.Install();
        }

        [TestMethod]
        public void TestContactUsForm()
        {
            // output should look like 9014dbca920e40dfa7c760a4a4808759@localhost
            var emailAddress = $"{Guid.NewGuid().ToString("N")}@localhost";

            var chromeOptions = new ChromeOptions();
            chromeOptions.AddArguments("headless");
            using (var driver = new ChromeDriver(chromeOptions))
            {
                driver.Navigate().GoToUrl("https://localhost:5001");
                driver.FindElementById("firstName").SendKeys("Jon");
                driver.FindElementById("lastName").SendKeys("Doe");

                driver.FindElementById("emailAddress").SendKeys(emailAddress);

                driver.FindElementById("question").SendKeys("Hello World!");

                driver.FindElementsByCssSelector("form button").First().Click();

                Assert.AreEqual("https://localhost:5001/Home/ThankYou", driver.Url);
                Assert.IsTrue(driver.PageSource.Contains("Thank you for contacting us"));
            }

            // more to come
        }
    }
}

```

The code does the following:

- Before running the test, the correct ChromeDriver is installed into the bin folder automatically using the ChromeDriverInstaller from a previous blog post: "[Download the right ChromeDriver version & keep it up to date on Windows/Linux/macOS using C# .NET](https://swimburger.net/blog/dotnet/download-the-right-chromedriver-version-and-keep-it-up-to-date-on-windows-linux-macos-using-csharp-dotnet "Download the right ChromeDriver version and keep it up to date on Windows/Linux/macOS using csharp dotNET")"
- The test itself does the following:
  - Generate a unique email address by using a GUID and concatenating it with "@localhost". By using a unique email address for this test session, you avoid false negatives from previous test runs.
  - Create a headless ChromeDriver instance.   
The ChromeDriver class is provided by the `Selenium.WebDriver` package. You can install the package using this command: `dotnet add package Selenium.WebDriver`
  - Navigate to the form located at _https://localhost:5001_
  - Fill out the form with dummy data
  - Submit the form by clicking the submit button
  - Verify you are redirected to the thank you page
  - Verify the content of the thank you page

This is a great start, but **unfortunately, this is often where the test automation ends and the manual verification starts**. In many instances, developers and QA engineers will manually verify whether the email was sent out and received, but you can automate this part too.

## Verify emails using IMAP

If you're not using smtp4dev or you're trying to verify the email functionality in production, you can use different protocols for downloading email like POP or IMAP. Alternatively, if your email service offers an HTTP API like Outlook and Gmail, you can also use that for email verification.

Smtp4dev has IMAP support—tho only basic functionality—which you can use to verify the emails. The test sample continues to verify the email functionality by fetching the emails using IMAP:

```csharp
// more to come

using (var client = new ImapClient())
{
    client.Connect("localhost", 143);
    client.Authenticate(userName: "", password: "");

    // The Inbox folder is always available on all IMAP servers...
    var inbox = client.Inbox;
    inbox.Open(FolderAccess.ReadOnly);

    var emailToSiteOwner = inbox.GetMessage(inbox.Count - 1);
    var emailToSubmitter = inbox.GetMessage(inbox.Count - 2);
    MimeKit.MimeMessage tempMessage = null;

    // swap emails if necessary
    if(
        !emailToSiteOwner.Subject.Contains("Contact Us submission by") && 
        emailToSubmitter.Subject.Contains("Contact Us submission by")
    )
    {
        tempMessage = emailToSiteOwner;
        emailToSiteOwner = emailToSubmitter;
        emailToSubmitter = tempMessage;
    }

    Assert.AreEqual("Contact Us submission by Jon Doe", emailToSiteOwner.Subject);
    Assert.IsTrue(emailToSiteOwner.TextBody.Contains("First Name: Jon"));
    Assert.IsTrue(emailToSiteOwner.TextBody.Contains("Last Name: Doe"));
    Assert.IsTrue(emailToSiteOwner.TextBody.Contains($"Email Address: {emailAddress}"));
    Assert.IsTrue(emailToSiteOwner.TextBody.Contains("Hello World!"));

    Assert.AreEqual("Thank you for contacting us", emailToSubmitter.Subject);
    Assert.IsTrue(emailToSubmitter.TextBody.Contains("Thank you Jon Doe for reaching out to us."));

    client.Disconnect(true);
}

```

The code does the following:

- Create an ImapClient instance. This class is provided by another open-source project called [Mailkit](https://github.com/jstedfast/MailKit).  
You can install mailkit into your test project using this command: `dotnet add package MailKit`
- Connect and authenticate to smtp4dev using IMAP
- Get the inbox and open it using read-only mode
- Store the last two messages into `emailToSiteOwner` and `emailToSubmitter` and swap them if necessary
- Verify the contents of the emails match the data from the form submission
- disconnect and dispose of the ImapClient

**IMAP also has support for advanced querying of emails** , but unfortunately, that is not supported by smtp4dev.   
The above sample code works fine for local testing, but for a production server where there are many more emails, you should use the [IMAP querying functionality to find and verify your emails](https://github.com/jstedfast/MailKit#searching-an-imap-folder).

## Additional resources

Check out these links to learn more about the topics in this post:

- [smtp4dev GitHub](https://github.com/rnwood/smtp4dev)
- [SmtpClient documentation by Microsoft](https://docs.microsoft.com/en-us/dotnet/api/system.net.mail.smtpclient)
- [The sample code for this post on GitHub](https://github.com/Swimburger/Smtp4devSample)
- [Query IMAP sample from MailKit](https://github.com/jstedfast/MailKit#searching-an-imap-folder)
- [How to UI test using Selenium and .NET Core on Windows, Ubuntu, and MacOS](https://swimburger.net/blog/dotnet/how-to-ui-test-using-selenium-and-net-core-on-windows-ubuntu-and-maybe-macos "How to UI test using Selenium and .NET Core on Windows, Ubuntu, and maybe MacOS?")
- [Download the right ChromeDriver version & keep it up to date on Windows/Linux/macOS using PowerShell](https://swimburger.net/blog/powershell/download-the-right-chromedriver-on-windows-linux-macos-using-powershell "Download the right ChromeDriver on Windows Linux macOS using PowerShell")
- [Download the right ChromeDriver version & keep it up to date on Windows/Linux/macOS using C# .NET](https://swimburger.net/blog/dotnet/download-the-right-chromedriver-version-and-keep-it-up-to-date-on-windows-linux-macos-using-csharp-dotnet "Download the right ChromeDriver version and keep it up to date on Windows/Linux/macOS using csharp dotNET")