Email Editor Plugin

You can simply initialize the editor by passing a config object to the editEmail function. The dropdownButtons, blockLibraries, and hooks properties are detailed later in this section.

const editorConfig = {
    document: {}, // a JSON object that represents the email document
    user: { // or false to turn off the user avatar
        id: "5c6bfaba1f848b135dc46c41", // Id of the current user
        name: "John Doe", // the display name of your user
        avatar: "https://example.com/avatar.png" // the avatar of your user
    },
    settings: {
        staticAssetsBaseUrl: "https://yourdomain.com/path/to/static/assets/", // there are a few image assets that should be hosted on your side
        videoElementBaseUrl: "https://yourdomain.com/path/to/video/backend/", // the video element requires a backend that should be hosted on your side
        buttons: {
            header: [], // an array of objects describing the top right corner buttons of the plugin
            textInsert: [] // an array of objects describing the buttons on the inline text editor
        },
        elements: { // or false, to turn off the elements tab (so only the blocks will be visible)
            content: { // or false, to hide the whole element group
                text: true,
                image: true,
                button: true,
                divider: true,
                social: false,
                code: false,
                video: false,
            },
            structure: { // or false, to hide the whole element group
                fullWidth: false,
                box: true,
                multiColumn: true
            },
            advanced: { // or false, to hide the whole element group
                loop: true,
                conditional: true,
                dynamicImage: true
            }
        },
        blockLibraries: [], // an array of block library descriptors
        fontFiles: { // an object, keys are the names of the font families, the values are the URLs to the font files.
          "Poppins": "https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,400;0,700;1,400;1,700&display=swap",
          "Zen Tokyo Zoo": "https://fonts.googleapis.com/css2?family=Zen+Tokyo+Zoo&display=swap"
        },
        fontStacks: [ // an array of arrays, the inner array contains the font families as strings in the font stack
          ["Poppins", "Helvetica Neue", "Helvetica", "Arial", "sans-serif"]
        ],
        hideDefaultFonts: true, // if true, the default built-in font stacks are hidden, if false, the custom font stacks are added to the default ones.
        addons: {
            blockLock: { // or false to fully turn off (hide) the addon
                enabled: false // enable / disable the addon
            },
            variableSystem: { // or false to fully turn off (hide) the addon
                enabled: false // enable / disable the addon
            }
        },
        actionMenu: { // control of different action menus
            block: { // if false the Block Menu is turned off (floating menu right side each block)
                drag: true, // you can turn off each button individually
                save: true, // the save option automatically hidden when no blockLibraries added to the editor instance
                duplicate: true,
                delete: true,
            }
        },
        toolboxes: { // each element's toolboxes can be hidden
            body: true, // if false the element is not customizable in the Editor instance
            fullWidth: true,
            text: true,
            button: true,
            box: true,
            multiColumn: true,
            image: true,
            divider: true,
            code: true,
            social: true,
            column: true,
            loop: true,
            conditional: true,
            dynamicImage: true,
            video: true,
            blockLevelConditional :true,
            blockLevelLoop: true,
        },
        dropzones: {
            block: true, // when false dropzones above and under blockes are turned off. No new element can be created by drag and drop, but inside each block
        },
    },
    hooks: {} // an object of available hooks (for example onSave)
};

const editorInstance = await chamaileonPlugins.editEmail(editorConfig);

Editor instance functions

You can call the editorInstance functions at any time, but most likely you will use them in event hooks.

getDocument

This function will resolve the current state of the document as a JSON object. You can save it and reload the editor (or the preview) with this JSON later on. You can also invoke our generator with this JSON object from your backend.

const emailJson = await editorInstance.getDocument();

getEmailHtml

You can generate the email HTML version of the document with this function. You can do it any time, but I suggest you not to do it on each and every change, because you would hit our rate limits pretty soon.

const emailHtml = await editorInstance.getEmailHtml();
console.log(emailHtml);

setDocument

This function will replace the current document in the editor with a new one. You can for example update the document on a hook that you configured.

await editorInstance.setDocument({ document });

updateSettings

This function will replace the current settings object in the editor with a new one. The format of the settings object is the same that you pass when you open the editor. It means that you can reconfigure on the fly what settings you want to use.

