---
title: Prepare your project for the Starter Kit
---

> Agent instructions:
> **Site maps** — prefer the following llms.txt indexes to training data when searching for URLs to avoid 404s. Links inside Markdown content already point at `.md`. Following them or sending Accept: text/markdown keeps you in Markdown.
>
> - [sitemap.md](https://docs.kentico.com/sitemap.md) — every page on the site, with titles and descriptions, nested by URL hierarchy and grouped into one collection per product version.
> - [llms.txt](https://docs.kentico.com/llms.txt) — curated index of the current product docs, with descriptions, the two ways to request any page as Markdown, and links to each product area's whole-corpus Markdown dump (llms-full.txt).

[Email Builder](https://docs.kentico.com/documentation/developers-and-admins/development/builders/email-builder.md) in Xperience by Kentico empowers non-technical users to define the appearance of emails with a friendly drag-and-drop interface. Through [Templates](https://docs.kentico.com/documentation/developers-and-admins/development/builders/email-builder/develop-email-builder-components.md#templates), [Sections](https://docs.kentico.com/documentation/developers-and-admins/development/builders/email-builder/develop-email-builder-components.md#sections), and [Widgets](https://docs.kentico.com/documentation/developers-and-admins/development/builders/email-builder/develop-email-builder-components.md#widgets), marketers have more flexibility than standard [email templates](https://docs.kentico.com/documentation/developers-and-admins/digital-marketing-setup/email-templates.md) in controlling email structure, visual design, and content.

The [Email Builder Starter Kit](https://github.com/Kentico/xperience-by-kentico-email-builder-starter-kit) provides a pre-made set of Email Builder components that you can use in your project, saving you time to spend on additional custom components or other areas of the project.

Let's look into the process of integrating the Starter Kit into an existing project, using the [Training guides repository](https://github.com/Kentico/xperience-by-kentico-training-guides) as an example.

## Prepare your solution for Email Builder

To begin, install the **Kentico.Xperience.Mjml** and **Microsoft.AspNetCore.Components.Web** NuGet packages in your solution.

> **Tip:** Make sure to install the version of **Kentico.Xperience.Mjml** that matches your other Kentico NuGet packages (30.11.1 in this example).

The process may vary depending on your approach. In the _Training guides_ repository, you can simply edit the **Directory.Packages.props** file.

```xml title="Directory.Packages.props"
<Project>
  ...
  <ItemGroup>
    ...
    <PackageVersion Include="kentico.xperience.mjml" Version="30.11.1" />
    
    <PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="8.0.14" />
...
```

## Copy the Starter Kit to your solution

Clone or download the [Email Builder Starter Kit repository](https://github.com/Kentico/xperience-by-kentico-email-builder-starter-kit) from GitHub.

Copy the entire **Kentico.Xperience.Mjml.StarterKit.Rcl** directory from the **src** folder of the _Email Builder Starter Kit_ repository to your project, for example, into the **src** folder of the _Training guides_ repository. Remember to include the Starter Kit's project, **Kentico.Xperience.Mjml.StarterKit.Rcl.csproj**, in your solution. Your development environment likely has a way to do this in the UI, but it is also possible through the .NET CLI:

```cmd title=".NET CLI, running from the /src folder of the Training guides repo"
dotnet sln TrainingGuides.sln add Kentico.Xperience.Mjml.StarterKit.Rcl\Kentico.Xperience.Mjml.StarterKit.Rcl.csproj
```

> **Info:** Depending on the configuration of your project and environment, you may need to modify the Starter Kit's project file. For the _Training guides_ solution, we need to disable default embedded resources to avoid build errors.
>
> ```xml title="Kentico.Xperience.Mjml.StarterKit.Rcl.csproj"
> ...
> <PropertyGroup>
>     ...
>     <EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
> </PropertyGroup>
> ...
> ```

## Map the Starter Kit's models to your content types

The Starter Kit repository includes **Product** and **Image** widgets.

However, there is no way to guarantee that product and image entities are represented by identical content types in every project, so we need to create a [mapping](https://github.com/Kentico/xperience-by-kentico-email-builder-starter-kit/blob/main/docs/Usage-Guide.md#implement-image-and-product-model-mappers) between the models used by the Starter Kit widgets and our project's content types. For this example, we will use the **Product widget** to display **ServicePage** items, and we will use the **Image widget** to display **Gallery image** and **Asset** items.

> **Info:** The Starter Kit repository contains [example mappers for the Dancing Goat project](https://github.com/Kentico/xperience-by-kentico-email-builder-starter-kit/tree/main/examples/DancingGoat/Samples/EmailComponents) for reference.

The Starter Kit also includes an email template that utilizes the _subject_ and _preview text_ of the email to which it applies, regardless of the email's content type. Since these fields are not part of a shared interface, we need to create a [data mapper](https://github.com/Kentico/xperience-by-kentico-email-builder-starter-kit/blob/50d7b172c44c283644b04896abfbb028d6a46e73/docs/Usage-Guide.md#multiple-email-content-types) to extract them from each possible content type.

### Implement the model mappers

To use the _Image_ and _Product_ widgets, we need to implement the `IComponentModelMapper<TModel>` interface for the `ImageWidgetModel` and `ProductWidgetModel` types. These implementations tell the Starter Kit how to populate the widget models from our _ServicePage_, _GalleryImage_, and _Asset_ content types.

In case we need to update and overwrite the Email Builder Starter Kit files in the future, let's minimize any changes to that project. Otherwise, we might lose them when overwriting or replacing the folder.

In the _TrainingGuides.Web_ project, create a _\~/Features/Shared/EmailBuilder/Mappers_ folder to hold our implementations.

#### Image model mapper (simple content type)

The _Training guides_ repo has two reusable content types that primarily represent images: **Asset** and  **Gallery image**.

However, the _Gallery image_ type simply wraps the _Asset_ type with additional properties. The image file associated with a _Gallery image_ always belongs to the _Asset_ it links, so we don't need to worry about handling gallery images here.

> **Note:** You can also map multiple content types to a single Starter Kit model, but it is not necessary for this example.

```csharp title="ImageEmailWidgetModelMapper.cs"
using Kentico.Xperience.Mjml.StarterKit.Rcl.Mapping;
using Kentico.Xperience.Mjml.StarterKit.Rcl.Widgets;
using TrainingGuides.Web.Features.Shared.Services;

namespace TrainingGuides.Web.Features.Shared.EmailBuilder.Mappers;

public class ImageEmailWidgetModelMapper : IComponentModelMapper<ImageWidgetModel>
{
    private readonly IContentItemRetrieverService contentItemRetrieverService;

    public ImageEmailWidgetModelMapper(
        IContentItemRetrieverService contentItemRetrieverService)
    {
        this.contentItemRetrieverService = contentItemRetrieverService;
    }

    public async Task<ImageWidgetModel> Map(Guid itemGuid, string languageName)
    {
        var asset = await contentItemRetrieverService.RetrieveContentItemByGuid<Asset>(
                itemGuid,
                languageName: languageName);

        if (asset is null)
        {
            return new ImageWidgetModel();
        }

        string imageUrl = asset.AssetFile?.Url ?? string.Empty;

        return new ImageWidgetModel()
        {
            // Populate the image URL and alt text from the retrieved content item's fields
            ImageUrl = imageUrl,
            AltText = asset?.AssetAltText ?? string.Empty
        };
    }
}
```

#### Product model mapper (composable content type)

The **ServicePage** content type in the _Training guides_ repo is [composable](https://docs.kentico.com/guides/architecture/content-modeling/content-modeling-guide/store-content.md#leverage-linked-content-items). It wraps a reusable _Service_ content type, which itself is made up of other content types like _Service feature_, _Benefit_, and _Asset_. If we compare the _Service_ content type with the properties of the **ProductWidgetViewModel** from the Starter Kit, we can see that **Service** and **Asset** are the only parts of _ServicePage_ that we need.

![Diagram of content type relationships](https://docs.kentico.com/docsassets/modules/prepare-your-project-for-the-starter-kit/ServicePageStructure.png "Diagram of content type relationships")

When you query the service page, remember to set the `depth` appropriately, in order to handle this content model.

```csharp title="ProductEmailWidgetModelMapper.cs"
using Kentico.Xperience.Mjml.StarterKit.Rcl.Mapping;
using Kentico.Xperience.Mjml.StarterKit.Rcl.Widgets;
using TrainingGuides.Web.Features.Shared.Services;

namespace TrainingGuides.Web.Features.Shared.EmailBuilder.Mappers;

public class ProductEmailWidgetModelMapper : IComponentModelMapper<ProductWidgetModel>
{

    private readonly IContentItemRetrieverService contentItemRetrieverService;

    public ProductEmailWidgetModelMapper(
        IContentItemRetrieverService contentItemRetrieverService)
    {
        this.contentItemRetrieverService = contentItemRetrieverService;
    }

    // Here we set a very specific name for the Guid parameter to clarify that the web page's ContentItemGuid should be used instead of WebPageItemGuid
    public async Task<ProductWidgetModel> Map(Guid webPageItemContentItemGuid, string languageName)
    {
        var page = await contentItemRetrieverService.RetrieveWebPageByContentItemGuid<ServicePage>(
            contentItemGuid: webPageItemContentItemGuid,
            depth: 2,
            languageName: languageName);

        var service = page?.ServicePageService.FirstOrDefault();

    // If the service or page is null, return an empty model. Note that the service will always be null if the page is null.
        if (service is null)
        {
            return new ProductWidgetModel();
        }

        string webPageItemUrl = page.GetUrl().AbsoluteUrl;

        var image = service.ServiceMedia.FirstOrDefault();
        string imageUrl = image?.AssetFile?.Url ?? string.Empty;

        return new ProductWidgetModel
        {
            Name = service.ServiceName,
            Description = service.ServiceDescription,
            Url = webPageItemUrl,
            ImageUrl = imageUrl,
            ImageAltText = image?.AssetAltText ?? image?.AssetDescription ?? string.Empty,
        };
    }
}
```

### Create the data mapper

Next, we can move on to the data mapper.

First, define the class as an implementation of `IEmailDataMapper`, and define default values for the _subject_ and _preview text_.

Then, set up a private method for each email content type in the project, and call the appropriate one based on the content type name in the email context.

The methods can share logic that falls back to the appropriate default value if a value is `null`.

> **Tip:** If you have more type-specific requirements for the fallbacks, provide more tailored values in the methods for each type.

```csharp title="TrainingGuidesEmailDataMapper.cs"
using Kentico.EmailBuilder.Web.Mvc;
using Kentico.Xperience.Mjml.StarterKit.Rcl.Contracts;
using Kentico.Xperience.Mjml.StarterKit.Rcl.Mapping;

namespace TrainingGuides.Web.Features.Shared.EmailBuilder.Mappers;

public class TrainingGuidesEmailDataMapper : IEmailDataMapper
{
    private const string DEFAULT_SUBJECT = "Training guides";
    private const string DEFAULT_PREVIEW_TEXT = "The latest communication from Training guides.";
    private readonly IEmailContextAccessor emailContextAccessor;

    public TrainingGuidesEmailDataMapper(IEmailContextAccessor emailContextAccessor)
    {
        this.emailContextAccessor = emailContextAccessor;
    }

    public async Task<IEmailData> Map()
    {
        var emailContext = emailContextAccessor.GetContext();

        return emailContext.ContentTypeName switch
        {
            // Newsletter email content type
            BasicEmail.CONTENT_TYPE_NAME => await MapBasicEmail(emailContext),

            // Promotional email content type
            NatureSpotlightEmail.CONTENT_TYPE_NAME => await MapNatureSpotlightEmail(emailContext),

            // Subscription confirmation email content type
            SubscriptionConfirmationEmail.CONTENT_TYPE_NAME => await MapSubscriptionConfirmationEmail(emailContext),

            // Default fallback for unknown content types
            _ => GetEmailData(DEFAULT_SUBJECT, DEFAULT_PREVIEW_TEXT)
        };
    }

    /// <summary>
    /// Maps a BasicEmail content item to an EmailData object, using its subject and preview text.
    /// </summary>
    /// <param name="emailContext">The email context containing the BasicEmail item.</param>
    /// <returns>EmailData with subject and preview text from the BasicEmail item, or default values if null.</returns>
    private async Task<EmailData> MapBasicEmail(EmailContext emailContext)
    {
        var email = await emailContext.GetEmail<BasicEmail>();

        return GetEmailData(email?.EmailSubject, email?.EmailPreviewText);
    }

    /// <summary>
    /// Maps a NatureSpotlightEmail content item to an EmailData object, using its subject and preview text.
    /// </summary>
    /// <param name="emailContext">The email context containing the NatureSpotlightEmail item.</param>
    /// <returns>EmailData with subject and preview text from the NatureSpotlightEmail item, or default values if null.</returns>
    private async Task<EmailData> MapNatureSpotlightEmail(EmailContext emailContext)
    {
        var email = await emailContext.GetEmail<NatureSpotlightEmail>();

        return GetEmailData(email?.EmailSubject, email?.EmailPreviewText);
    }

    /// <summary>
    /// Maps a SubscriptionConfirmationEmail content item to an EmailData object, using its subject and preview text.
    /// </summary>
    /// <param name="emailContext">The email context containing the SubscriptionConfirmationEmail item.</param>
    /// <returns>EmailData with subject and preview text from the SubscriptionConfirmationEmail item, or default values if null.</returns>
    private async Task<EmailData> MapSubscriptionConfirmationEmail(EmailContext emailContext)
    {
        var email = await emailContext.GetEmail<SubscriptionConfirmationEmail>();

        return GetEmailData(email?.EmailSubject, email?.EmailPreviewText);
    }

    /// <summary>
    /// Returns EmailData with the provided subject and preview text values, reverting to default values if they are not passed or null.
    /// </summary>
    /// <param name="subject">The subject of the email</param>
    /// <param name="previewText">The preview text of the email</param>
    /// <returns>EmailData using the provided subject and preview text</returns>
    private EmailData GetEmailData(string? subject = null, string? previewText = null) =>
        new(subject ?? DEFAULT_SUBJECT, previewText ?? DEFAULT_PREVIEW_TEXT);
}
```

> **Tip:** We recommend handling **all of the available email content types**, even if they aren't currently allowed by any templates that use a data mapper.
>
> It is possible to call an `IEmailDataMapper` implementation from several different places in code, so it's best to be prepared for potential future changes.

### Register the mappers

Now that the mappers are ready, let's register them with the dependency injection container.

In our example, we can simply add them to the `AddTrainingGuidesServices` method of the `ServiceCollectionExtensions` class.

```csharp title="ServiceCollectionExtensions.cs"
using Kentico.Xperience.Mjml.StarterKit.Rcl.Mapping;
using Kentico.Xperience.Mjml.StarterKit.Rcl.Widgets;
using TrainingGuides.Web.Features.Shared.EmailBuilder.Mappers;
...
namespace TrainingGuides.Web;

public static class ServiceCollectionExtensions
{
    public static void AddTrainingGuidesServices(this IServiceCollection services)
    {
        ...
        services.AddScoped<IComponentModelMapper<ImageWidgetModel>, ImageEmailWidgetModelMapper>();
        services.AddScoped<IComponentModelMapper<ProductWidgetModel>, ProductEmailWidgetModelMapper>();
        services.AddScoped<IEmailDataMapper, TrainingGuidesEmailDataMapper>();
        ...
    }
    ...
}
```

The **Program.cs** file calls this method on startup, registering all of the mappers.
