emails - polish + better dark theme handling
DELETE
src/assets/email/mjml-components/config.mjml
-<mj-head>
- <mj-title>{{i18n:title}}</mj-title>
- <mj-font name="Roboto" href="https://fonts.googleapis.com/css?family=Roboto:300,500"></mj-font>
- <mj-attributes>
- <mj-all font-family="Roboto, Helvetica, sans-serif"></mj-all>
- <mj-text font-weight="300" font-size="18px" color="#616161" line-height="20px"></mj-text>
- <mj-section padding="0px"></mj-section>
- </mj-attributes>
-</mj-head>

ADD
src/assets/email/mjml-components/email_config.mjml
+<mj-head>
+ <mj-title>{{i18n:title}}</mj-title>
+ <mj-font name="Roboto" href="https://fonts.googleapis.com/css?family=Roboto:300,500"></mj-font>
+ <mj-attributes>
+ <mj-section padding="0px"></mj-section>
+ <mj-all font-family="Roboto, Helvetica, sans-serif"></mj-all>
+ <mj-text font-weight="300" color="#616161" line-height="20px"></mj-text>
+ </mj-attributes>
+</mj-head>

RENAME
src/assets/email/mjml-components/footer.mjml
src/assets/email/mjml-components/email_footer.mjml
-<mj-section padding-top="20px" padding-bottom="20px">
+<mj-section background-color="white" padding-top="20px" padding-bottom="20px">
<mj-column>
<mj-divider border-width="1px" border-color="#E0E0E0" padding="0px" ></mj-divider>
<mj-text font-size="12px">

ADD
src/assets/email/mjml-components/email_header.mjml
+<mj-section padding-top="40px">
+ <mj-column>
+ <mj-image width="100px" href="{{evseDashboardURL}}" src="{{tenantLogoURL}}""></mj-image>
+ <mj-text color="white" font-size="12px">{{tenantName}}</mj-text>
+ </mj-column>
+</mj-section>

RENAME
src/assets/email/mjml-components/button.mjml
src/assets/email/mjml-components/email_main_action.mjml
-<mj-section>
+<mj-section background-color="white">
<mj-column>
<mj-button background-color="#308080" color="white" font-size="16px" href="{{buttonUrl}}">
{{i18n:buttonText}}

RENAME
src/assets/email/mjml-components/text1.mjml
src/assets/email/mjml-components/email_main_message.mjml
-<mj-section padding-top="20px">
+<mj-section background-color="white">
<mj-column>
<mj-text>
<p>{{i18n:common.greetings}},</p>

RENAME
src/assets/email/mjml-components/table.mjml
src/assets/email/mjml-components/email_table.mjml
RENAME
src/assets/email/mjml-components/title.mjml
src/assets/email/mjml-components/email_title.mjml
-<mj-section>
+<mj-section background-color="white" padding-top="20px">
<mj-column>
<mj-text font-weight="500" font-size="20px">{{i18n:title}}</mj-text>
</mj-column>

DELETE
src/assets/email/mjml-components/header.mjml
-<mj-section>
- <mj-column>
- <mj-image width="100px" href="{{evseDashboardURL}}" src="{{tenantLogoURL}}""></mj-image>
- <mj-text font-size="12px">{{tenantName}}</mj-text>
- <mj-divider border-width="1px" border-color="#E0E0E0"></mj-divider>
- </mj-column>
-</mj-section>

