Serverless Chamaileon SDK

You can integrate some of the Chamaileon plugins without having to deal with server-side integration. This way, a limited set of functionality is avaliable for you: you will be able to edit and preview an email.

For advanced functionalities, you will need server-side integration as well. These functionalities are for example:

  • collaborative editing
  • access control
  • template selector
  • share link & approval workflow
  • and much more.

Initialize

In order to use the Chamaileon SDK, you will need to include it in your project via a <script> tag, but shortly, we are going to publish it to npm as well.

<script src="https://plugins.chamaileon.io/static/chamaileonSdk.js"></script>

You can initialize the SDK by invoking the init function on the chamaileonSdk object:

const chamaileonPlugins = await window.chamaileonSdk.init({
    environmentName: "<YOUR-WHITELABEL-CHAMAILEON-ENVIRONMENT>"
});

The environmentName parameter defines your whitelabel environment. Your custom theme and splascreen is tied to the environment itself.

The chamaileonPlugins object exposes multiple functions, but in the "serverless" mode, you can only use the follwing functions:

  • editEmail
  • previewEmail

Please read the following to sections for the description of these functions.

Edit email

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 = {
    title: "Test title", // the title of the document
    autoSaveInterval: 10000, // in milliseconds, optional
    emailJson: {}, // a JSON object that represents the email document
    user: {
        name: "John Doe", // the display name of your user
        avatar: "https://example.com/avatar.png" // the avatar of your user
    },
    dropdownButtons: [], // an array of objects describing the content of the dropdown in the top right corner of the editor
    blockLibraries: [], // an array of block library descriptors
    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.

getEmailJson

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.getEmailJson();

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
title string The title of the document.
autoSaveInterval number This optional parameter sets the frequency of autosaving in milliseconds. The onAutoSave hook will be called when autosave happens.
emailJson object The document descriptor object. You might want to save it as a JSON object.
user object 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.
dropdownButtons array You can configure the elements in the top right dropdown. Check the editorConfig.dropdownButtons section for details.
blockLibraries array You can provide multiple block libraries for your users, with different access-level. See the editorConfig.blockLibraries section for more.
hooks object You can register callbacks on multiple events coming from the editor. For more, please check out the editorConfig.hooks section.

editorConfig.dropdownButtons

You can configure the buttons in the dropdown 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 onDropdownButtonClicked 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.

The dropdownButtons array consists of objects with the following properties:

Property Type Description
id string The id of the button. You can use this id in the onDropdownButtonClicked hook to figure out which button was clicked by the user.
label string This value will be the text of that button in the dropdown.
icon string You can customize the icon of the button, you can use the icon names from material icons.

Example:

editorConfig.dropdownButtons = [
    {
        id: "send-test-email",
        label: "Send test email",
        icon: "envelope"
    },
    {
        id: "open-help-center",
        label: "Help center",
        icon: "account-question"
    }
];

editorConfig.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.
accessLevel string You can allow your users to be able to save a block by setting this value to "readWrite".

Example:

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

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,

    onDropdownButtonClicked
};
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 originalImg. 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:
 - originalImg: 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 = ({ originalImg, 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.onLoadBlocks = ({ 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.onLoadBlocks = ({ 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.onLoadBlocks = ({ libId, block: { _id } }) => {
    return new Promise(resolve => {
        // Here, you can delete your entry and resolve the promise
        resolve();
    });
};
editorConfig.hooks.onDropdownButtonClicked

If you have set up dropdown 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.onDropdownButtonClicked = ({ buttonId }) => {
    return new Promise(resolve => {
        // Here, you can implement your custom dialog
        resolve();
    });
};

Preview email

If you already have an emailJson object you saved from the editor, you can open the preivew with the following function call:

const previewInstance = await chamaileonPlugins.previewEmail({
    title: "The title of the document",
    emailJson: {} //email JSON - required    
});

Preview instance functions

There are not so many previewInstance functions.

close

Closes the preview.

await previewInstance.close();