You can update the settings when your user clicks one of the previously configured buttons.

await editorInstance.updateSettings(settings);

close

Closes the editor.

await editorInstance.close();

Editor configuration

As you have seen previously, some of the configuration values are pretty self-explanatory, some of them needs somewhat more clarification. The config object you pass to the editEmail function, has the following properties:

Property Type Description
document object The document descriptor object. You might want to save it as a JSON object.
user object or null You can define how the user will be displayed in the editor. The name property of this object will be the name displayed when you hover on the user's avatar. The avatar property is the URL or the user's profile picture. If the value is null then the avatar won't be displayed.
settings object The settings of the editor instance.
hooks object You can register callbacks on multiple events coming from the editor. For more, please check out the editorConfig.hooks section.

editorConfig.settings

The settings object has the following properties:

Property Type Description
staticAssetsBaseUrl string This parameter sets the base URL of the default image and the social icon images. You can find those images in the following repository and should host on your domain: https://github.com/chamaileon-sdk/static-content If you are not hosting these images on your domain, that can negatively impact the deliverability of your emails.
videoElementBaseUrl string This parameter sets the base URL of the video element. This should point to the hosted video backend. If you are not hosting this backend on your domain, then the video element will not work.
autoSaveInterval number This optional parameter sets the frequency of autosaving in milliseconds. The onAutoSave hook will be called when autosave happens. If the value is not given, the autosave hook will not be triggered.
buttons object The configuration of the header buttons and textInsert buttons.
blockLibraries array You can provide multiple block libraries for your users, with different access-level. See the editorConfig.blockLibraries section for more.
fontFiles object An object, containing the custom font resources. Keys are the font names, values are the URLs.
fontStacks array An array of array, containing the font stacks.
hideDefaultFonts boolean If it's true, the default built-in font stacks are hidden, if it's false, the custom font stacks are added to the default ones.
addons object Addon configurations

editorConfig.settings.buttons.header

You can configure the buttons in the top right corner of the editor. You can set up their icons, labels and ids. If a user clicks on them, then the onHeaderButtonClicked hook will be called with the id of the button. With this option, you can add custom functionality to the editor. For example, you can create custom dialogs. You can also configure dropdowns.

Example:

editorConfig.settings.buttons.header = [
    {
        id: 'preview',
        type: 'button',
        icon: 'eye',
        label: 'Preview',
        color: '#aaaaaa',
        style: 'text' // filled, depressed (no shadow filled), outlined, text, plain
    },
    {
        type: 'dropdown',
        icon: 'dog',
        label: 'Dropdown',
        color: "secondary",
        style: 'outlined',
        items: [  // if any button has a items field will generate a dropdown button, and only the items get callbacks
            {
                id: "share-email",
                label: "Get shareable link",
                icon: "share",
            },
            {
                id: "send-test-email",
                label: "Send test email",
                icon: "email",
            },
            {
                id: "request-review",
                label: "Request a review",
                icon: "comment-eye",
            },
        ]
    }
];

You can also use the onClick property of buttons. There is only one function within the editor that you can trigger this way. It is called openVariableModal which will open the modal on which you can add, remove or edit variables. (You need to have the variable system addon enabled on your API key.)

editorConfig.settings.buttons.textInsert

You can add clickable buttons into the CKEditor, to insert your custom text. The onTextInsertPluginButtonClicked hook will be called, when you click on one of the buttons. This is a great way to provide merge tags to your users.

The textInsertPluginButtons array consists of objects with the following properties:

Property Type Description
id string The id of the button. The onTextInsertPluginButtonClicked hook will get this value as a parameter.
label string This will be displayed in the CKEditor header.
icon string (optional) You can set the CKEditor icon with an image src. If blank or undefined a text button will be created with the given label.

Example:

editorConfig.buttons.textInsert = [
    {
        id: "insert-tag",
        label: "Custom tags",
        icon: "https://raw.githubusercontent.com/ckeditor/ckeditor4/major/skins/kama/icons/paste.png",
    }
]

editorConfig.settings.elements

This is an object that let's you to turn off some (or all) of the elements draggable from the left-hand-side.

Example:

