Nastavení emailingu z webové služby

06.04.2021

Posílání emailů je běžná operace, která je součástí mnoha aplikací. Email uživatele můžeme použít při registraci nebo při zapomenutí přihlašovacích údajů, ale také uživatele informovat o již proběhlých nebo budoucích událostech, případně i jednorázově sdílet soubory, například faktury, protokoly, potvrzení a podobně.

Slovníček

ASP.NET Core Open-source webový framework od Microsoftu (první release 2016).
MailKit Klientská knihovna pro protokoly SMTP/POP3/IMAP4.
System.Net.Mail.SmtpClient Knihovna od Microsoftu, která již není doporučovaná ve prospěch MailKitu.

Obsah

Implementace

V našem příkladě si připravíme webovou aplikaci, do které si nainstalujeme MailKit Nuget balíček. Poté si připravíme konfiguraci a vytvoříme jednoduchou emailovou službu, která údaje z konfigurace bude přijímat přes konstruktor. Emailová služba bude volat API MailKitu a umožní nám odesílat emaily ve formě HTML s možností přidat jednu nebo více přiloh.

Příprava prostředí

Vytvoříme webový projekt ze základní šablony Visual Studia

dotnet new mvc --output Web

Instalace MailKitu a příprava konfigurace

Pomocí Package Manager Console ve Visual Studiu si nainstalujeme MailKit balíček

Install-Package MailKit

Do appsettings.json přidáme novou sekci SmtpServer. Atributy si každý musí nastavit podle svého prostředí. Pomyslný SMTP server, kterého budu chtít použít, má adresu smtp.company.com a odesílací emailová adresa bude webapp@company.com. Pokud SMTP Sever vyžaduje autentizaci, tak není dobré dávat přihlašovací údaje přímo do konfiguračního souboru appsettings.json, protože by hrozilo, že se nám údaje uloží do verzovacího systému, což není žádoucí. Pro tento účel je lepší přihlašovací údaje nastavit buď jako globální proměnné (produkce, staging) nebo přidat do secrets.json (development).

appsettings.json
"SmtpServer": {
    "Address": "smtp.company.com",
    "Port": 25,
    "UseSsl": false,
    "From": "webapp@company.com"
  }

V případě autentizace a development prostředí, nastavíme přihlašovací údaje v secrets.json souboru (Projekt > Manage User Secrets), který je uložený v %APPDATA%\microsoft\UserSecrets\<userSecretsId>\secrets.json a není tak součástí projektu.

secrets.json
{
  "SmtpServer": {
    "Username": "John.Doe@company.com",
    "Password": "P@55W02D"
  }
}

Připravíme si třídu, která bude obsahovat atributy ze SmtpServer sekce

namespace Web.Configuration
{
    public class SmtpServerConfiguration
    {
        public string Address { get; set; }
        public int Port { get; set; }
        public bool UseSsl { get; set; }
        public string From { get; set; }
        public string Username { get; set; }
        public string Password { get; set; }
    }
}

Třídu SmtpServerConfiguration si inicializujeme ve Startup.cs a rovnou zaregistrujeme i email službu, kterou si následně vytvoříme. Na tomto místě může být služba zaregistrována také jako singleton.

var smtpServer = new SmtpServerConfiguration();
Configuration.Bind("SmtpServer", smtpServer);
services.AddTransient((System.Func<System.IServiceProvider, Infrastructure.Services.Email.IEmailService>)(service => 
    new EmailService(
        smtpServer.Address, 
        smtpServer.Port, 
        smtpServer.UseSsl, 
        smtpServer.From,
        //smtpServer.Username,
        //smtpServer.Password
        )));

Email služba

IEmailService.cs

using System.Collections.Generic;

namespace Web.Infrastructure.Services.Email
{
    public interface IEmailService
    {
        void Send(string to, string subject, string body, List<Attachment> attachments = null);
    }
}

Attachment.cs

namespace Web.Infrastructure.Services.Email
  {
      public class Attachment
      {
          public string FileName { get; set; }
          public byte[] Content { get; set; }
          public Attachment(string fileName, byte[] content)
          {
              FileName = fileName;
              Content = content;
          }
      }
  }

EmailService.cs

using MimeKit;
using System.Collections.Generic;

namespace Web.Infrastructure.Services.Email
{
    public class EmailService : IEmailService
    {
        private readonly string _smptServer;
        private readonly int _port;
        private readonly bool _useSsl;
        private readonly string _from;
        private readonly string _username;
        private readonly string _password;

        public EmailService(string smtpServer, int port, bool useSsl, string from, string username = null, string password = null)
        {
            _smptServer = smtpServer;
            _port = port;
            _useSsl = useSsl;
            _from = from;
            _username = username;
            _password = password;
        }

        public void Send(string to, string subject, string htmlBody, List<Attachment> attachments = null)
        {
            var message = new MimeMessage();

            BuildBody(htmlBody, attachments, message);

            Execute(to, subject, message);
        }

        private void BuildBody(string htmlBody, List<Attachment> attachments, MimeMessage message)
        {
            var bodyBuilder = new BodyBuilder { HtmlBody = new TextPart("html") { Text = htmlBody }.Text };

            if (attachments != null)
            {
                foreach (var attachment in attachments)
                {
                    bodyBuilder.Attachments.Add(fileName: attachment.FileName, data: attachment.Content);
                }
            }

            message.Body = bodyBuilder.ToMessageBody();
        }

        private void Execute(string to, string subject, MimeMessage message)
        {
            message.From.Add(MailboxAddress.Parse(_from));

            message.To.Add(MailboxAddress.Parse(to));

            message.Subject = subject;

            using var client = new MailKit.Net.Smtp.SmtpClient();

            client.Connect(_smptServer, _port, _useSsl);

            if (_username != null && _password != null)
            {
                client.Authenticate(_username, _password);
            }

            client.Send(message);

            client.Disconnect(true);
        }
    }
}

Závěr

Nyní máme k dispozici interface i implementaci emailové služby, kterou máme zaregistrovanou ve Startup.cs podle konfigurace, kterou jsme nastavili v appsettings.json souboru. Službu tedy nyní můžeme snadno volat napříč projektem, například v controlleru nebo jiné službě, ale také jí snadno prohodit za fake nebo mock službu pro účely testování.

Odkazy