Skip to content
A library smoothing email generation from .NET code, by enforcing a strong boundary between the template, and code rendering the template.
Branch: master
Clone or download

skyhop logo

This .NET Core 3.1 library is an abstraction of the approach we use over at Skyhop to send transactional emails to our subscribers. We believe that having a flexible method is vital to the ability to flexibly send emails.

This core principle behind this library is based on several prior blog posts:

There is a blog post which extensively describes the approach and background of this library available over here.


The NuGet hosted package is available.


Install it using the following commands:

Using the NuGet Package Manager

Install-Package Skyhop.Mail

Using the .NET CLI

dotnet add package Skyhop.Mail

You should reference this project on your template project (or at least the project which contains your views and view-models). After which there are two changes you will need to make to your .csproj file:

  1. Set the SDK target to Microsoft.NET.Sdk.Razor.
  2. Make sure you add the AddRazorSupportForMvc element to the file so that it looks like this:

Samples which can be used as a reference can be found in this repository.


  1. Add the Skyhop.Mail project to your template project.
  2. Use the .AddMailDispatcher() extension method on your IServiceCollection as follows. Note that this library expects you to bring your own transport mechanism.
services.AddMailDispatcher(builder =>
    builder.DefaultFromAddress = new MimeKit.MailboxAddress("Email Support", "support@example.tld");
  1. Add an implementation of IMailSender to your IServiceCollection. In the example project a simple Smtp implementation is included.
  2. Add views and viewmodels to your templating project.
  3. Use the DI container to grab a MailDispatcher instance. Usage from code can be as follows. After this you can send an email based on the viewmodel as follows:
await _mailDispatcher.SendMail(
    data: new ServiceActionModel
        ActionName = "Starting",
        Timestamp = DateTime.UtcNow
    to: new[] { new MailboxAddress("John Doe", "john.doe@example.tld") });

Convention based view loading

If you would like to load all views in *.Views.dll's you can use an overload AddMailDispatcher, this overload enables the extension of IMvcCoreBuilder. We created an extension which will find and load all *.Views.dll files as application parts:

services.AddMailDispatcher(options =>
    options.DefaultFromAddress = new MailboxAddress("Email Support", "support@example.tld");
builder => builder.AddViewsApplicationParts());


The following limitations are currently available. Feel free to submit a PR to fix one or more of those ☺.

  • This library only works with projects which target netcoreapp3.1. This is a limitation based on the requirements of the Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation dependency.
  • It is expected that a view-model is only used once within a view. The code will use the first view it encounters that has the chosen model.
You can’t perform that action at this time.