I released a new starter kit for Umbraco 9 called Portfolio Starter Kit. In that, there is a contact form and if you have your SMTP settings set up correctly in appSettings.json it will send the email for you.

Here is the Contact Controller Code:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Portfolio.Core.Models.ViewModels;
using System.Threading.Tasks;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Logging;
using Umbraco.Cms.Core.Mail;
using Umbraco.Cms.Core.Models.Email;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Web.Website.Controllers;


namespace Portfolio.Core.Controllers.Surface
{
    public class ContactSurfaceController : SurfaceController
    {
        private readonly IEmailSender _emailSender;
        private readonly ILogger<ContactSurfaceController> _logger;
        private readonly GlobalSettings _globalSettings;

        public ContactSurfaceController(
            IUmbracoContextAccessor umbracoContextAccessor, 
            IUmbracoDatabaseFactory databaseFactory, 
            ServiceContext services, 
            AppCaches appCaches, 
            IProfilingLogger profilingLogger, 
            IPublishedUrlProvider publishedUrlProvider, 
            IEmailSender emailSender, 
            ILogger<ContactSurfaceController> logger, 
            IOptions<GlobalSettings> globalSettings)
            : base(umbracoContextAccessor, databaseFactory, 
                  services, appCaches, profilingLogger, 
                  publishedUrlProvider)
        {
            _emailSender = emailSender;
            _logger = logger;
            _globalSettings = globalSettings.Value;
        }

        [HttpPost]
        public async Task<IActionResult> SubmitForm(ContactViewModel model)
        {
            if (!ModelState.IsValid) return CurrentUmbracoPage();

            TempData["Success"] = await SendEmail(model);

            return RedirectToCurrentUmbracoPage();
        }

        public async Task<bool> SendEmail(ContactViewModel model)
        {
            try
            {
                var fromAddress = _globalSettings.Smtp.From;

                var subject = string.Format("Enquiry from: {0} - {1}", model.Name, model.Email);
                EmailMessage message = new EmailMessage(fromAddress, model.Email, subject, model.Message, false);
                await _emailSender.SendAsync(message, emailType: "Contact");

                _logger.LogInformation("Contact Form Submitted Successfully");
                return true;
            }
            catch (System.Exception ex)
            {
                _logger.LogError(ex, "Error When Submitting Contact Form");
                return false;
            }
        }
    }
}

The first thing to notice is the amount of services you have to inject into your surface controller

public ContactSurfaceController(
    IUmbracoContextAccessor umbracoContextAccessor, 
    IUmbracoDatabaseFactory databaseFactory, 
    ServiceContext services, 
    AppCaches appCaches, 
    IProfilingLogger profilingLogger, 
    IPublishedUrlProvider publishedUrlProvider, 
    IEmailSender emailSender, 
    ILogger<ContactSurfaceController> logger, 
    IOptions<GlobalSettings> globalSettings)
    : base(umbracoContextAccessor, databaseFactory, 
          services, appCaches, profilingLogger, 
          publishedUrlProvider)
{
    _emailSender = emailSender;
    _logger = logger;
    _globalSettings = globalSettings.Value;
}

The first set of interfaces injected in are to pass to the base class, then we pass in 3 more.

    IEmailSender emailSender, 
    ILogger<ContactSurfaceController> logger, 
    IOptions<GlobalSettings> globalSettings

The IEmailSender is an interface to allow you to send emails using MailKit instead of System.Net.Mail that we are used to from .NET Framework.

ILogger logger is just set up the logger instance for this class

IOptions globalSettings allows us to read from the global settings from the appSettings file and get the sender email address.

The SubmitForm action is an async task and is pretty simple. If the form is valid then call the send email method and if not then show the form with the error messages.

[HttpPost]
public async Task<IActionResult> SubmitForm(ContactViewModel model)
{
    if (!ModelState.IsValid) return CurrentUmbracoPage();

    TempData["Success"] = await SendEmail(model);

    return RedirectToCurrentUmbracoPage();
}

Finally here is the SendEmail method.

public async Task<bool> SendEmail(ContactViewModel model)
{
    try
    {
        var fromAddress = _globalSettings.Smtp.From;

        var subject = string.Format("Enquiry from: {0} - {1}", model.Name, model.Email);
        EmailMessage message = new EmailMessage(fromAddress, model.Email, subject, model.Message, false);
        await _emailSender.SendAsync(message, emailType: "Contact");

        _logger.LogInformation("Contact Form Submitted Successfully");
        return true;
    }
    catch (System.Exception ex)
    {
        _logger.LogError(ex, "Error When Submitting Contact Form");
        return false;
    }
}

I believe the email sending doesn't work if you leave the from address as [email protected] so you need to change that.

If you want to test it out locally, you may need to run this command to add a valid dev SSL certificate to allow the sending of emails.

dotnet dev-certs https --trust

And if that doesn't work, you might need to add this to your appSettings.development.json Smtp section settings:

enter image description here

Paul Seal

Umbraco MVP and .NET Web Developer from Derby (UK) who specialises in building Content Management System (CMS) websites using MVC with Umbraco as a framework. Paul is passionate about web development and programming as a whole. Apart from when he's with his wife and son, if he's not writing code, he's thinking about it or listening to a podcast about it.

Proudly sponsored by

Moriyama

  • Moriyama build, support and deploy Umbraco, Azure and ASP.NET websites and applications.
AppVeyor

  • CI/CD service for Windows, Linux and macOS
  • Build, test, deploy your apps faster, on any platform.
elmah.io

  • elmah.io is the easy error logging and uptime monitoring service for .NET.
  • Take back control of your errors with support for all .NET web and logging frameworks.
uSync Complete

  • uSync.Complete gives you all the uSync packages, allowing you to completely control how your Umbraco settings, content and media is stored, transferred and managed across all your Umbraco Installations.
uSkinned

  • More than a theme for Umbraco CMS, take full control of your content and design with a feature-rich, award-nominated & content editor focused website platform.
UmbHost

  • Affordable, Geo-Redundant, Umbraco hosting which gives back to the community by sponsoring an Umbraco Open Source Developer with each hosting package sold.