editorConfig.settings.elements = { // or false, then the elements tab will not be visible
    content: { // or false, then the content elements section will not be visible
        text: true, // true: visible, false: hidden
        image: true,
        button: true,
        social: true,
        divider: true,
        code: true
    },
    structure: { // or false, then the structure elements section will not be visible
        box: true,
        multiColumn: true
    },
    advanced: { // or false, then the advanced personalization elements section will not be visible
        loop: true,
        conditional: true,
        dynamicImage: true
    }
}

editorConfig.settings.blockLibraries

You can also configure block libraries, with the blockLibraries array. They will appear in two parts of the editor:

  • on the left hand side in a dropdown when you selected the blocks tab
  • and when a user wants to save a block, also in a dropdown.

This way, you can provide multiple block libraries to your users and can allow them to save their blocks into different libraries.

When a user selects a block library on the left hand side, the onLoadBlocks hook will be called with the id of the library. This way, you can provide as many block libraries to your clients as you want.

The blockLibraries array consists of objects with the following properties:

Property Type Description
id string The id of the block library. The onLoadBlocks hook will get this value as a parameter.
label string This will be displayed in the forementioned dropdowns.
canDeleteBlock boolean If true, the actual user will be able to delete from this block library. If false, the delete button is not shown.
canRenameBlock boolean If true, the actual user will be able to rename blocks in this block library. If false, the rename functionality is disabled.
canSaveBlock boolean If true, the actual user will be able to save blocks to this this block library. If false, then this block library will not be shown to the user when they try to save a block.

Example:

editorConfig.blockLibraries = [
    {
        id: "email-blocks",
        label: "Email's blocks",
        canDeleteBlock: false,
        canRenameBlock: false,
        canSaveBlock: false,
    },
    {
        id: "john-doe-favs",
        label: "John Doe's Favorite Blocks",
        canDeleteBlock: true,
        canRenameBlock: true,
        canSaveBlock: true,
    }
]

editorConfig.settings.fontFiles

To display a new font in the email editor and your emails (depending on email clients), you need to provide a font URL under the font URL settings.

Every font URL should include the following font styles:

  • Regular 400
  • Regular 400 italic
  • Bold 700
  • Bold 700 italic

As an example, the URL for Poppins would look like this:

https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,400;0,700;1,400;1,700&display=swap

Keys are the names of the font families, the values are the URLs to the font files.

Example:

editorConfig.settings.fontFiles = {
  "Poppins": "https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,400;0,700;1,400;1,700&display=swap"
};

editorConfig.settings.fontStacks

Once you added the required Font URL, you need to define the Font stack to ensure that email clients (like Gmail) that don't support custom fonts will display your text in a similar web-safe font.

This property is an array of arrays, the inner array contains the font families as strings in the font stack. The font stacks are displayed in the order as they are in the outer array.

Example:

editorConfig.settings.fontStacks: [
  ["Poppins", "Helvetica Neue", "Helvetica", "Arial", "sans-serif"]
];

In the HTML code, it will look like this:

"Poppins, Helvetica Neue, Helvetica, Arial, sans-serif"

editorConfig.settings.hideDefaultFonts

This is a boolean property. If the value is true, the default built-in font stacks are hidden, if it's falsy, the custom font stacks are added to the default ones.

editorConfig.settings.addons

Property Type Description
blockLock object or false Enabling or disabling the block lock addon.
variableSystem object or false Enabling or disabling the variable system addon.

Each of the addons can be an object or false. If you set it as false, the addon will not be visible. If you use an object, then there should be an enabled property on the addon. If it is true, then the addon is enabled, if its value is false, the addon will be visible, but disabled.

Example:

editorConfig.settings.addons = {
    blockLock: { // or false, then the addon will not be visible
        enabled: false // if it is false, then the addon will be disabled
    },
    variableSystem: { // or false, then the addon will not be visible
        enabled: true // if it is false, then the addon will be disabled
    }
}

The block lock addon allows users to lock the design or design & content of a block. If the block is locked, you won't be able to edit the layout or even the content of that block. You can find the toggle button for this feature in the "Block toolbox".

The variable system addons enables your users to define variables, and refer those variables in the email. For example, you can define a primaryColor variable, that you can use at multiple places. Whenever you update the value of the primaryColor variable, it will be automatically updated everywhere in the email where you refer its value.

editorConfig.settings.actionMenu.block

