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
        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
        buttons: {
            header: [], // an array of objects describing the content of the dropdown in the top right corner of the editor
            textInsert: []
        },
        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
            },
            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
        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
            }
        }
    },
    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);

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.
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.
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 onClicked 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.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: visibe, 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.

Example:

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

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.hooks

Each and every hook is supposedly an asychrounus 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 occured 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

};

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 here the logic 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 = () => {
    return new Promise(resolve => {
        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 = "someting"; // 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 mergetags.

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

Has to resolve:
- value: The string you want to insert.
*/
editorConfig.hooks.onTextInsertPluginButtonClicked = ({ buttonId }) => {
    return new Promise(resolve => {
        // Here, you can implement your custom dialog
        resolve({ value: "Your inserted text." });
    });
};

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 inputfield.

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

Has to resolve:
- expression: Your expression to insert into the editor's toolbox inputfield.
*/
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>" });
    });
};

Example

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