From 0b9944517c2cce989b8be979ba6537e1c02c6e09 Mon Sep 17 00:00:00 2001
From: Dima Grossman <dima@grossman.io>
Date: Wed, 12 Jun 2024 12:04:43 +0300
Subject: [PATCH] refactor(worker): Local instance selection handlebars (#5622)

* feat: add i18n instance

* feat: add workers

* feat: instance selection

* feat: add compilation

* fix: update source file
---
 .idea/.name                                   |   1 -
 .../runConfigurations/APPLICATION_GENERIC.xml |   2 +-
 .idea/runConfigurations/EE_AUTH.xml           |  12 +
 .idea/runConfigurations/RUN_LOCAL_ENV.xml     |   3 +-
 .idea/runConfigurations/SHARED_WEB.xml        |  12 +
 .vscode/launch.json                           |   5 +-
 .../content-templates.controller.ts           |   6 +-
 .../inbound-email-parse.usecase.ts            |  10 +-
 .../send-message/send-message-chat.usecase.ts |  17 +-
 .../send-message-email.usecase.ts             |   2 +-
 .../send-message/send-message-push.usecase.ts |  30 +-
 .../send-message/send-message-sms.usecase.ts  |  17 +-
 .../send-message/send-message.base.ts         |   5 +-
 .../compile-email-template.usecase.ts         |  86 +++--
 .../templates/basic.handlebars                |   1 +
 .../compile-in-app-template.usecase.ts        |  38 +-
 .../compile-step-template.usecase.ts          |  35 +-
 .../compile-template.command.ts               |   2 +
 .../compile-template/compile-template.spec.ts | 243 ++++++------
 .../compile-template.usecase.ts               | 359 +++++++++---------
 .../conditions-filter.usecase.ts              |  14 +-
 libs/application-generic/tsconfig.json        |   1 +
 libs/application-generic/tsconfig.module.json |   1 +
 23 files changed, 481 insertions(+), 421 deletions(-)
 delete mode 100644 .idea/.name
 create mode 100644 .idea/runConfigurations/EE_AUTH.xml
 create mode 100644 .idea/runConfigurations/SHARED_WEB.xml

diff --git a/.idea/.name b/.idea/.name
deleted file mode 100644
index 2ff8622f172..00000000000
--- a/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-package.json
\ No newline at end of file
diff --git a/.idea/runConfigurations/APPLICATION_GENERIC.xml b/.idea/runConfigurations/APPLICATION_GENERIC.xml
index f2f2dc3a290..16afa4bc0f1 100644
--- a/.idea/runConfigurations/APPLICATION_GENERIC.xml
+++ b/.idea/runConfigurations/APPLICATION_GENERIC.xml
@@ -1,6 +1,6 @@
 <component name="ProjectRunConfigurationManager">
   <configuration default="false" name="APPLICATION GENERIC" type="js.build_tools.npm">
-    <package-json value="$PROJECT_DIR$/packages/application-generic/package.json" />
+    <package-json value="$PROJECT_DIR$/libs/application-generic/package.json" />
     <command value="run" />
     <scripts>
       <script value="watch:build" />
diff --git a/.idea/runConfigurations/EE_AUTH.xml b/.idea/runConfigurations/EE_AUTH.xml
new file mode 100644
index 00000000000..cd00c3a5a85
--- /dev/null
+++ b/.idea/runConfigurations/EE_AUTH.xml
@@ -0,0 +1,12 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="EE-AUTH" type="js.build_tools.npm">
+    <package-json value="$PROJECT_DIR$/enterprise/packages/auth/package.json" />
+    <command value="run" />
+    <scripts>
+      <script value="build:watch" />
+    </scripts>
+    <node-interpreter value="project" />
+    <envs />
+    <method v="2" />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/.idea/runConfigurations/RUN_LOCAL_ENV.xml b/.idea/runConfigurations/RUN_LOCAL_ENV.xml
index bfe0f90e5d4..f61c18fb646 100644
--- a/.idea/runConfigurations/RUN_LOCAL_ENV.xml
+++ b/.idea/runConfigurations/RUN_LOCAL_ENV.xml
@@ -3,12 +3,13 @@
     <toRun name="API" type="js.build_tools.npm" />
     <toRun name="APPLICATION GENERIC" type="js.build_tools.npm" />
     <toRun name="DAL" type="js.build_tools.npm" />
+    <toRun name="EE-AUTH" type="js.build_tools.npm" />
     <toRun name="SHARED" type="js.build_tools.npm" />
+    <toRun name="SHARED-WEB" type="js.build_tools.npm" />
     <toRun name="TESTING" type="js.build_tools.npm" />
     <toRun name="WEB" type="js.build_tools.npm" />
     <toRun name="WORKER" type="js.build_tools.npm" />
     <toRun name="WS" type="js.build_tools.npm" />
-    <toRun name="auth &gt; build:watch" type="js.build_tools.npm" />
     <method v="2" />
   </configuration>
 </component>
\ No newline at end of file
diff --git a/.idea/runConfigurations/SHARED_WEB.xml b/.idea/runConfigurations/SHARED_WEB.xml
new file mode 100644
index 00000000000..21004381511
--- /dev/null
+++ b/.idea/runConfigurations/SHARED_WEB.xml
@@ -0,0 +1,12 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="SHARED-WEB" type="js.build_tools.npm">
+    <package-json value="$PROJECT_DIR$/libs/shared-web/package.json" />
+    <command value="run" />
+    <scripts>
+      <script value="build:watch" />
+    </scripts>
+    <node-interpreter value="project" />
+    <envs />
+    <method v="2" />
+  </configuration>
+</component>
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 62c8e896691..0df58e43d86 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -230,7 +230,7 @@
   "compounds": [
     {
       "name": "-- RUN FULL ENV - Local",
-      "configurations": ["API", "DAL", "EMBED", "SHARED", "TESTING LIB", "WS", "WEB", "WIDGET"]
+      "configurations": ["API", "DAL", "EMBED", "SHARED", "TESTING LIB", "WS", "WEB", "WIDGET", "worker"]
     },
     {
       "name": "-- RUN FULL ENV - Test",
@@ -242,7 +242,8 @@
         "TESTING LIB",
         "WS - TEST ENV",
         "WEB",
-        "WIDGET - test"
+        "WIDGET - test",
+        "worker"
       ]
     }
   ]