Property Type Description
block object or false Enabling or disabling blocks' actions.

Action menu fields can be objects or false. False will hide the whole menu. In the object each action can be disabled with a false value. When all actions are disabled, the menu will be hidden. All actionMenu items default value is true.

Example:

editorConfig.settings.actionMenu.block = {
    drag: true,
    save: true,
    duplicate: true,
    delete: true,
}

The block actions are designed to handle block level editing easily. You can duplicate, delete or drag blocks by pressing the action button. The save action only available if the blockLibraries array has at least one element defined.

editorConfig.settings.toolboxes

Property Type Description
*element true or false Enabling or disabling toolboxes for *element

Toolboxes' fields can be a Boolean value. False hides all toolboxes for the element type. Default value is true for all elements.

Example:

editorConfig.settings.actionMenu.toolboxes: {
    body: true,
    fullWidth: true,
    text: true,
    button: true,
    box: true,
    multiColumn: true,
    image: true,
    divider: true,
    code: true,
    social: true,
    column: true,
    loop: true,
    conditional: true,
    dynamicImage: true,
    video: true,
    blockLevelConditional :true,
    blockLevelLoop: true,
},

Toolboxes are located in the right side drawer in the editor. Each element can have multiple toolboxes, this options hide them all, make them not costumizable.

editorConfig.settings.dropzone

Property Type Description
block true or false Enabling or disabling blok level dropzone

When false, dropzones above and under blocks are turned off. No new element can be created by drag and drop, but inside each block.

Example:

editorConfig.settings.dropzone: {
    block: true,
},

editorConfig.hooks

Each and every hook should be an asynchronous process, so all of the hook handler functions has to return Promises. The SDK resolves these promises and sends back the result to the plugin.

For example, when a user clicks on the save button, a load indicator will start spinning, and the onSave hook is called. Until the promise is not resolved, the loading indicator will continue spinning.

In most of the cases you just have to resolve the promise when the async operation is done without any params, but in some cases you will have to resolve certain objects with properties that the editor plugin can use. Similarly to the parameters, we always expect an object to be resolved, even if it only has one property. (This way it will be easier to add new properties later on if needed.)

If any errors occurred on your side, you can reject the promise with an instance of Error. In this case, the error message will be shown in a snackbar in the editor.

function handler(params) {
    return new Promise((resolve, reject) => {
        // You can put the logic here.
        // Resolve the promise when everything is okay
        // Reject the promise on error

        if (!error) {
            resolve(dataToResolve) // In some cases, you don't have to resolve any data. You can resolve the promise without a parameter.
        } else {
            reject(new Error("Your error message"))
        }
    })
}

Note that, with the async syntax, the unexpected errors will be also displayed:

async function handler(params) {
    // Unexpected errors will also cause promise rejections in this case
    // For example, if you get a timeout error, that will also be displayed in a snackbar in the editor.
    // Any exception will be catched by the SDK and the message property of the error object will be shown in a snackbar.
    return dataToResolve
}

You can see below the hooks you can use. Read more about them in the following sections.

editorConfig.hooks = {
    onSave,
    onAutoSave,
    onChange,
    onBeforeClose,
    onAfterClose,

    onEditTitle,

    onEditImage,
    onEditBackgroundImage,

    onBlockSave,
    onLoadBlocks,
    onBlockRename,
    onBlockDelete,

    onHeaderButtonClicked,
    onTextInsertPluginButtonClicked,
    onExpressionEditClicked,

    onUserEvent
};

editorConfig.hooks.onSave

This function is called when the user clicks on the save button. The "save in progress" indicator will be spinning, until the returned promise is not resolved or rejected.

/*
Params:
 - emailJson: The object representation of the document.

Has to resolve: nothing.
*/
editorConfig.hooks.onSave = ({ emailJson }) => {
    return new Promise(resolve => {
        // you can put here the logic that saves the emailJson object
        // and when it's done, you can resolve the promise
        resolve();
    });
};

editorConfig.hooks.onAutoSave

This hook is very similar to the previous one. It is triggered when autosave happened, and it gets the same params as the previous one. The progress indicator will also be spinning until the promise is not resolved.

