Zimbra SkillZ: Empty Subject Zimlet

Hi Zimbra Customers, Partners & Friends,

Today’s Zimbra Skillz blog explains how to write a Zimlet that listens for an event using zimletEventEmitter. This particular Zimlet listens for the ONSEND event and warns the user if the Subject field is empty.

Downloading and Running the Empty Subject Zimlet

Create a folder on your local computer to store the Empty Subject Zimlet:

  mkdir ~/zimbra_course_pt17
  cd ~/zimbra_course_pt17
  git clone https://github.com/Zimbra/zimbra-zimlet-emptysubject
  cd zimbra-zimlet-emptysubject
  npm install
  zimlet watch

The output of this command should be:

Compiled successfully!

You can view the application in browser.

Local:            https://localhost:8081/index.js
On Your Network:

Visit https://localhost:8081/index.js in your browser and accept the self-signed certificate. The index.js is a packed version of the Empty Subject Zimlet. More information about the zimlet command, npm and using SSL certificates can be found in https://github.com/Zimbra/zm-zimlet-guide.

Have you used Zimlet CLI in the past? Make sure to update it using sudo npm install -g @zimbra/zimlet-cli. You can check your version using zimlet --version. You will need version 12.8.0 of the Zimlet CLI for this Zimlet to work.

Sideload the Empty Subject Zimlet

Log on to your Zimbra development server and make sure you see the modern UI. Then click the jigsaw puzzle icon and Zimlets Sideloader. If you don’t see the Zimlet Sideloader menu, run apt/yum install zimbra-zimlet-sideloader on your Zimbra server. This enables the Sideloader Zimlet in your Class of Service.

Sideload the Empty Subject Zimlet by clicking Load Zimlet. The Zimlet is now added to the Zimbra UI in real-time. No reload is necessary.

Sideload the Empty Subject Zimlet by clicking Load Zimlet. The Zimlet is now added to the Zimbra UI in real-time. No reload is necessary.

Write a new email and put something in the body of the email. Leave the Subject field empty and click Send. You’ll see the new Empty Subject Zimlet in action.

The Empty Subject Alert

The Empty Subject Alert

zimletEventEmitter events

Zimlets can register listeners that are provided via zimletEventEmitter. The following events are supported:


New events will be added to Zimbra soon, and the Zimlet guide will be updated when that happens.

After the user clicks the send button and when all ONSEND event handlers have resolved, the AFTERONSEND event is fired. At this point, the back-end will process the email for sending. This event cannot abort the sending, so it should always resolve. This event can be used for compliance, custom logging or custom REST API calls.

The LOGOUT event is fired when the user clicks the Logout menu item. It can be used to trigger a log-out in non Single Log Out aware 3rd party application.

The ONSEND event is fired when the user clicks the Send button when sending an email. It can be used for email error checks, such as a forgotten attachment reminder, or do a check in a 3rd party application for compliance validation.

The ONSENDINVITEREPLY is fired when a user RSVP’s to a calendar invitation. The verb and invitation are passed to the event handler. You can use the verb to determine if the user accepted, declined, proposed a new time or tentatively accepted the invitation. Define your handler like: onSendHandler = (args) ⇒ {console.log(args);}.

There can be two types of handlers.

  1. Handler doing synchronous tasks like calculating something, displaying toast or updating view/state. Here is an example of this kind of handler:
import { zimletEventEmitter } from '@zimbra-client/util';
import { ZIMBRA_ZIMLET_EVENTS } from '@zimbra-client/constants';

const onLogoutHandler = () => { /** Display toast message */ };
zimletEventEmitter.on(ZIMBRA_ZIMLET_EVENTS.LOGOUT, onLogoutHandler);
  1. Handler doing asynchronous tasks like invoking an API call or displaying a dialog to confirm the action with the user. Here is an example of this kind of handler:
import { zimletEventEmitter } from '@zimbra-client/util';
import { ZIMBRA_ZIMLET_EVENTS } from '@zimbra-client/constants';

