The actual content inside the email editor is always contained inside an Email Control
, some core controls are:
These controls are displayed in the toolbox on the right-hand side inside the email editor.
It's possible to create custom Email Controls and use them for both Campaigns and Transactional emails. Follow this guide to create your own control. Our example is a control that allows for custom Hello-message to be pasted into the email using a text area, but the possibilities are endless.
This guide will build a custom control, the full source can be found in the Newsletter Studio Contrib-project on Github.
Start by creating a this folder inside your Umbraco-project: Extensions/EmailEditorControl
, inside this folder we need to create some C#-files for our custom control.
This model contains the data that your control must store, which could be raw HTML (as for the Rick Text-control) or image-urls, links, etc. Each time the control is used inside an email this model will be used to serialize and store the data.
Create a file called HelloEmailControlData.cs
with the following content:
using NewsletterStudio.Core.Editor.Models;
namespace Demo.Web.Extensions.EmailEditorControl;
public class HelloEmailControlData : EmailControl
{
public override string ControlTypeAlias => HelloControlConstants.Alias;
public string Text { get; set; } = "";
public Padding Padding { get; set; } = Padding.Default;
}
The view model is used when the Razor-engine renders the email, this is what will get passed down to the cshtml-view when the actual email is rendered.
In the same folder, create a file called HelloEmailControlViewModel.cs
:
using NewsletterStudio.Core.Rendering.ViewModels;
namespace Demo.Web.Extensions.EmailEditorControl;
public class HelloEmailControlViewModel : EmailControlViewModelBase
{
public string Text { get; set; } = "";
}
The Control Type is the main definition for the Email Control, this class is responsible for processing data and also holds important meta-data about the Email Control (like alias, icon, etc.).
Create a file called HelloEmailControlType.cs
:
using NewsletterStudio.Core.Editor.Controls;
using NewsletterStudio.Core.Editor.Models;
using NewsletterStudio.Core.Models;
using NewsletterStudio.Core.Rendering;
namespace Demo.Web.Extensions.EmailEditorControl;
//NOTE! Formatting here is made to fit the snippets on the website to avoid horizontal scrolling
public class HelloEmailControlType
: EmailControlTypeBase<HelloEmailControlData, HelloEmailControlViewModel>
{
public override string Alias => HelloControlConstants.Alias;
public override string IconSvg { get; }
public override string Icon => "icon-alarm-clock";
public override HelloEmailControlViewModel DoBuildViewModel(
HelloEmailControlData model,
EmailControlRenderingData renderingData
)
{
// This method will be called once for each Campaign, this can be used
// to perform work that is not unique to the recipient. In our case the
// content of the textarea will always be the same so we can safely perform
// all our work here.
var vm = new HelloEmailControlViewModel();
vm.Text = model.Text;
return vm;
}
public override HelloEmailControlViewModel DoUpdateUniqueViewModel(
HelloEmailControlViewModel model,
IRecipientDataModel recipient
)
{
// This method is called once for every recipient, the "model" object
// will be a clone of the result from the DoBuildViewModel-method.
// Here we could update specific properties based on the recipient.
// In our case we don't need to do this.
if (recipient is SubscriberRecipientDataModel subscriberRecipient)
{
// A Campaign in sending
// Do something with SubscriberRecipientDataModel
}
if (recipient is TransactionalRecipientDataModel transactionalRecipient)
{
// A Transactional Email is sending
// Do something with TransactionalRecipientDataModel
}
return model;
}
public override HelloEmailControlData BuildEmptyInstance()
{
// This method is called to create an empty instance of the model,
// used to set default values when the control is dragged from the
// toolbox into the email.
return new HelloEmailControlData()
{
Text = "Default data..."
};
}
public override void DoPrepareForEdit(
HelloEmailControlData control
)
{
// This method is called when the editor is loaded, here we can pass
// meta-data to the control the meta data is not saved but only used
// for the front end editing
control.Meta.Add("addedBy","customCSharpCode");
base.DoPrepareForEdit(control);
}
public override bool ShouldAppearInToolbox(
EmailType emailType
)
{
// This could be used to limit the usage of a control type to only be available
// to campaigns or transactional emails.
return true;
}
}
After these classes have been created you need to tell Newsletter Studio about the EmailControlType, create a Composer and append it to the list of ControlTypes.
using NewsletterStudio.Core.Composing;
using Umbraco.Cms.Core.Composing;
namespace Demo.Web.Extensions.EmailEditorControl;
public class HelloEmailControlComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.NewsletterStudio().EmailControlTypes.Append<HelloEmailControlType>();
}
}
When this is done the Email Control Type should show up in the email editor toolbox, otherwise, you might need to go to "Administration -> (Workspace)" and configure the "Allowed Email Controls".
At this point you need a setup for backoffice extensions. The official umbraco documentation has a guide on how to setup the tools needed. There are plenty of ways to setup a frontend development environment for Umbraco, in this guide we'll use a fairly common approach that includes using TypeScript and Lit. You can have a look at the Client
-folder of our the NewsletterStudioContrib-project for inspiration.
From now on we'll assume that you already have the frontend environment setup in the /Client
folder of the web-project and we'll also assume that you have basic knowledge about extending the backoffice, like registering extensions etc.
First we need to ensure that we have localization entries for our new control, add a localization
-extension and register the manifest into the backoffice.
This will give a friendly name to our newly created control:
import type { UmbLocalizationDictionary } from '@umbraco-cms/backoffice/localization-api';
export default {
ns : {
control_hello : 'Hello'
}
} as UmbLocalizationDictionary
Note the alias, it's a combination of "ns_control_" and the alias of the Email Control Type. This will ensure that English backoffice users get the right text in the UI, of course, you can use any language you like, this is standard Umbraco localizations.
If your workspace configuration allows for it, the new control icon should now appear in the email editor toolbox. You should be able to drag it into the content of the email.
We now need to add two new extension to handle the visual representation inside the email editor, these extensions are of the types:
First, we need to declare a typescript model for the data that our control needs to work with, let's create a file called demo-email-editor-control-hello.models.ts
with the following content:
import { NsEmailEditorControlModelWithPaddingBase } from "@newsletterstudio/umbraco/extensibility";
export type NsEmailEditorControlHelloData = {text : string} & NsEmailEditorControlModelWithPaddingBase;
Let's start with the edit view, in your project add a file called demo-email-editor-control-hello-edit.element.ts
with this content:
import { NsEmailEditorControlEditUiBase } from "@newsletterstudio/umbraco/extensibility";
import { NsEmailEditorControlHelloData } from "./demo-email-editor-control-hello.models";
import { css, customElement, html } from "@umbraco-cms/backoffice/external/lit";
@customElement('demo-email-editor-hello-edit')
export class DemoEmailEditorHalloEditElement extends NsEmailEditorControlEditUiBase<NsEmailEditorControlHelloData>
{
#updateText(e : InputEvent){
this.updateModel({
text : (e.target as HTMLTextAreaElement).value
});
}
//TODO: Update event parameter to use NsPaddingChangedEvent
render(){
return html`
<div>
<ns-property label="Enter text">
<textarea .value=${this.model?.text ?? ''} @keyup=${this.#updateText}></textarea>
</ns-property>
<ns-property label="ns_margin">
<ns-padding-editor
id="padding"
.value=${this.model!.padding}
@change=${(e : any)=>this.updateModel({padding:e.detail})}></ns-padding-editor>
</ns-property>
</div>
`;
}
static styles = css`
textarea {
width:100%;
min-height:250px;
}
`
}
export default DemoEmailEditorHalloEditElement;
declare global {
interface HTMLElementTagNameMap {
'demo-email-editor-hello-edit': DemoEmailEditorHalloEditElement;
}
}
Register this component as a extension of type nsEmailControlEditUi
:
import { ManifestEmailControlEditUi } from '@newsletterstudio/umbraco/extensibility'
const editUi : ManifestEmailControlEditUi = {
type: 'nsEmailControlEditUi',
alias : 'nsEmailControlEditUiHello',
name : 'NS Email Control Edit Ui Hello',
element : ()=> import('./demo-email-editor-control-hello-edit.element.js'),
meta : {
controlTypeAlias : 'hello'
}
}
Notice here that we're extending NsEmailEditorControlEditUiBase
which is a base class for edit views. It provides access to some methods to work with the control data, for example updateModel()
, that takes a partial instance of the data class and updates the model that will be stored.
In our example the data looks something like this
{
"controlTypeAlias": "hello",
"text": "....",
"rowAndColumnInfo": {
"columns": 4,
"stackOnSmallScreen": false
},
"meta": {
"addedBy" : "customCSharpCode"
}
}
The "meta"-property can be used to pass meta-data from the server to the email control and is not stored in the database. See the C#-example above where we're adding the addedBy
property to the meta-object.
Next, we need to make sure that the preview is rendering the content from the textarea, create a file called demo-email-editor-control-hello-display.element.ts
that looks like this:
import { NsEmailEditorControlDisplayUiBase } from "@newsletterstudio/umbraco/extensibility";
import { NsEmailEditorControlHelloData } from "./demo-email-editor-control-hello.models";
import { customElement, html, styleMap } from "@umbraco-cms/backoffice/external/lit";
@customElement('demo-email-editor-hello-display')
export class DemoEmailEditorHalloDisplayElement extends NsEmailEditorControlDisplayUiBase<NsEmailEditorControlHelloData>
{
render(){
// Fetching the wrapperStyles (e.g. padding) form the base class
const wrapperStyles =
{
...this.getWrapperStyleMap(this.model)
}
return html`
<div style=${styleMap(wrapperStyles)}>
<p>Text: ${this.model?.text ?? ''}</p>
</div>
`;
}
}
export default DemoEmailEditorHalloDisplayElement;
declare global {
interface HTMLElementTagNameMap {
'demo-email-editor-hello-display': DemoEmailEditorHalloDisplayElement;
}
}
We also need to register this extension
import { ManifestEmailControlDisplayUi } from '@newsletterstudio/umbraco/extensibility'
const displayUi : ManifestEmailControlDisplayUi = {
type: 'nsEmailControlDisplayUi',
alias : 'nsEmailControlDisplayUiHello',
name : 'NS Email Control Display Ui Hello',
element : ()=> import('./demo-email-editor-control-hello-display.element.js'),
meta : {
controlTypeAlias : 'hello'
}
}
With all this in place you, should have a working Email Control for working in the email editor.
Finally, we need to create a Razor cshtml-file that will be used to render the HTML in the emails.
To provide a default rendering we recommend that you put the view in the App_Plugins/NewsletterStudioExtensions/Views/Controls
-folder. However, if you have a Custom theme, you could also place the view inside your themes Views\Controls
-folder.
In the Controls
-folder, create a file with the same name as your email control type alias, in our case hello.cshtml
.
@model NewsletterStudio.Core.Rendering.ViewModels.ControlWithEmailViewModel
@{
var settings = Model.Email.Settings;
}
@if (Model.Model is Demo.Web.Extensions.EmailEditorControl.HelloEmailControlViewModel controlModel)
{
<div>
@controlModel.Text
</div>
}