/*
Params:
 - emailJson: The object representation of the document.

Has to resolve: nothing.
*/
editorConfig.hooks.onAutoSave = ({ emailJson }) => {
    return new Promise(resolve => {
        // you can put the logic here that saves the emailJson object
        // and when it's done, you can resolve the promise
        resolve();
    });
};

editorConfig.hooks.onChange

This hook is invoked whenever something happened in the editor. It might be useful if you want to know if there were any changes since the last time you saved the document. For example, you can set a variable that shows that there were changes, and you can check it in the onBeforeClose hook.

/*
Params: nothing.

Has to resolve: nothing.
*/
editorConfig.hooks.onChange = ({ mutation }) => {
    return new Promise(resolve => {
        // you can put the logic here that saves the email mutation
        resolve();
    });
};

editorConfig.hooks.onBeforeClose

This hook is called right before the editor is closed. The editor will not be closed until you resolve the promise. You can use this hook if you want to save the current state of the document before closing (using editorInstance.getEmailJson).

Since this prevents the editor from closing, you will have to make sure to show a progress indicator if your saving process takes a long time.

/*
Params: nothing.

Has to resolve: nothing.
*/
editorConfig.hooks.onBeforeClose = () => {
    return new Promise(resolve => {
        resolve();
    });
};

editorConfig.hooks.onAfterClose

This hook is called when the editor is already closed. From this point in time, you will not be able to call the editor instance functions (getEmailJson and getEmailHtml), because the editor instance is destroyed.

/*
Params: nothing.

Has to resolve: nothing.
*/
editorConfig.hooks.onAfterClose = () => {
    return new Promise(resolve => {
        resolve();
    });
};

editorConfig.hooks.onEditTitle

Invoked when a user changes the title of the document. The progress indicator will be spinning until the promise is resolved.

/*
Params:
 - title: The title of the document, you previously set up in the editorConfig object

Has to resolve: nothing.
*/
editorConfig.hooks.onEditTitle = ({ title }) => {
    return new Promise(resolve => {
        resolve();
    });
};

editorConfig.hooks.onEditImage

This function is called when the user wants to edit an image. You can use this hook to pop up your gallery. This function has to be resolved with an object that has an src property. That will be the new src of the image.

This function has two optional parameters:

The first is the originalImage. If you get a string value in this parameter, that means that the user wants to edit that image, so you should initialize an image editor and resolve the promise with the modified image. If this value is undefined, that means that the user wants to change the image, so you might want to pop up an image selector.

The second is the lockDimensions. When defined, it means that the user wants to change an image in a block, which has a locked design. In this case, you will have to resolve an image with the exact same aspect ratio as defined by the width and height property of the lockDimensions object. (Also, the dimensions has to be at least as big as these values.) Usually it means, that you will have to use a crop tool if you don't have images with the proper aspect ratio.

When you resolve the promise with an src property on an object, the editor will set up that value as the new src of the image. This might be a long-running promise, since you will probably resolve it, when your user selects one of their images from your image library.

/*
Params:
 - originalImage: optional, string, shows that a user wants to edit an image
 - lockDimensions: optional, object, you have to crop the image to this aspect ratio
   - width
   - height

Has to resolve:
 - src
*/
editorConfig.hooks.onEditImage = ({ originalImage, lockDimensions: { width, height } }) => {
    return new Promise(resolve => {
        // Eventually, you will have to resolve an object with an src prop
        resolve({ src });
    });
};

editorConfig.hooks.onEditBackgroundImage

This hook is invoked when a user wants to change the background image of an element.

/*
Params: nothing.

Has to resolve:
 - src
*/
editorConfig.hooks.onEditBackgroundImage = () => {
    return new Promise(resolve => {
        // Eventually, you will have to resolve an object with an src prop
        resolve({ src });
    });
};

editorConfig.hooks.onLoadBlocks

This will be called when the user selects a block library and right after the editor is loaded. The editor will call this hook with one of the preconfigured block library ids. This way, you may provide multiple sets of blocks for your clients.

This hook is also called right after the editor is loaded, because we pre-load one of the block libraries.

The resolved blocks array has to contain the block objects. You can get those block objects in the onSaveBlock hook.