const onLogoutHandler = () => new Promise((resolve, reject) => {
    if (window.confirm("Do you really want to logout?")) {
    } else {
zimletEventEmitter.on(ZIMBRA_ZIMLET_EVENTS.LOGOUT, onLogoutHandler, true);


Visual Studio Code

This blog includes a fully functional Empty Subject Zimlet. It works by registering the ONSEND event. In the onSendHandler method, the Zimlet checks if the email message has an empty subject and will show the user a reminder to complete the Subject field.

To learn from this Zimlet, open it in Visual Studio Code and take a look at the implementation of the Empty Subject Zimlet.

Open the folder ~/zimbra_course_pt17/zimbra-zimlet-emptysubject in Visual Studio Code to see the code in the Empty Subject Zimlet.

Empty Subject Zimlet

The file src/index.js implements the base of the Zimlet with support for i18n:

import { createElement } from 'preact';

import { InitializeEvents } from './components/initialize-events';

export default function Zimlet(context) {
    const { plugins } = context;
    const exports = {};

    exports.init = function init() {
        plugins.register('slot::mail-composer-toolbar-send', () => (
            <InitializeEvents context={context} />

    return exports;


If you have trouble understanding this code snippet, please see: https://github.com/Zimbra/zm-zimlet-guide

The file src/components/initialize-events implements the onSendHandler:

import { createElement } from 'preact';
import { useCallback, useEffect } from 'preact/hooks';

import { zimletEventEmitter, callWith } from '@zimbra-client/util';
import { ZIMBRA_ZIMLET_EVENTS } from '@zimbra-client/constants';
import ConfirmModal from './confirm-modal';

const MODAL_ID = 'zimbra-zimlet-emptysubject-dialog';

export const InitializeEvents = ({ context }) => {
    const { dispatch } = context.store;
    const { addModal } = context.zimletRedux.actions.zimlets;
    const { removeModal } = context.zimletRedux.actions.zimlets;

    const onDialogClose = useCallback(
        reject => {
            dispatch(removeModal({ id: MODAL_ID }));
        [dispatch, removeModal]

    const onDialogAction = useCallback(
        resolve => {
            dispatch(removeModal({ id: MODAL_ID }));
        [dispatch, removeModal]

    const onSendHandler = useCallback(
        ({ message }) =>
            new Promise((resolve, reject) => {
                // Check subject is empty or not
                if (message.subject) {
                } else {
                    const modal = (
                            onClose={callWith(onDialogClose, reject)}
                            onAction={callWith(onDialogAction, resolve)}
                    dispatch(addModal({ id: MODAL_ID, modal }));
        [dispatch, addModal, onDialogAction, onDialogClose]

    useEffect(() => {
        zimletEventEmitter.on(ZIMBRA_ZIMLET_EVENTS.ONSEND, onSendHandler, true);

        return () => {
            zimletEventEmitter.off(ZIMBRA_ZIMLET_EVENTS.ONSEND, onSendHandler);
    }, [onSendHandler]);

    return null;


Finally this Zimlet introduces a new way of creating a Modal dialog by the use of a wrapper component. This is done for increased performance. The Modal dialog wrapper is implemented in src/components/confirm-modal:

import { createElement } from 'preact';
import { Text } from 'preact-i18n';

import { withIntl } from '../enhancers';
import { ModalDialog } from '@zimbra-client/components';

const ConfirmModal = ({ onClose, onAction }) => {
    return (
                <Text id="noSubject.description" />

export default withIntl()(ConfirmModal);


React Performance: Event Handlers using useCallback hook

This Zimlet introduces the use of useCallback to increase performance. For more information read: https://medium.com/@KTAsim/react-performance-event-handlers-using-usecallback-hook-9e4a06f8bb2f


The latest version of this guide can be found at:

Your Zimbra Team

, , , ,

Comments are closed.

Copyright © 2022 Zimbra, Inc. All rights reserved.

All information contained in this blog is intended for informational purposes only. Synacor, Inc. is not responsible or liable in any manner for the use or misuse of any technical content provided herein. No specific or implied warranty is provided in association with the information or application of the information provided herein, including, but not limited to, use, misuse or distribution of such information by any user. The user assumes any and all risk pertaining to the use or distribution in any form of any subject matter contained in this blog.

Legal Information | Privacy Policy | Do Not Sell My Personal Information | CCPA Disclosures