MODIFY
src/notification/email/EMailNotificationTask.ts
src/notification/email/EMailNotificationTask.ts
/* eslint-disable max-len */
-import { AccountVerificationNotification, AdminAccountVerificationNotification, BaseNotification, BillingAccountActivationNotification, BillingAccountCreationLinkNotification, BillingInvoiceSynchronizationFailedNotification, BillingNewInvoiceNotification, BillingUserSynchronizationFailedNotification, CarCatalogSynchronizationFailedNotification, ChargingStationRegisteredNotification, ChargingStationStatusErrorNotification, ComputeAndApplyChargingProfilesFailedNotification, EmailNotificationMessage, EndOfChargeNotification, EndOfSessionNotification, EndOfSignedSessionNotification, EndUserErrorNotification, NewRegisteredUserNotification, NotificationResult, NotificationSeverity, OCPIPatchChargingStationsStatusesErrorNotification, OICPPatchChargingStationsErrorNotification, OICPPatchChargingStationsStatusesErrorNotification, OfflineChargingStationNotification, OptimalChargeReachedNotification, PreparingSessionNotStartedNotification, RequestPasswordNotification, SessionNotStartedNotification, TransactionStartedNotification, UnknownUserBadgedNotification, UserAccountInactivityNotification, UserAccountStatusChangedNotification, UserCreatePassword, VerificationEmailNotification } from '../../types/UserNotifications';
-import EmailComponentManager, { EmailComponent } from './email-component-manager/EmailComponentManager';
+import { AccountVerificationNotification, AdminAccountVerificationNotification, BillingAccountActivationNotification, BillingAccountCreationLinkNotification, BillingInvoiceSynchronizationFailedNotification, BillingNewInvoiceNotification, BillingUserSynchronizationFailedNotification, CarCatalogSynchronizationFailedNotification, ChargingStationRegisteredNotification, ChargingStationStatusErrorNotification, ComputeAndApplyChargingProfilesFailedNotification, EmailNotificationMessage, EndOfChargeNotification, EndOfSessionNotification, EndOfSignedSessionNotification, EndUserErrorNotification, NewRegisteredUserNotification, NotificationResult, NotificationSeverity, OCPIPatchChargingStationsStatusesErrorNotification, OICPPatchChargingStationsErrorNotification, OICPPatchChargingStationsStatusesErrorNotification, OfflineChargingStationNotification, OptimalChargeReachedNotification, PreparingSessionNotStartedNotification, RequestPasswordNotification, SessionNotStartedNotification, TransactionStartedNotification, UnknownUserBadgedNotification, UserAccountInactivityNotification, UserAccountStatusChangedNotification, UserCreatePassword, VerificationEmailNotification } from '../../types/UserNotifications';
+import EmailComponentManager, { EmailComponent } from './EmailComponentManager';
import FeatureToggles, { Feature } from '../../utils/FeatureToggles';
import { Message, SMTPClient, SMTPError } from 'emailjs';
@@ -22,7 +22,7 @@ import Utils from '../../utils/Utils';
import ejs from 'ejs';
import fs from 'fs';
import global from '../../types/GlobalType';
-import mjmlBuilder from './mjml-builder/MjmlBuilder';
+import mjmlBuilder from './EmailMjmlBuilder';
import rfc2047 from 'rfc2047';
const MODULE_NAME = 'EMailNotificationTask';
@@ -68,19 +68,19 @@ export default class EMailNotificationTask implements NotificationTask {
public async sendOptimalChargeReached(data: OptimalChargeReachedNotification, user: User, tenant: Tenant, severity: NotificationSeverity): Promise<NotificationResult> {
data.buttonUrl = data.evseDashboardChargingStationURL;
- const optionalComponents = [await EmailComponentManager.getComponent(EmailComponent.TABLE)];
+ const optionalComponents = [await EmailComponentManager.getComponent(EmailComponent.MJML_TABLE)];
return await this.prepareAndSendEmail('optimal-charge-reached', data, user, tenant, severity, optionalComponents);
}
public async sendEndOfCharge(data: EndOfChargeNotification, user: User, tenant: Tenant, severity: NotificationSeverity): Promise<NotificationResult> {
data.buttonUrl = data.evseDashboardChargingStationURL;
- const optionalComponents = [await EmailComponentManager.getComponent(EmailComponent.TABLE)];
+ const optionalComponents = [await EmailComponentManager.getComponent(EmailComponent.MJML_TABLE)];
return await this.prepareAndSendEmail('end-of-charge', data, user, tenant, severity, optionalComponents);
}
public async sendEndOfSession(data: EndOfSessionNotification, user: User, tenant: Tenant, severity: NotificationSeverity): Promise<NotificationResult> {
data.buttonUrl = data.evseDashboardChargingStationURL;
- const optionalComponents = [await EmailComponentManager.getComponent(EmailComponent.TABLE)];
+ const optionalComponents = [await EmailComponentManager.getComponent(EmailComponent.MJML_TABLE)];
return await this.prepareAndSendEmail('end-of-session', data, user, tenant, severity, optionalComponents);
}
@@ -161,11 +161,11 @@ export default class EMailNotificationTask implements NotificationTask {
public async sendOfflineChargingStations(data: OfflineChargingStationNotification, user: User, tenant: Tenant, severity: NotificationSeverity): Promise<NotificationResult> {
// TODO - old stuff - to be removed asap
- data.chargeBoxIDs = data.chargingStationIDs.join(", ");
+ data.chargeBoxIDs = data.chargingStationIDs.join(', ');
// Populate the context to have a human-readable message
data.nbChargingStationIDs = data.chargingStationIDs?.length || 0;
// Show only the ten first charging stations
- data.tenFirstChargingStationIDs = data.chargingStationIDs.slice(0, 10).join(", ") + "...";
+ data.tenFirstChargingStationIDs = data.chargingStationIDs.slice(0, 10).join(', ') + '...';
data.buttonUrl = data.evseDashboardURL;
return await this.prepareAndSendEmail('offline-charging-station', data, user, tenant, severity);
}
@@ -196,7 +196,7 @@ export default class EMailNotificationTask implements NotificationTask {
}
public async sendBillingNewInvoice(data: BillingNewInvoiceNotification, user: User, tenant: Tenant, severity: NotificationSeverity): Promise<NotificationResult> {
- const optionalComponents = [await EmailComponentManager.getComponent(EmailComponent.TABLE)];
+ const optionalComponents = [await EmailComponentManager.getComponent(EmailComponent.MJML_TABLE)];
let templateName: string;
if (FeatureToggles.isFeatureActive(Feature.NEW_EMAIL_TEMPLATES)) {
if (data.invoiceStatus === 'paid') {
@@ -383,10 +383,10 @@ export default class EMailNotificationTask implements NotificationTask {
const i18nInstance = I18nManager.getInstanceForLocale(recipient.locale);
// Aggregate the templates
const template = (await mjmlBuilder.initialize())
- .addToBody(await EmailComponentManager.getComponent(EmailComponent.TITLE))
- .addToBody(await EmailComponentManager.getComponent(EmailComponent.TEXT1))
+ .addToBody(await EmailComponentManager.getComponent(EmailComponent.MJML_TITLE))
+ .addToBody(await EmailComponentManager.getComponent(EmailComponent.MJML_MAIN_MESSAGE))
.addToBody(optionalComponents.join())
- .addToBody(await EmailComponentManager.getComponent(EmailComponent.BUTTON))
+ .addToBody(await EmailComponentManager.getComponent(EmailComponent.MJML_MAIN_ACTION))
.buildTemplate();
template.resolve(i18nInstance, context, prefix);
// if (Utils.isDevelopmentEnv()) {

RENAME
src/notification/email/email-component-manager/EmailComponentManager.ts
src/notification/email/EmailComponentManager.ts
import { promises as fs } from 'fs';
-import global from '../../../types/GlobalType';
+import global from '../../types/GlobalType';
export enum EmailComponent {
- FOOTER = 'footer.mjml',
- HEADER = 'header.mjml',
- CONFIG = 'config.mjml',
-
- BUTTON = 'button.mjml',
- TITLE = 'title.mjml',
- TEXT1 = 'text1.mjml',
-
- TABLE='table.mjml',
+ MJML_CONFIG = 'email_config.mjml',
+ MJML_FOOTER = 'email_footer.mjml',
+ MJML_HEADER = 'email_header.mjml',
+ MJML_TITLE = 'email_title.mjml',
+ MJML_MAIN_MESSAGE = 'email_main_message.mjml',
+ MJML_TABLE='email_table.mjml',
+ MJML_MAIN_ACTION = 'email_main_action.mjml',
}
export default class EmailComponentManager {

RENAME
src/notification/email/mjml-builder/MjmlBuilder.ts
src/notification/email/EmailMjmlBuilder.ts
-import EmailComponentManager, { EmailComponent } from '../email-component-manager/EmailComponentManager';
+import EmailComponentManager, { EmailComponent } from './EmailComponentManager';
-import mjmlTemplate from '../mjml-template/MjmlTemplate';
+import mjmlTemplate from './EmailMjmlTemplate';
export default class MjmlBuilder {
private header: string;
@@ -17,9 +17,9 @@ export default class MjmlBuilder {
public static async initialize(): Promise<MjmlBuilder> {
const instance = new MjmlBuilder();
- instance.addConfig(await EmailComponentManager.getComponent(EmailComponent.CONFIG))
- .addHeader(await EmailComponentManager.getComponent(EmailComponent.HEADER))
- .addFooter(await EmailComponentManager.getComponent(EmailComponent.FOOTER));
+ instance.addConfig(await EmailComponentManager.getComponent(EmailComponent.MJML_CONFIG))
+ .addHeader(await EmailComponentManager.getComponent(EmailComponent.MJML_HEADER))
+ .addFooter(await EmailComponentManager.getComponent(EmailComponent.MJML_FOOTER));
return instance;
}
@@ -49,7 +49,7 @@ export default class MjmlBuilder {
const template =
'<mjml>' +
this.config +
- '<mj-body>' +
+ '<mj-body background-color="#308080">' +
this.header +
this.body.join() +
this.footer +

RENAME
src/notification/email/mjml-template/MjmlTemplate.ts
src/notification/email/EmailMjmlTemplate.ts
-import I18nManager from '../../../utils/I18nManager';
+import I18nManager from '../../utils/I18nManager';
import mjml2html from 'mjml';
export default class MjmlTemplate {
@@ -16,7 +16,7 @@ export default class MjmlTemplate {
return mjml2html(this.template).html;
}
- public resolve(i18nManager: I18nManager, context: any, prefix: string): void {
+ public resolve(i18nManager: I18nManager, context: Record<string, unknown>, prefix: string): void {
this.buildComponents(i18nManager, context, prefix);
this.prepareI18nSelectors(prefix);
const i18nSelectors = this.getI18nSelectors();
@@ -36,7 +36,7 @@ export default class MjmlTemplate {
}
}
- private buildTable(i18nManager: I18nManager, context: any, prefix: string): void {
+ private buildTable(i18nManager: I18nManager, context: Record<string, unknown>, prefix: string): void {
const regex = new RegExp(/_TABLE_CONTENT_/, 'g');
const match = this.template.match(regex);
if (!match) {
@@ -72,7 +72,7 @@ export default class MjmlTemplate {
this.replace(match[0], table);
}
- private buildComponents(i18nManager: I18nManager, context: any, prefix: string) {
+ private buildComponents(i18nManager: I18nManager, context: Record<string, unknown>, prefix: string) {
this.buildTable(i18nManager, context, prefix);
}

emails - polish + better dark theme handling