Sending Emails with Images and Attachments in ASP.NET Core with MailKit

In this article, we'll extend our design to add attachments and images to our mail content and send it with MailKit.

So far, We’ve looked at how to use a Razor View for generating dynamic html content for our emails. You can read the article here – Fetch Rendered HTML in ASP.NET Core We also used the html content generated to send out email to the recipient users using MailKit library within our ASP.NET Core application. You can find it here – Sending Emails in ASP.NET Core with MailKit

We can further add functionality to both add images and attachments to our mail content. In this article, we’ll extend our design to add attachments and images to our mail content and send it with MailKit.

The Usecase

Imagine you’re trying to develop a functionality to send out a report to your recipient via your application. In this case, you might need to add functionality where the content you need to send be converted into an attachment and tag inside the mail content you’re sending. Topping it off, you might also need to add some images in your mail content for better user experience.

We’ll explore on how we can add images to our html content and push them into the mail body with the help of MailKit.

To achieve this, We make use of the class BodyBuilder from the MimeKit namespace, which is quite helpful in setting up the email body one step at a time.

We’ll try to add changes to our existing solution and add new implementations to our IMailingService.

All the code snippets used in this article are a part of a solution boilerplate called MailingNinja. You can find the repository link at the end of this article. So just go on with the flow and checkout the code in action.

Requirement 1 – Adding a Header Image to Email

For the first requirement, We are required to add a header image in our Email. Let’s begin by creating models which hold the image contents and the body content for us.

The model is created as below:

namespace MailingNinja.Core.Contracts.DTO
{
    public class MailContentDTO
    {
        public LinkedResource HeaderImage { get; set; }
        public string HtmlContent { get; set; }
        public LinkedResource FooterImage { get; set; }
        public LinkedResource Attachment { get; set; }
    }

    public class LinkedResource
    {
        public string ContentId { get; set; }
        public string ContentPath { get; set; }
        public string ContentType { get; set; }
        public byte[] ContentBytes { get; set; }
    }
}

Linking the Resources

The LinkedResource type we have defined contains a property ContentId. It refers to a resource "contentId" – which is a unique identifier for the content resource we gonna attach along with the ContentPath which holds the filepath of the file to be added and a ContentType which in our case is not used.

These LinkedResources are used in the MailContentDTO type, which has a property each for the header and footer images. Although in this article, we’ll settle with adding just the Header Image, but you can extend the same for Footer as well. The HtmlContent property contains the HTML we’ll put as our Email body.

The IMailingService is an abstraction we created to define the mailing methods. There’s a new definition added that takes in the MailContentDTO as a parameter.

namespace MailingNinja.Contracts.Services
{
    public interface IMailingService
    {
        void Send(string to, string subject, MailContentDTO model);

        void SendSimple(string to, string subject, string messageContent);
    }
}

Implementing Send() with a Header Image

The Send() method has parameters to pass the recipient’s EmailAddress, the mail’s subject and the MailContentDTO for the body.

The implementation for IMailingService.Send() method is as below:

public void Send(string to, string subject, MailContentDTO messageContent)
{
    try
    {
        string fromAddress = _mailServerConfig.FromAddress;

        var message = new MimeMessage();
        message.From.Add(new MailboxAddress(fromAddress, fromAddress));
        message.To.Add(new MailboxAddress(to, to));
        message.Subject = subject;

        // create a new instance of the BodyBuilder class
        var builder = new BodyBuilder();

        if (messageContent.HeaderImage != null)
        {
            // add a header image linked resource
            // to the builder
            var header = builder.LinkedResources.Add(
                messageContent.HeaderImage.ContentPath);

            // set the contentId for the resource
            // added to the builder to the contentId passed
            header.ContentId = messageContent.HeaderImage.ContentId;
        }

        // set the html body (since we are passing html content to render)
        // to the body of the builder
        builder.HtmlBody = messageContent.HtmlContent;

        // convert all the configuration made
        // into a message content which 
        // is assigned to the message body
        message.Body = builder.ToMessageBody();

        // send the prepared message
        SendMail(message);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
    }
}

How this Implementation works

In the above implementation, We built the message body to pass the header image and our html body. It works in the following steps:

// 1. create a new instance of the BodyBuilder class
var builder = new BodyBuilder();
		