diff --git a/apps/api/src/app/content-templates/content-templates.controller.ts b/apps/api/src/app/content-templates/content-templates.controller.ts
index 3d4c4918f79..b4ee4b2d5de 100644
--- a/apps/api/src/app/content-templates/content-templates.controller.ts
+++ b/apps/api/src/app/content-templates/content-templates.controller.ts
@@ -148,8 +148,8 @@ export class ContentTemplatesController {
           environmentId,
           organizationId
         );
-
-        await i18next.init({
+        const instance = i18next.createInstance();
+        await instance.init({
           resources,
           ns: namespaces,
           defaultNS: false,
@@ -168,6 +168,8 @@ export class ContentTemplatesController {
             },
           },
         });
+
+        return instance;
       }
     } catch (e) {
       Logger.error(e, `Unexpected error while importing enterprise modules`, 'TranslationsService');
diff --git a/apps/worker/src/app/workflow/usecases/inbound-email-parse/inbound-email-parse.usecase.ts b/apps/worker/src/app/workflow/usecases/inbound-email-parse/inbound-email-parse.usecase.ts
index 438405bf737..7ed26951ff9 100644
--- a/apps/worker/src/app/workflow/usecases/inbound-email-parse/inbound-email-parse.usecase.ts
+++ b/apps/worker/src/app/workflow/usecases/inbound-email-parse/inbound-email-parse.usecase.ts
@@ -44,12 +44,10 @@ export class InboundEmailParse {
       );
     }
 