/*
Params:
 - libId: string id of the library from the editorConfig.blockLibraries array

Has to resolve:
 - blocks: array of block objects
*/
editorConfig.hooks.onLoadBlocks = ({ libId }) => {
    return new Promise(resolve => {
        // Eventually, you will have to resolve an object with an src prop
        const blocks = [];
        // Just put the block objects into the array
        resolve({ blocks });
    });
};

editorConfig.hooks.onBlockSave

Called when a user saves a new block. After you saved it to your backend, you have to resolve the promise with a block object on it. The block object has to have an _id property, that is the id of your database entry.

/*
Params:
 - libId: string id of the library from the editorConfig.blockLibraries array
 - block: the object representing a block

Has to resolve:
 - block: the final block object with an _id field
*/
editorConfig.hooks.onBlockSave = ({ libId, block }) => {
    return new Promise(resolve => {
        // Eventually, you will have to resolve an object with an src prop
        block._id = "something"; // You have to set up the _id of the block
        resolve({ block });
    });
};

editorConfig.hooks.onBlockRename

When the user renames a block, this hook will be invoked. You can save the changes in your db.

/*
Params:
 - libId: string id of the library from the editorConfig.blockLibraries array
 - block: the object representing a block
  - _id: the id of the block, you can update your db entry based on this
  - title: the new value of the block's title

Has to resolve: nothing.
*/
editorConfig.hooks.onBlockRename = ({ libId, block: { _id, title } }) => {
    return new Promise(resolve => {
        // Here, you can save the new title of the block -> resolve when done.
        resolve();
    });
};

editorConfig.hooks.onBlockDelete

Invoked when a user deletes a block from a block library.

/*
Params:
 - libId: string id of the library from the editorConfig.blockLibraries array
 - block: the object representing a block
  - _id: the id of the block, you can delete your db entry based on this

Has to resolve: nothing.
*/
editorConfig.hooks.onBlockDelete = ({ libId, block: { _id } }) => {
    return new Promise(resolve => {
        // Here, you can delete your entry and resolve the promise
        resolve();
    });
};

editorConfig.hooks.onHeaderButtonClicked

If you have set up header buttons in the config, you can use this hook to implement anything you like. (Most likely you will pop up a dialog.)

/*
Params:
 - buttonId: string id of the button from the editorConfig.dropdownButtons array

Has to resolve: nothing.
*/
editorConfig.hooks.onHeaderButtonClicked = ({ buttonId }) => {
    return new Promise(resolve => {
        // Here, you can implement your custom dialog
        resolve();
    });
};

editorConfig.hooks.onTextInsertPluginButtonClicked

When a user clicks on any of the configured buttons in the CKEditor header, you will be able to handle the actions with this hook, and insert your own custom text into the editor surface. This is a very handy feature for implementing merge tags.

/*
Params:
 - buttonId: string id of the button from the editorConfig.textInsertPluginButtons array

Has to resolve:
- value: The string you want to insert.

Can resolve:
- isHTML: true if the value contains a HTML snippet. Defaults to false.
*/
editorConfig.hooks.onTextInsertPluginButtonClicked = ({ buttonId }) => {
    return new Promise(resolve => {
        // Here, you can implement your custom dialog
        resolve({ value: "Your inserted text.", isHTML: false });
    });
};

editorConfig.hooks.onExpressionEditClicked

Loop, conditional and dynamic image elements have an expression option. If you have a list of expressions, you can pop up a dialog, and resolve the selected expression, and it will be added to the editor's expression input field.

/*
Params:
 - expression: The current selected expression in the input field.

Has to resolve:
- expression: Your expression to insert into the editor's toolbox input field.
*/
editorConfig.hooks.onExpressionEditClicked = ({ expression }) => {
    return new Promise(resolve => {
        // Here, you can implement your custom dialog with a list of expressions
        resolve({ expression: "<Your inserted expression>" });
    });
};

editorConfig.hooks.onUserEvent

You can get various information about user interaction on this hook. For example if the zoom was changed or a new element was dropped onto the canvas.

/*
Params:
 - userEvent: The current event information

Has to resolve: nothing.
*/
editorConfig.hooks.onUserEvent = ({ userEvent }) => {
    return new Promise(resolve => {
        // Here, you can save the event information
        resolve();
    });
};

Examples

We put together a few demos and you can check out the code here.

You can also check out the email variable editor plugin on the Chamaileon SDK Playground.