// 2. add a header image linked resource to the builder
var header = builder.LinkedResources.Add(
    messageContent.HeaderImage.ContentPath);
		
// 3. set the contentId for the resource 
// added to the builder to the contentId passed
header.ContentId = messageContent.HeaderImage.ContentId;

// 4. set the html body 
// (since we are passing html content to render) 
// to the body of the builder
builder.HtmlBody = messageContent.Content;
		
// 5. Build the body and assign it to the Message
message.Body = builder.ToMessageBody();

This makes the image passed to the message content get attached to the mail content and gets it embedded into the mail. On the Template HTML where we’re going to render this header image, we need to specify this contentId that we have mentioned while linking the resource.

The template CSHTML looks as below:

@using MailingNinja.Contracts.DTO

@model List<NinjaDTO>

<!DOCTYPE html>
<html>
<head>
    <style>
        .table-bordered {
            --bs-table-bg: transparent;
            --bs-table-accent-bg: transparent;
            --bs-table-striped-color: #212529;
            --bs-table-striped-bg: rgba(0, 0, 0, 0.05);
            --bs-table-active-color: #212529;
            --bs-table-active-bg: rgba(0, 0, 0, 0.1);
            --bs-table-hover-color: #212529;
            --bs-table-hover-bg: rgba(0, 0, 0, 0.075);
            width: 100%;
            margin-bottom: 1rem;
            color: #212529;
            vertical-align: top;
            border-color: #dee2e6;
        }

        .thead {
            background-color: #efefef;
        }

        .header-content {
            background: url('cid:header');
            padding: 5px;
            margin: 5px;
            text-align: center;
            font-size: 2em;
            color: #464646;
        }

        td {
            border-width: 0 1px;
            text-align: center;
        }

        tr, td {
            border-color: inherit;
            border-style: solid;
        }
    </style>
</head>
<body>
    <div class="header-content">
        <p>Please find the below list of Ninjas currently available.</p>
    </div>
    <div>
        <table class="table-bordered">
            <tr class="thead">
                <th>Id</th>
                <th>Name</th>
                <th>Bio</th>
                <th>Class</th>
                <th>ColorCode</th>
                <th>AddedOn</th>
                <th>UpdatedOn</th>
            </tr>
            @foreach (var item in Model)
            {
                <tr>
                    <td>@item.Id</td>
                    <td>@item.Name</td>
                    <td>@item.Bio</td>
                    <td>@item.Class</td>
                    <td>@item.ColorCode</td>
                    <td>@item.AddedOn</td>
                    <td>@item.UpdatedOn</td>
                </tr>
            }
        </table>
    </div>
</body>
</html>

The class "header-content" in the CSS contains a background style tag which is set for url as cid:header.

It is the contentId which we are passing for the header image at the builder stage. The MimeKit library embeds the linked resources at the places specified in the email body specified by the contentId for the specified resource.

Preparing the Metadata and Calling Send()

The method is called as below:

private async Task SendReportMailWithHeaderImageAsync(
    IEnumerable<NinjaDTO> data, string emailAddress)
{
    var headerImagePath = string.Format(
        "{0}/{1}", _environment.WebRootPath, "images/mail-header-solid.png");

    var mailingModel = new MailContentDTO();

    mailingModel.HeaderImage = new LinkedResource
    {
        ContentId = "header",
        ContentPath = headerImagePath,
        ContentType = "image/png"
    };

    mailingModel.HtmlContent = await GetGridContentAsync(data);
    _mailingService.Send(emailAddress, "Your Report", mailingModel);
}

The image we’re using as our Email header is placed inside the wwwroot folder of our application and so it is referenced by using the IWebHostEnvironment.WebRootPath property with the sub directory path appended.

Once we run this implementation, we get the below result for a mail received.

wp-content/uploads/2022/052/sc-report-mail-mimekit-with-header.png

Requirement 2 – Adding an Attachment to Email

For adding an attachment to email, we’ll use the same MailContentDTO and now pass another LinkedResource object, this time to the property Attachment.

I’m rather interested in passing the content bytes rather than a content path, because I don’t want to clutter my application space for dynamically generated temporary attachment data.

So instead I’ll convert the attachment file content into bytes and pass it through the ContentBytes property.