-    const compiledDomain = await this.compileTemplate.execute(
-      CompileTemplateCommand.create({
-        template: currentParseWebhook as string,
-        data: job.payload,
-      })
-    );
+    const compiledDomain = await this.compileTemplate.execute({
+      template: currentParseWebhook as string,
+      data: job.payload,
+    });
 
     const userPayload: IUserWebhookPayload = {
       hmac: createHash(environment?.apiKeys[0]?.key, subscriber.subscriberId),
diff --git a/apps/worker/src/app/workflow/usecases/send-message/send-message-chat.usecase.ts b/apps/worker/src/app/workflow/usecases/send-message/send-message-chat.usecase.ts
index 630de444054..8d84301440e 100644
--- a/apps/worker/src/app/workflow/usecases/send-message/send-message-chat.usecase.ts
+++ b/apps/worker/src/app/workflow/usecases/send-message/send-message-chat.usecase.ts
@@ -80,7 +80,11 @@ export class SendMessageChat extends SendMessageBase {
     if (!step?.template) throw new PlatformException('Chat channel template not found');
 
     const { subscriber } = command.compileContext;
-    await this.initiateTranslations(command.environmentId, command.organizationId, subscriber.locale);
+    const i18nextInstance = await this.initiateTranslations(
+      command.environmentId,
+      command.organizationId,
+      subscriber.locale
+    );
 
     const template = await this.processVariants(command);
 
@@ -92,12 +96,11 @@ export class SendMessageChat extends SendMessageBase {
 
     try {
       if (!command.chimeraData) {
-        content = await this.compileTemplate.execute(
-          CompileTemplateCommand.create({
-            template: step.template.content as string,
-            data: this.getCompilePayload(command.compileContext),
-          })
-        );
+        content = await this.compileTemplate.execute({
+          template: step.template.content as string,
+          data: this.getCompilePayload(command.compileContext),
+          i18next: i18nextInstance,
+        });
       }
     } catch (e) {
       await this.sendErrorHandlebars(command.job, e.message);
diff --git a/apps/worker/src/app/workflow/usecases/send-message/send-message-email.usecase.ts b/apps/worker/src/app/workflow/usecases/send-message/send-message-email.usecase.ts
index a10b9972641..96b27a941ea 100644
--- a/apps/worker/src/app/workflow/usecases/send-message/send-message-email.usecase.ts
+++ b/apps/worker/src/app/workflow/usecases/send-message/send-message-email.usecase.ts
@@ -235,7 +235,7 @@ export class SendMessageEmail extends SendMessageBase {
         });
       }
     } catch (e) {
-      Logger.error({ payload }, 'Compiling the email template or storing it or inlining it has failed', LOG_CONTEXT);
+      Logger.error({ payload, e }, 'Compiling the email template or storing it or inlining it has failed', LOG_CONTEXT);
       await this.sendErrorHandlebars(command.job, e.message);
 
       return;
diff --git a/apps/worker/src/app/workflow/usecases/send-message/send-message-push.usecase.ts b/apps/worker/src/app/workflow/usecases/send-message/send-message-push.usecase.ts
index 136033dc094..978b1717a2f 100644
--- a/apps/worker/src/app/workflow/usecases/send-message/send-message-push.usecase.ts
+++ b/apps/worker/src/app/workflow/usecases/send-message/send-message-push.usecase.ts
@@ -80,7 +80,11 @@ export class SendMessagePush extends SendMessageBase {
     const { subscriber, step: stepData } = command.compileContext;
 
     const template = await this.processVariants(command);
-    await this.initiateTranslations(command.environmentId, command.organizationId, subscriber.locale);
+    const i18nInstance = await this.initiateTranslations(
+      command.environmentId,
+      command.organizationId,
+      subscriber.locale
+    );
 
     if (template) {
       step.template = template;
@@ -92,19 +96,17 @@ export class SendMessagePush extends SendMessageBase {
 
     try {
       if (!command.chimeraData) {
-        content = await this.compileTemplate.execute(
-          CompileTemplateCommand.create({
-            template: step.template?.content as string,
-            data,
-          })
-        );
-
-        title = await this.compileTemplate.execute(
-          CompileTemplateCommand.create({
-            template: step.template?.title as string,
-            data,
-          })
-        );
+        content = await this.compileTemplate.execute({
+          template: step.template?.content as string,
+          data,
+          i18next: i18nInstance,
+        });
+
+        title = await this.compileTemplate.execute({
+          template: step.template?.title as string,
+          data,
+          i18next: i18nInstance,
+        });
       }
     } catch (e) {
       await this.sendErrorHandlebars(command.job, e.message);
diff --git a/apps/worker/src/app/workflow/usecases/send-message/send-message-sms.usecase.ts b/apps/worker/src/app/workflow/usecases/send-message/send-message-sms.usecase.ts
index 5569cd48455..c763f53d595 100644
--- a/apps/worker/src/app/workflow/usecases/send-message/send-message-sms.usecase.ts
+++ b/apps/worker/src/app/workflow/usecases/send-message/send-message-sms.usecase.ts
@@ -80,7 +80,11 @@ export class SendMessageSms extends SendMessageBase {
 
     const { subscriber } = command.compileContext;
     const template = await this.processVariants(command);
-    await this.initiateTranslations(command.environmentId, command.organizationId, subscriber.locale);
+    const i18nextInstance = await this.initiateTranslations(
+      command.environmentId,
+      command.organizationId,
+      subscriber.locale
+    );
 
     if (template) {
       step.template = template;
@@ -91,12 +95,11 @@ export class SendMessageSms extends SendMessageBase {
 
     try {
       if (!command.chimeraData) {
-        content = await this.compileTemplate.execute(
-          CompileTemplateCommand.create({
-            template: step.template.content as string,
-            data: this.getCompilePayload(command.compileContext),
-          })
-        );
+        content = await this.compileTemplate.execute({
+          template: step.template.content as string,
+          data: this.getCompilePayload(command.compileContext),
+          i18next: i18nextInstance,
+        });
 
         if (!content) {
           throw new PlatformException(`Unexpected error: SMS content is missing`);
diff --git a/apps/worker/src/app/workflow/usecases/send-message/send-message.base.ts b/apps/worker/src/app/workflow/usecases/send-message/send-message.base.ts
index 77771022702..0e1bdaa3d96 100644
--- a/apps/worker/src/app/workflow/usecases/send-message/send-message.base.ts
+++ b/apps/worker/src/app/workflow/usecases/send-message/send-message.base.ts
@@ -149,7 +149,8 @@ export abstract class SendMessageBase extends SendMessageType {
           organizationId
         );
 
-        await i18next.init({
+        const instance = i18next.createInstance();
+        await instance.init({
           resources,
           ns: namespaces,
           defaultNS: false,
@@ -168,6 +169,8 @@ export abstract class SendMessageBase extends SendMessageType {
             },
           },
         });
+
+        return instance;
       }
     } catch (e) {
       Logger.error(e, `Unexpected error while importing enterprise modules`, 'TranslationsService');
diff --git a/libs/application-generic/src/usecases/compile-email-template/compile-email-template.usecase.ts b/libs/application-generic/src/usecases/compile-email-template/compile-email-template.usecase.ts
index 080a21d0083..d764c815695 100644
--- a/libs/application-generic/src/usecases/compile-email-template/compile-email-template.usecase.ts
+++ b/libs/application-generic/src/usecases/compile-email-template/compile-email-template.usecase.ts
@@ -39,8 +39,9 @@ export class CompileEmailTemplate extends CompileTemplateBase {
     const verifyPayloadService = new VerifyPayloadService();
     const organization = await this.getOrganization(command.organizationId);
 
+    let i18nInstance;
     if (initiateTranslations) {
-      await initiateTranslations(
+      i18nInstance = await initiateTranslations(
         command.environmentId,
         command.organizationId,
         command.locale ||
@@ -97,17 +98,26 @@ export class CompileEmailTemplate extends CompileTemplateBase {
     };
 
     try {
-      subject = await this.renderContent(command.subject, payload);
+      subject = await this.renderContent(
+        command.subject,
+        payload,
+        i18nInstance
+      );
 
       if (preheader) {
-        preheader = await this.renderContent(preheader, payload);
+        preheader = await this.renderContent(preheader, payload, i18nInstance);
       }
+
       if (command.senderName) {
-        senderName = await this.renderContent(command.senderName, payload);
+        senderName = await this.renderContent(
+          command.senderName,
+          payload,
+          i18nInstance
+        );
       }
     } catch (e: any) {
       throw new ApiException(
-        e?.message || `Message content could not be generated`
+        e?.message || `Email subject message content could not be generated`
       );
     }
 
@@ -115,6 +125,21 @@ export class CompileEmailTemplate extends CompileTemplateBase {
       layoutContent as string
     );
 
+    if (isEditorMode) {
+      for (const block of content as IEmailBlock[]) {
+        block.content = await this.renderContent(
+          block.content,
+          payload,
+          i18nInstance
+        );
+        block.url = await this.renderContent(
+          block.url || '',
+          payload,
+          i18nInstance
+        );
+      }
+    }
+
     const templateVariables = {
       ...payload,
       subject,
@@ -123,31 +148,22 @@ export class CompileEmailTemplate extends CompileTemplateBase {
       blocks: isEditorMode ? content : [],
     };
 
-    if (isEditorMode) {
-      for (const block of content as IEmailBlock[]) {
-        block.content = await this.renderContent(block.content, payload);
-        block.url = await this.renderContent(block.url || '', payload);
-      }
-    }
-
-    const body = await this.compileTemplate.execute(
-      CompileTemplateCommand.create({
-        template: !isEditorMode
-          ? (content as string)
-          : (helperBlocksContent as string),
-        data: templateVariables,
-      })
-    );
+    const body = await this.compileTemplate.execute({
+      i18next: i18nInstance,
+      template: !isEditorMode
+        ? (content as string)
+        : (helperBlocksContent as string),
+      data: templateVariables,
+    });
 
     templateVariables.body = body as string;
 
     const html = customLayout
-      ? await this.compileTemplate.execute(
-          CompileTemplateCommand.create({
-            template: customLayout,
-            data: templateVariables,
-          })
-        )
+      ? await this.compileTemplate.execute({
+          i18next: i18nInstance,
+          template: customLayout,
+          data: templateVariables,
+        })
       : body;
 
     return { html, content, subject, senderName };
@@ -155,16 +171,16 @@ export class CompileEmailTemplate extends CompileTemplateBase {
 
   private async renderContent(
     content: string,
-    payload: Record<string, unknown>
+    payload: Record<string, unknown>,
+    i18nInstance: any
   ) {
-    const renderedContent = await this.compileTemplate.execute(
-      CompileTemplateCommand.create({
-        template: content,
-        data: {
-          ...payload,
-        },
-      })
-    );
+    const renderedContent = await this.compileTemplate.execute({
+      i18next: i18nInstance,
+      template: content,
+      data: {
+        ...payload,
+      },
+    });
 
     return renderedContent?.trim() || '';
   }
diff --git a/libs/application-generic/src/usecases/compile-email-template/templates/basic.handlebars b/libs/application-generic/src/usecases/compile-email-template/templates/basic.handlebars
index d58c4a9de14..5a01a5f7864 100644
--- a/libs/application-generic/src/usecases/compile-email-template/templates/basic.handlebars
+++ b/libs/application-generic/src/usecases/compile-email-template/templates/basic.handlebars
@@ -1,3 +1,4 @@
+
 {{#each blocks}}
   <div style="margin-bottom: 10px" data-test-id="block-item-wrapper">
     {{#equals type 'text'}}
diff --git a/libs/application-generic/src/usecases/compile-in-app-template/compile-in-app-template.usecase.ts b/libs/application-generic/src/usecases/compile-in-app-template/compile-in-app-template.usecase.ts
index dd6a02b81bc..22b2584af95 100644
--- a/libs/application-generic/src/usecases/compile-in-app-template/compile-in-app-template.usecase.ts
+++ b/libs/application-generic/src/usecases/compile-in-app-template/compile-in-app-template.usecase.ts
@@ -31,8 +31,9 @@ export class CompileInAppTemplate extends CompileTemplateBase {
   ) {
     const organization = await this.getOrganization(command.organizationId);
 
+    let i18nInstance;
     if (initiateTranslations) {
-      await initiateTranslations(
+      i18nInstance = await initiateTranslations(
         command.environmentId,
         command.organizationId,
         command.locale ||
@@ -52,7 +53,8 @@ export class CompileInAppTemplate extends CompileTemplateBase {
         ? await this.compileInAppTemplate(
             command.content,
             payload,
-            organization
+            organization,
+            i18nInstance
           )
         : '';
 
@@ -60,7 +62,8 @@ export class CompileInAppTemplate extends CompileTemplateBase {
         url = await this.compileInAppTemplate(
           command.cta?.data?.url,
           payload,
-          organization
+          organization,
+          i18nInstance
         );
       }
 
@@ -69,14 +72,15 @@ export class CompileInAppTemplate extends CompileTemplateBase {
           const buttonContent = await this.compileInAppTemplate(
             action.content,
             payload,
-            organization
+            organization,
+            i18nInstance
           );
           ctaButtons.push({ type: action.type, content: buttonContent });
         }
       }
     } catch (e: any) {
       throw new ApiException(
-        e?.message || `Message content could not be generated`
+        e?.message || `In-App Message content could not be generated`
       );
     }
 
@@ -86,19 +90,19 @@ export class CompileInAppTemplate extends CompileTemplateBase {
   private async compileInAppTemplate(
     content: string,
     payload: any,
-    organization: OrganizationEntity | null
+    organization: OrganizationEntity | null,
+    i18nInstance: any
   ): Promise<string> {
-    return await this.compileTemplate.execute(
-      CompileTemplateCommand.create({
-        template: content as string,
-        data: {
-          ...payload,
-          branding: {
-            logo: organization?.branding?.logo,
-            color: organization?.branding?.color || '#f47373',
-          },
+    return await this.compileTemplate.execute({
+      i18next: i18nInstance,
+      template: content as string,
+      data: {
+        ...payload,
+        branding: {
+          logo: organization?.branding?.logo,
+          color: organization?.branding?.color || '#f47373',
         },
-      })
-    );
+      },
+    });
   }
 }
diff --git a/libs/application-generic/src/usecases/compile-step-template/compile-step-template.usecase.ts b/libs/application-generic/src/usecases/compile-step-template/compile-step-template.usecase.ts
index c2a5bd3a253..e0b97177f19 100644
--- a/libs/application-generic/src/usecases/compile-step-template/compile-step-template.usecase.ts
+++ b/libs/application-generic/src/usecases/compile-step-template/compile-step-template.usecase.ts
@@ -30,8 +30,9 @@ export class CompileStepTemplate extends CompileTemplateBase {
   ) {
     const organization = await this.getOrganization(command.organizationId);
 
+    let i18nInstance;
     if (initiateTranslations) {
-      await initiateTranslations(
+      i18nInstance = await initiateTranslations(
         command.environmentId,
         command.organizationId,
         command.locale ||
@@ -47,14 +48,22 @@ export class CompileStepTemplate extends CompileTemplateBase {
     let title: string | undefined = undefined;
 
     try {
-      content = await this.compileStepTemplate(command.content, payload);
+      content = await this.compileStepTemplate(
+        command.content,
+        payload,
+        i18nInstance
+      );
 
       if (command.title) {
-        title = await this.compileStepTemplate(command.title, payload);
+        title = await this.compileStepTemplate(
+          command.title,
+          payload,
+          i18nInstance
+        );
       }
     } catch (e: any) {
       throw new ApiException(
-        e?.message || `Message content could not be generated`
+        e?.message || `Compile step content failed to generate`
       );
     }
 
@@ -63,15 +72,15 @@ export class CompileStepTemplate extends CompileTemplateBase {
 
   private async compileStepTemplate(
     content: string,
-    payload: any
+    payload: any,
+    i18nInstance?: any
   ): Promise<string> {
-    return await this.compileTemplate.execute(
-      CompileTemplateCommand.create({
-        template: content as string,
-        data: {
-          ...payload,
-        },
-      })
-    );
+    return await this.compileTemplate.execute({
+      i18next: i18nInstance,
+      template: content as string,
+      data: {
+        ...payload,
+      },
+    });
   }
 }
diff --git a/libs/application-generic/src/usecases/compile-template/compile-template.command.ts b/libs/application-generic/src/usecases/compile-template/compile-template.command.ts
index 6fc7147263c..f84214dba4f 100644
--- a/libs/application-generic/src/usecases/compile-template/compile-template.command.ts
+++ b/libs/application-generic/src/usecases/compile-template/compile-template.command.ts
@@ -8,4 +8,6 @@ export class CompileTemplateCommand extends BaseCommand {
 
   @IsObject()
   data: any; // eslint-disable-line @typescript-eslint/no-explicit-any
+
+  i18next?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
 }
diff --git a/libs/application-generic/src/usecases/compile-template/compile-template.spec.ts b/libs/application-generic/src/usecases/compile-template/compile-template.spec.ts
index 9b2393168b2..9cca122c4a1 100644
--- a/libs/application-generic/src/usecases/compile-template/compile-template.spec.ts
+++ b/libs/application-generic/src/usecases/compile-template/compile-template.spec.ts
@@ -16,129 +16,116 @@ describe('Compile Template', function () {
   });
 
   it('should render custom html', async function () {
-    const result = await useCase.execute(
-      CompileTemplateCommand.create({
-        data: {
-          branding: {
-            color: '#e7e7e7e9',
-          },
-          name: 'Test Name',
+    const result = await useCase.execute({
+      data: {
+        branding: {
+          color: '#e7e7e7e9',
         },
-        template: '<div>{{name}}</div>',
-      })
-    );
+        name: 'Test Name',
+      },
+      template: '<div>{{name}}</div>',
+    });
 
     expect(result).toEqual('<div>Test Name</div>');
   });
 
   it('should render pluralisation in html', async function () {
-    const result = await useCase.execute(
-      CompileTemplateCommand.create({
-        data: {
-          branding: {
-            color: '#e7e7e7e9',
-          },
-          dog_count: 1,
-          sausage_count: 2,
+    const result = await useCase.execute({
+      data: {
+        branding: {
+          color: '#e7e7e7e9',
         },
-        template:
-          '<div>{{dog_count}} {{pluralize dog_count "dog" "dogs"}} and {{sausage_count}} {{pluralize sausage_count "sausage" "sausages"}} for {{pluralize dog_count "him" "them"}}</div>',
-      })
-    );
+        dog_count: 1,
+        sausage_count: 2,
+      },
+      template:
+        '<div>{{dog_count}} {{pluralize dog_count "dog" "dogs"}} and {{sausage_count}} {{pluralize sausage_count "sausage" "sausages"}} for {{pluralize dog_count "him" "them"}}</div>',
+    });
 
     expect(result).toEqual('<div>1 dog and 2 sausages for him</div>');
   });
 
   it('should render unique values of array', async function () {
-    const result = await useCase.execute(
-      CompileTemplateCommand.create({
-        data: {
-          names: [{ name: 'dog' }, { name: 'cat' }, { name: 'dog' }],
-        },
-        template:
-          '<div>{{#each (unique names "name")}}{{this}}-{{/each}}</div>',
-      })
-    );
+    const result = await useCase.execute({
+      data: {
+        names: [{ name: 'dog' }, { name: 'cat' }, { name: 'dog' }],
+      },
+      template: '<div>{{#each (unique names "name")}}{{this}}-{{/each}}</div>',
+    });
 
     expect(result).toEqual('<div>dog-cat-</div>');
   });
 
   it('should render groupBy values of array', async function () {
-    const result = await useCase.execute(
-      CompileTemplateCommand.create({
-        data: {
-          names: [
-            {
-              name: 'Name 1',
-              age: '30',
-            },
-            {
-              name: 'Name 2',
-              age: '31',
-            },
-            {
-              name: 'Name 1',
-              age: '32',
-            },
-          ],
-        },
-        template:
-          '{{#each (groupBy names "name")}}<h1>{{key}}</h1>{{#each items}}{{age}}-{{/each}}{{/each}}',
-      })
-    );
+    const result = await useCase.execute({
+      data: {
+        names: [
+          {
+            name: 'Name 1',
+            age: '30',
+          },
+          {
+            name: 'Name 2',
+            age: '31',
+          },
+          {
+            name: 'Name 1',
+            age: '32',
+          },
+        ],
+      },
+      template:
+        '{{#each (groupBy names "name")}}<h1>{{key}}</h1>{{#each items}}{{age}}-{{/each}}{{/each}}',
+    });
 
     expect(result).toEqual('<h1>Name 1</h1>30-32-<h1>Name 2</h1>31-');
   });
 
   it('should render sortBy values of array', async function () {
-    const result = await useCase.execute(
-      CompileTemplateCommand.create({
-        data: {
-          people: [
-            {
-              name: 'a75',
-              item1: false,
-              item2: false,
-              id: 1,
-              updated_at: '2023-01-01T06:25:24Z',
-            },
-            {
-              name: 'z32',
-              item1: true,
-              item2: false,
-              id: 3,
-              updated_at: '2023-01-09T11:25:13Z',
-            },
-            {
-              name: 'e77',
-              item1: false,
-              item2: false,
-              id: 2,
-              updated_at: '2023-01-05T04:13:24Z',
-            },
-          ],
-        },
-        template: `{{#each (sortBy people 'updated_at')}}{{name}} - {{id}}{{/each}}`,
-      })
-    );
+    const result = await useCase.execute({
+      data: {
+        people: [
+          {
+            name: 'a75',
+            item1: false,
+            item2: false,
+            id: 1,
+            updated_at: '2023-01-01T06:25:24Z',
+          },
+          {
+            name: 'z32',
+            item1: true,
+            item2: false,
+            id: 3,
+            updated_at: '2023-01-09T11:25:13Z',
+          },
+          {
+            name: 'e77',
+            item1: false,
+            item2: false,
+            id: 2,
+            updated_at: '2023-01-05T04:13:24Z',
+          },
+        ],
+      },
+      template: `{{#each (sortBy people 'updated_at')}}{{name}} - {{id}}{{/each}}`,
+    });
 
     expect(result).toEqual('a75 - 1e77 - 2z32 - 3');
   });
 
   it('should allow the user to specify handlebars helpers', async function () {
-    const result = await useCase.execute(
-      CompileTemplateCommand.create({
-        data: {
-          branding: {
-            color: '#e7e7e7e9',
-          },
-          message: 'hello world',
-          messageTwo: 'hEllo world',
+    const result = await useCase.execute({
+      data: {
+        branding: {
+          color: '#e7e7e7e9',
         },
-        template:
-          '<div>{{titlecase message}} and {{lowercase messageTwo}} and {{uppercase message}}</div>',
-      })
-    );
+        message: 'hello world',
+        messageTwo: 'hEllo world',
+      },
+      template:
+        '<div>{{titlecase message}} and {{lowercase messageTwo}} and {{uppercase message}}</div>',
+    });
 
     expect(result).toEqual(
       '<div>Hello World and hello world and HELLO WORLD</div>'
@@ -146,65 +133,55 @@ describe('Compile Template', function () {
   });
 
   it('should allow apostrophes to be in data', async function () {
-    const result = await useCase.execute(
-      CompileTemplateCommand.create({
-        data: {
-          message: "hello' world",
-        },
-        template: '<div>{{message}}</div>',
-      })
-    );
+    const result = await useCase.execute({
+      data: {
+        message: "hello' world",
+      },
+      template: '<div>{{message}}</div>',
+    });
 
     expect(result).toEqual("<div>hello' world</div>");
   });
 
   describe('Date Formation', function () {
     it('should allow user to format the date', async function () {
-      const result = await useCase.execute(
-        CompileTemplateCommand.create({
-          data: {
-            date: '2020-01-01',
-          },
-          template: "<div>{{dateFormat date 'EEEE, MMMM Do yyyy'}}</div>",
-        })
-      );
+      const result = await useCase.execute({
+        data: {
+          date: '2020-01-01',
+        },
+        template: "<div>{{dateFormat date 'EEEE, MMMM Do yyyy'}}</div>",
+      });
       expect(result).toEqual('<div>Wednesday, January 1st 2020</div>');
     });
 
     it('should not fail and return same date for invalid date', async function () {
-      const result = await useCase.execute(
-        CompileTemplateCommand.create({
-          data: {
-            date: 'ABCD',
-          },
-          template: "<div>{{dateFormat date 'EEEE, MMMM Do yyyy'}}</div>",
-        })
-      );
+      const result = await useCase.execute({
+        data: {
+          date: 'ABCD',
+        },
+        template: "<div>{{dateFormat date 'EEEE, MMMM Do yyyy'}}</div>",
+      });
       expect(result).toEqual('<div>ABCD</div>');
     });
   });
 
   describe('Number formating', () => {
     it('should format number', async () => {
-      const result = await useCase.execute(
-        CompileTemplateCommand.create({
-          data: { number: 1000000000 },
-          template:
-            '<div>{{numberFormat number decimalSep="," decimalLength="2" thousandsSep="|"}}</div>',
-        })
-      );
+      const result = await useCase.execute({
+        data: { number: 1000000000 },
+        template:
+          '<div>{{numberFormat number decimalSep="," decimalLength="2" thousandsSep="|"}}</div>',
+      });
 
       expect(result).toEqual('<div>1|000|000|000,00</div>');
     });
 
     it('should not fail and return passed value', async () => {
-      const result = await useCase.execute(
-        CompileTemplateCommand.create({
-          data: { number: 'Not a number' },
-          template:
-            '<div>{{numberFormat number decimalSep="," decimalLength="2" thousandsSep="|"}}</div>',
-        })
-      );
+      const result = await useCase.execute({
+        data: { number: 'Not a number' },
+        template:
+          '<div>{{numberFormat number decimalSep="," decimalLength="2" thousandsSep="|"}}</div>',
+      });
 
       expect(result).toEqual('<div>Not a number</div>');
     });
diff --git a/libs/application-generic/src/usecases/compile-template/compile-template.usecase.ts b/libs/application-generic/src/usecases/compile-template/compile-template.usecase.ts
index 61c452b5e13..7253a4a8184 100644
--- a/libs/application-generic/src/usecases/compile-template/compile-template.usecase.ts
+++ b/libs/application-generic/src/usecases/compile-template/compile-template.usecase.ts
@@ -4,7 +4,6 @@ import { format } from 'date-fns';
 import { HandlebarHelpersEnum } from '@novu/shared';
 
 import { CompileTemplateCommand } from './compile-template.command';
-import * as i18next from 'i18next';
 import { ApiException } from '../../utils/exceptions';
 
 const assertResult = (condition: boolean, options) => {
@@ -13,212 +12,228 @@ const assertResult = (condition: boolean, options) => {
   return typeof fn === 'function' ? fn(this) : condition;
 };
 
-Handlebars.registerHelper(
-  HandlebarHelpersEnum.I18N,
-  function (key, { hash, data, fn }) {
-    const options = {
-      ...data.root.i18next,
-      ...hash,
-      returnObjects: false,
-    };
+function createHandlebarsInstance(i18next: any) {
+  const handlebars = Handlebars.create();
+
+  handlebars.registerHelper('json', function (context) {
+    return JSON.stringify(context);
+  });
+
+  if (i18next) {
+    handlebars.registerHelper(
+      HandlebarHelpersEnum.I18N,
+      function (key, { hash, data, fn }) {
+        const options = {
+          ...data.root.i18next,
+          ...hash,
+          returnObjects: false,
+        };
+
+        const replace = (options.replace = {
+          // eslint-disable-next-line
+          // @ts-ignore
+          ...this,
+          ...options.replace,
+          ...hash,
+        });
+        delete replace.i18next; // may creep in if this === data.root
+
+        if (fn) {
+          options.defaultValue = fn(replace);
+        }
+
+        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+        // @ts-ignore
+        return new handlebars.SafeString(i18next.t(key, options));
+      }
+    );
+  }
 
-    const replace = (options.replace = {
+  handlebars.registerHelper(
+    HandlebarHelpersEnum.EQUALS,
+    function (arg1, arg2, options) {
       // eslint-disable-next-line
-      // @ts-ignore
-      ...this,
-      ...options.replace,
-      ...hash,
-    });
-    delete replace.i18next; // may creep in if this === data.root
-
-    if (fn) {
-      options.defaultValue = fn(replace);
+      // @ts-expect-error
+      return arg1 == arg2 ? options.fn(this) : options.inverse(this);
     }
-
-    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
-    // @ts-ignore
-    return new Handlebars.SafeString(i18next.t(key, options));
-  }
-);
-Handlebars.registerHelper(
-  HandlebarHelpersEnum.EQUALS,
-  function (arg1, arg2, options) {
-    // eslint-disable-next-line
-    // @ts-expect-error
-    return arg1 == arg2 ? options.fn(this) : options.inverse(this);
-  }
-);
-
-Handlebars.registerHelper(HandlebarHelpersEnum.TITLECASE, function (value) {
-  return value
-    ?.split(' ')
-    .map(
-      (letter) => letter.charAt(0).toUpperCase() + letter.slice(1).toLowerCase()
-    )
-    .join(' ');
-});
-
-Handlebars.registerHelper(HandlebarHelpersEnum.UPPERCASE, function (value) {
-  return value?.toUpperCase();
-});
-
-Handlebars.registerHelper(HandlebarHelpersEnum.LOWERCASE, function (value) {
-  return value?.toLowerCase();
-});
-
-Handlebars.registerHelper(
-  HandlebarHelpersEnum.PLURALIZE,
-  function (number, single, plural) {
-    return number === 1 ? single : plural;
-  }
-);
-
-Handlebars.registerHelper(
-  HandlebarHelpersEnum.DATEFORMAT,
-  function (date, dateFormat) {
-    // Format date if parameters are valid
-    if (date && dateFormat && !isNaN(Date.parse(date))) {
-      return format(new Date(date), dateFormat);
+  );
+
+  handlebars.registerHelper(HandlebarHelpersEnum.TITLECASE, function (value) {
+    return value
+      ?.split(' ')
+      .map(
+        (letter) =>
+          letter.charAt(0).toUpperCase() + letter.slice(1).toLowerCase()
+      )
+      .join(' ');
+  });
+
+  handlebars.registerHelper(HandlebarHelpersEnum.UPPERCASE, function (value) {
+    return value?.toUpperCase();
+  });
+
+  handlebars.registerHelper(HandlebarHelpersEnum.LOWERCASE, function (value) {
+    return value?.toLowerCase();
+  });
+
+  handlebars.registerHelper(
+    HandlebarHelpersEnum.PLURALIZE,
+    function (number, single, plural) {
+      return number === 1 ? single : plural;
     }
-
-    return date;
-  }
-);
-
-Handlebars.registerHelper(
-  HandlebarHelpersEnum.GROUP_BY,
-  function (array, property) {
-    if (!Array.isArray(array)) return [];
-    const map = {};
-    array.forEach((item) => {
-      if (item[property]) {
-        const key = item[property];
-        if (!map[key]) {
-          map[key] = [item];
-        } else {
-          map[key].push(item);
-        }
+  );
+
+  handlebars.registerHelper(
+    HandlebarHelpersEnum.DATEFORMAT,
+    function (date, dateFormat) {
+      // Format date if parameters are valid
+      if (date && dateFormat && !isNaN(Date.parse(date))) {
+        return format(new Date(date), dateFormat);
       }
-    });
 
-    const result = [];
-    for (const [key, value] of Object.entries(map)) {
-      result.push({ key: key, items: value });
+      return date;
     }
-
-    return result;
-  }
-);
-
-Handlebars.registerHelper(
-  HandlebarHelpersEnum.UNIQUE,
-  function (array, property) {
-    if (!Array.isArray(array)) return '';
-
-    return array
-      .map((item) => {
+  );
+
+  handlebars.registerHelper(
+    HandlebarHelpersEnum.GROUP_BY,
+    function (array, property) {
+      if (!Array.isArray(array)) return [];
+      const map = {};
+      array.forEach((item) => {
         if (item[property]) {
-          return item[property];
+          const key = item[property];
+          if (!map[key]) {
+            map[key] = [item];
+          } else {
+            map[key].push(item);
+          }
         }
-      })
-      .filter((value, index, self) => self.indexOf(value) === index);
-  }
-);
+      });
 
-Handlebars.registerHelper(
-  HandlebarHelpersEnum.SORT_BY,
-  function (array, property) {
-    if (!Array.isArray(array)) return '';
-    if (!property) return array.sort();
+      const result = [];
+      for (const [key, value] of Object.entries(map)) {
+        result.push({ key: key, items: value });
+      }
 
-    return array.sort(function (a, b) {
-      const _x = a[property];
-      const _y = b[property];
+      return result;
+    }
+  );
+
+  handlebars.registerHelper(
+    HandlebarHelpersEnum.UNIQUE,
+    function (array, property) {
+      if (!Array.isArray(array)) return '';
+
+      return array
+        .map((item) => {
+          if (item[property]) {
+            return item[property];
+          }
+        })
+        .filter((value, index, self) => self.indexOf(value) === index);
+    }
+  );
 
-      return _x < _y ? -1 : _x > _y ? 1 : 0;
-    });
-  }
-);
-
-// based on: https://gist.github.com/DennyLoko/61882bc72176ca74a0f2
-Handlebars.registerHelper(
-  HandlebarHelpersEnum.NUMBERFORMAT,
-  function (number, options) {
-    if (isNaN(number)) {
-      return number;
+  handlebars.registerHelper(
+    HandlebarHelpersEnum.SORT_BY,
+    function (array, property) {
+      if (!Array.isArray(array)) return '';
+      if (!property) return array.sort();
+
+      return array.sort(function (a, b) {
+        const _x = a[property];
+        const _y = b[property];
+
+        return _x < _y ? -1 : _x > _y ? 1 : 0;
+      });
     }
+  );
+
+  // based on: https://gist.github.com/DennyLoko/61882bc72176ca74a0f2
+  handlebars.registerHelper(
+    HandlebarHelpersEnum.NUMBERFORMAT,
+    function (number, options) {
+      if (isNaN(number)) {
+        return number;
+      }
 
-    const decimalLength = options.hash.decimalLength || 2;
-    const thousandsSep = options.hash.thousandsSep || ',';
-    const decimalSep = options.hash.decimalSep || '.';
+      const decimalLength = options.hash.decimalLength || 2;
+      const thousandsSep = options.hash.thousandsSep || ',';
+      const decimalSep = options.hash.decimalSep || '.';
 
-    const value = parseFloat(number);
+      const value = parseFloat(number);
 
-    const re = '\\d(?=(\\d{3})+' + (decimalLength > 0 ? '\\D' : '$') + ')';
+      const re = '\\d(?=(\\d{3})+' + (decimalLength > 0 ? '\\D' : '$') + ')';
 
-    const num = value.toFixed(Math.max(0, ~~decimalLength));
+      const num = value.toFixed(Math.max(0, ~~decimalLength));
 
-    return (decimalSep ? num.replace('.', decimalSep) : num).replace(
-      new RegExp(re, 'g'),
-      '$&' + thousandsSep
-    );
-  }
-);
+      return (decimalSep ? num.replace('.', decimalSep) : num).replace(
+        new RegExp(re, 'g'),
+        '$&' + thousandsSep
+      );
+    }
+  );
 
-Handlebars.registerHelper(
-  HandlebarHelpersEnum.GT,
-  function (arg1, arg2, options) {
-    return assertResult(arg1 > arg2, options);
-  }
-);
+  handlebars.registerHelper(
+    HandlebarHelpersEnum.GT,
+    function (arg1, arg2, options) {
+      return assertResult(arg1 > arg2, options);
+    }
+  );
 
-Handlebars.registerHelper(
-  HandlebarHelpersEnum.GTE,
-  function (arg1, arg2, options) {
-    return assertResult(arg1 >= arg2, options);
-  }
-);
+  handlebars.registerHelper(
+    HandlebarHelpersEnum.GTE,
+    function (arg1, arg2, options) {
+      return assertResult(arg1 >= arg2, options);
+    }
+  );
 
-Handlebars.registerHelper(
-  HandlebarHelpersEnum.LT,
-  function (arg1, arg2, options) {
-    return assertResult(arg1 < arg2, options);
-  }
-);
+  handlebars.registerHelper(
+    HandlebarHelpersEnum.LT,
+    function (arg1, arg2, options) {
+      return assertResult(arg1 < arg2, options);
+    }
+  );
 
-Handlebars.registerHelper(
-  HandlebarHelpersEnum.LTE,
-  function (arg1, arg2, options) {
-    return assertResult(arg1 <= arg2, options);
-  }
-);
+  handlebars.registerHelper(
+    HandlebarHelpersEnum.LTE,
+    function (arg1, arg2, options) {
+      return assertResult(arg1 <= arg2, options);
+    }
+  );
 
-Handlebars.registerHelper(
-  HandlebarHelpersEnum.EQ,
-  function (arg1, arg2, options) {
-    return assertResult(arg1 === arg2, options);
-  }
-);
+  handlebars.registerHelper(
+    HandlebarHelpersEnum.EQ,
+    function (arg1, arg2, options) {
+      return assertResult(arg1 === arg2, options);
+    }
+  );
 
-Handlebars.registerHelper(
-  HandlebarHelpersEnum.NE,
-  function (arg1, arg2, options) {
-    return assertResult(arg1 !== arg2, options);
-  }
-);
+  handlebars.registerHelper(
+    HandlebarHelpersEnum.NE,
+    function (arg1, arg2, options) {
+      return assertResult(arg1 !== arg2, options);
+    }
+  );
+
+  return handlebars;
+}
 
 @Injectable()
 export class CompileTemplate {
   async execute(command: CompileTemplateCommand): Promise<string> {
     const templateContent = command.template || '';
+
     let result = '';
     try {
-      const template = Handlebars.compile(templateContent);
+      const handlebars = createHandlebarsInstance(command.i18next);
+      const template = handlebars.compile(templateContent);
 
       result = template(command.data, {});
     } catch (e: any) {
       throw new ApiException(
-        e?.message || `Message content could not be generated`
+        e?.message || `Handlebars message content could not be generated ${e}`
       );
     }
 
diff --git a/libs/application-generic/src/usecases/conditions-filter/conditions-filter.usecase.ts b/libs/application-generic/src/usecases/conditions-filter/conditions-filter.usecase.ts
index 703eeadfbf8..8e0c0a4647e 100644
--- a/libs/application-generic/src/usecases/conditions-filter/conditions-filter.usecase.ts
+++ b/libs/application-generic/src/usecases/conditions-filter/conditions-filter.usecase.ts
@@ -645,14 +645,12 @@ export class ConditionsFilter extends Filter {
     job: IJob
   ): Promise<string | undefined> {
     try {
-      return await this.compileTemplate.execute(
-        CompileTemplateCommand.create({
-          template: value,
-          data: {
-            ...variables,
-          },
-        })
-      );
+      return await this.compileTemplate.execute({
+        template: value,
+        data: {
+          ...variables,
+        },
+      });
     } catch (e: any) {
       await this.executionLogRoute.execute(
         ExecutionLogRouteCommand.create({
diff --git a/libs/application-generic/tsconfig.json b/libs/application-generic/tsconfig.json
index 3f478640993..e417b7aaf4c 100644
--- a/libs/application-generic/tsconfig.json
+++ b/libs/application-generic/tsconfig.json
@@ -1,6 +1,7 @@
 {
   "extends": "../../tsconfig.json",
   "compilerOptions": {
+    "sourceMap": true,
     "strictNullChecks": false,
     "allowSyntheticDefaultImports": true,
     "outDir": "build/main",
diff --git a/libs/application-generic/tsconfig.module.json b/libs/application-generic/tsconfig.module.json
index 9df84697738..82d60a2e6d7 100644
--- a/libs/application-generic/tsconfig.module.json
+++ b/libs/application-generic/tsconfig.module.json
@@ -1,6 +1,7 @@
 {
   "extends": "./tsconfig",
   "compilerOptions": {
+    "sourceMap": true,
     "target": "esnext",
     "outDir": "build/module",
     "module": "esnext",