Quote of the Day

more Quotes

Categories

Buy me a coffee

Send email with attachments using MailKit for .NET core

Published April 5, 2019 in .NET core , ASP.NET core - 0 Comments

This post is another example of setting up and using MailKit for .NET core to send an email with attachments.

First, add the library to your project.

In the Startup class, configure MailKit as followed.

 public void ConfigureServices(IServiceCollection services)
        {
            // ....
            services.AddEmail(Configuration);
        }

// AddEmail is an extension method. 
 public static class StartupExtensions
    {
        public static IServiceCollection AddEmail(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddMailKit(optionBuilder =>
            {
                var mailKitOptions = new MailKitOptions()
                {
                    // get options from secrets.json 
                    Server = configuration.GetValue<string>("Email:Server"),
                    Port = configuration.GetValue<int>("Email:Port"),
                    SenderName = configuration.GetValue<string>("Email:SenderName"),
                    SenderEmail = configuration.GetValue<string>("Email:SenderEmail"),
                    // can be optional with no authentication 
                    Account = configuration.GetValue<string>("Email:Account"),
                    Password = configuration.GetValue<string>("Email:Password"),
                    Security = configuration.GetValue<bool>("Email:Security")
                };                 if (mailKitOptions.Server == null)
                {
                    throw new InvalidOperationException("Please specify SmtpServer in appsettings");
                }
                if (mailKitOptions.Port == 0)
                {
                    throw new InvalidOperationException("Please specify Smtp port in appsettings");
                }

                if (mailKitOptions.SenderEmail == null)
                {
                    throw new InvalidOperationException("Please specify SenderEmail in appsettings");
                }
                
                optionBuilder.UseMailKit(mailKitOptions);
            });
            services.AddScoped<IAppEmailService, AppEmailService>();
            return services;
        }
    }

In the above codes, the configurations come from appsettings.json file. Below shows a sample configuration for sending email using Gmail smtp server.

{
  "Email": {
    "Server": "smtp.gmail.com",
    "Port": 465,
    "Security": true,
    "SenderName": "your name",
    "SenderEmail": "your email",
    "Account": "username",
    "Password": "password"
  }
}
public interface IAppEmailService : IEmailService
  {
      Task SendAsync(MimeMessage message);
      Task SendAsync(EmailRequest emailRequest);
  }

public class AppEmailService : EmailService, IAppEmailService
  {
      private readonly IMailKitProvider _mailKitProvider; 

      public AppEmailService(IMailKitProvider provider) : base(provider)
      {
          _mailKitProvider = provider; 
      }

      public async Task SendAsync(MimeMessage message)
      {
          message.From.Add(new MailboxAddress(_mailKitProvider.Options.SenderEmail));
          using (var emailClient = _mailKitProvider.SmtpClient)
          {
              if (!emailClient.IsConnected)
              {
                  await emailClient.AuthenticateAsync(_mailKitProvider.Options.Account, 
                  _mailKitProvider.Options.Password);
                  await emailClient.ConnectAsync(_mailKitProvider.Options.Server,
                  _mailKitProvider.Options.Port, _mailKitProvider.Options.Security);
              }
              await emailClient.SendAsync(message);
              await emailClient.DisconnectAsync(true);
          }
      }

      public async Task SendAsync(EmailRequest emailRequest)
      {
          MimeMessage mimeMessage = new MimeMessage();
          mimeMessage.To.Add(new MailboxAddress(emailRequest.ToAddress));
          mimeMessage.Subject = emailRequest.Subject;
          var builder = new BodyBuilder { HtmlBody = emailRequest.Body };
          if ( emailRequest.Attachment != null)
          { 
              using (MemoryStream memoryStream = new MemoryStream())
              {
                  await emailRequest.Attachment.CopyToAsync(memoryStream);
                  builder.Attachments.Add(emailRequest.Attachment.FileName, memoryStream.ToArray());
              }
          
          }
          mimeMessage.Body = builder.ToMessageBody();
          await SendAsync(mimeMessage);
      }
  }

EmailMessage is a simple model which encapsulates the metadata of an email. It has a IFormFile property and is suitable for deserializing a multipart/form-data containing attachment file in a HTTP request.

[Required]
public string ToAddress { get; set; }
[Required]
public string Body { get; set; }
[Required]
public string Subject { get; set; }
public IFormFile Attachment { get; set;  }

You should only use model binding for small files. For large files, you may consider streaming the form data instead of using model binding.

The IEmailService interface and the EmailService do not expose a method for including an attachment. However, through the SmtpClient class, which is also part of MailKit, we can send a MimeMessage in which we can include binary data as attachments.

public interface IAppEmailService : IEmailService
    {
        Task SendAsync(MimeMessage message);
    }

public class AppEmailService : EmailService, IAppEmailService
    {
        private readonly IMailKitProvider _mailKitProvider; 
        public AppEmailService(IMailKitProvider provider) : base(provider)
        {
            _mailKitProvider = provider; 
        }

        public async Task SendAsync(MimeMessage message)
        {
            message.From.Add(new MailboxAddress(_mailKitProvider.Options.SenderEmail));

            using (var emailClient = _mailKitProvider.SmtpClient)
            {
                if (!emailClient.IsConnected)
                {
                    await emailClient.ConnectAsync(_mailKitProvider.Options.Server, _mailKitProvider.Options.Port, true);
                }
                await emailClient.SendAsync(message);
                emailClient.Disconnect(true);
            }
        }
       
    }

We can inject an instance of the IAppEmailService to where we need the email functionalities, such as in the controller.

[Route("api/[controller]")]
[ApiController]
public class EmailController : Controller
{
    private IAppEmailService _appEmailService;

    [HttpGet]
    public string Status()
    {
        return "Service is up.";
    }


    public EmailController(IAppEmailService appEmailService)
    {
        _appEmailService = appEmailService;
    }
    
    [HttpPost]
    public async Task<IActionResult> SendEmail([FromForm] EmailRequest emailRequest)
    {
        await _appEmailService.SendAsync(emailRequest);
        return new OkResult();
    }
}

Sample HTTP request

curl -X POST \
  http://localhost:5000/api/email \
  -H 'Accept: application/json' \
  -H 'Content-Type: multipart/form-data' \
  -H 'Postman-Token: 811d6439-5ede-4070-b891-fc16d4054ae4' \
  -H 'cache-control: no-cache' \
  -H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \
  -F ToAddress=youraddress@gmail.com \
  -F 'Body=This is a test body' \
  -F 'Subject=This is a test subject' \
  -F Attachment=@/{replace_with_link_to_attachment_File}

Complete source code is available on GitHub

References

Using MailKit To Send And Receive Email In ASP.net Core

File uploads in ASP.NET core

MailKit project

No comments yet