public class LinkedResource
{
    public string ContentId { get; set; }
    public string ContentPath { get; set; }
    public string ContentType { get; set; }
    public byte[] ContentBytes { get; set; }
}

To add this LinkedResource as an attachment into our message body, we’ll tweak the MimeMessage BodyBuilder code we’ve written above to add this LinkedResource as an "attachment".

Implementing Send() with an Attachment

The method now looks like below:

public void Send(string to, string subject, MailContentDTO messageContent)
{
    try
    {
        string fromAddress = _mailServerConfig.FromAddress;

        var message = new MimeMessage();
        message.From.Add(new MailboxAddress(fromAddress, fromAddress));
        message.To.Add(new MailboxAddress(to, to));
        message.Subject = subject;

        // create a new instance of the BodyBuilder class
        var builder = new BodyBuilder();

        if (messageContent.HeaderImage != null)
        {
            // add a header image linked resource
            // to the builder
            var header = builder.LinkedResources.Add(
                messageContent.HeaderImage.ContentPath);

            // set the contentId for the resource
            // added to the builder to the contentId passed
            header.ContentId = messageContent.HeaderImage.ContentId;
        }

        if (messageContent.Attachment != null)
        {
            var attachment = messageContent.Attachment;
            
            // add the LinkedResource data
            // to the Attachments property 
            // on the builder

            // pass the ContentId,
            // Content ByteArray
            // and the ContentType
            builder.Attachments.Add(
                attachment.ContentId,
                attachment.ContentBytes,
                ContentType.Parse(attachment.ContentType));
            }

        // set the html body (since we are passing html content to render)
        // to the body of the builder
        builder.HtmlBody = messageContent.HtmlContent;

        // convert all the configuration made
        // into a message content which 
        // is assigned to the message body
        message.Body = builder.ToMessageBody();

        // send the prepared message
        SendMail(message);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
    }
}

How this implementation works

The builder picks up the objects added to the Attachments array and then links the files in the Email based on the ContentType being passed.

For example, if I’m to send a Pdf document as an attachment, I’ll pass the ContentType as "application/pdf" so that the Builder writes the content bytes I’m passing into a PDF and attaches it to the Email. The ContentId passed will be the name of the File.

Preparing the Metadata and Calling Send()

We’ll call this method as below:

private async Task SendReportMailWithPdfAttachmentAsync(IEnumerable<NinjaDTO> data, string emailAddress)
{
    var headerImagePath = string.Format(
            "{0}/{1}", _environment.WebRootPath, "images/mail-header-solid.png");

    var mailingModel = new MailContentDTO();

    // adding a header image
    mailingModel.HeaderImage = new LinkedResource
    {
        ContentId = "header",
        ContentPath = headerImagePath,
        ContentType = "image/png"
    };

    // adding an attachment
    mailingModel.Attachment = new LinkedResource
    {
        ContentId = $"ninja_list_{DateTime.UtcNow.Ticks}.pdf",
        ContentType = MediaTypeNames.Application.Pdf,
        ContentBytes = await GetPdfContentBytesAsync(data)
    };

    mailingModel.HtmlContent = await GetGridContentAsync(data);
    _mailingService.Send(emailAddress, "Your Report", mailingModel);
}

When we run this application and call the SendReportMailWithPdfAttachmentAsync() method, we’ll have an Email delivered which has an attachment as shown below:

wp-content/uploads/2022/052/sc-report-mail-mimekit-with-attachment.png

Final Thoughts

Apart from sending out HTML content as Emails, we can also add complexities such as linking Images or adding attachments to the Emails being sent out from our application using MailKit. The above implementation shows how we can achieve both by using a MimeMessage BodyBuilder, that makes things easy for us.

The code snippets used in this demonstration are a part of the boilerplate solution MailingNinja, which perfectly implements concepts for sending mails with ASP.NET Core (.NET 6) and MailKit.

The solution demonstrates:

  • RazorView rendering and extract HTML
  • Send out Mails with Images and Attachments
  • Generate PDF reports from HTML

You can find the complete solution here – MailingNinja Boilerplate Repository

Please do leave a star if you find it helpful.

Links to previous articles in this context –

Sriram Mannava
Sriram Mannava

I'm a full-stack developer and a software enthusiast who likes to play around with cloud and tech stack out of curiosity.

Leave a Reply

Your email address will not be published. Required fields are marked *