From 6e0855f9b32c230c9ad5594fb6af6dd460300fc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20S=C3=B3jko?= Date: Wed, 7 Dec 2022 11:09:59 +0100 Subject: [PATCH] feat(scheduler): add scheduled emails contents --- .pnp.cjs | 1 + packages/scheduler/jest.config.js | 1 + packages/scheduler/package.json | 1 + .../src/Domain/Email/EncourageEmailBackups.ts | 9 + .../Email/EncourageSubscriptionPurchasing.ts | 11 + .../src/Domain/Email/ExitInterview.ts | 9 + .../Domain/Email/encourage-email-backups.html | 18 ++ .../encourage-subscription-purchasing.html | 83 +++++++ .../src/Domain/Email/exit-interview.html | 28 +++ .../Domain/Event/DomainEventFactory.spec.ts | 223 ------------------ .../src/Domain/Event/DomainEventFactory.ts | 15 +- .../Event/DomainEventFactoryInterface.ts | 13 +- .../src/Domain/Job/JobDoneInterpreter.spec.ts | 37 ++- .../src/Domain/Job/JobDoneInterpreter.ts | 41 +++- yarn.lock | 1 + 15 files changed, 221 insertions(+), 270 deletions(-) create mode 100644 packages/scheduler/src/Domain/Email/EncourageEmailBackups.ts create mode 100644 packages/scheduler/src/Domain/Email/EncourageSubscriptionPurchasing.ts create mode 100644 packages/scheduler/src/Domain/Email/ExitInterview.ts create mode 100644 packages/scheduler/src/Domain/Email/encourage-email-backups.html create mode 100644 packages/scheduler/src/Domain/Email/encourage-subscription-purchasing.html create mode 100644 packages/scheduler/src/Domain/Email/exit-interview.html delete mode 100644 packages/scheduler/src/Domain/Event/DomainEventFactory.spec.ts diff --git a/.pnp.cjs b/.pnp.cjs index 22db4dc74..b6195528a 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -3056,6 +3056,7 @@ const RAW_RUNTIME_STATE = ["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\ ["@sentry/node", "npm:7.19.0"],\ ["@standardnotes/common", "workspace:packages/common"],\ + ["@standardnotes/domain-core", "workspace:packages/domain-core"],\ ["@standardnotes/domain-events", "workspace:packages/domain-events"],\ ["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\ ["@standardnotes/predicates", "workspace:packages/predicates"],\ diff --git a/packages/scheduler/jest.config.js b/packages/scheduler/jest.config.js index 09dfc80db..e3f10e3d7 100644 --- a/packages/scheduler/jest.config.js +++ b/packages/scheduler/jest.config.js @@ -7,4 +7,5 @@ module.exports = { transform: { ...tsjPreset.transform, }, + coveragePathIgnorePatterns: ['/Bootstrap/', '/Infra/', '/Domain/Email/', '/Domain/Event/'], } diff --git a/packages/scheduler/package.json b/packages/scheduler/package.json index 2f062b76d..c30d06ad4 100644 --- a/packages/scheduler/package.json +++ b/packages/scheduler/package.json @@ -27,6 +27,7 @@ "@newrelic/winston-enricher": "^4.0.0", "@sentry/node": "^7.19.0", "@standardnotes/common": "workspace:*", + "@standardnotes/domain-core": "workspace:^", "@standardnotes/domain-events": "workspace:*", "@standardnotes/domain-events-infra": "workspace:*", "@standardnotes/predicates": "workspace:*", diff --git a/packages/scheduler/src/Domain/Email/EncourageEmailBackups.ts b/packages/scheduler/src/Domain/Email/EncourageEmailBackups.ts new file mode 100644 index 000000000..6c169e8a4 --- /dev/null +++ b/packages/scheduler/src/Domain/Email/EncourageEmailBackups.ts @@ -0,0 +1,9 @@ +import { readFileSync } from 'fs' + +export function getSubject(): string { + return 'Enable email backups for your account' +} + +export function getBody(): string { + return readFileSync(`${__dirname}/encourage-email-backups.html`).toString() +} diff --git a/packages/scheduler/src/Domain/Email/EncourageSubscriptionPurchasing.ts b/packages/scheduler/src/Domain/Email/EncourageSubscriptionPurchasing.ts new file mode 100644 index 000000000..16867fcb2 --- /dev/null +++ b/packages/scheduler/src/Domain/Email/EncourageSubscriptionPurchasing.ts @@ -0,0 +1,11 @@ +import { readFileSync } from 'fs' + +export function getSubject(): string { + return 'Checking in after one month with Standard Notes' +} + +export function getBody(registrationDate: string): string { + const body = readFileSync(`${__dirname}/encourage-subscription-purchasing.html`).toString() + + return body.replace('%%REGISTRATION_DATE%%', registrationDate) +} diff --git a/packages/scheduler/src/Domain/Email/ExitInterview.ts b/packages/scheduler/src/Domain/Email/ExitInterview.ts new file mode 100644 index 000000000..1d08354d0 --- /dev/null +++ b/packages/scheduler/src/Domain/Email/ExitInterview.ts @@ -0,0 +1,9 @@ +import { readFileSync } from 'fs' + +export function getSubject(): string { + return 'Can we ask why you canceled?' +} + +export function getBody(): string { + return readFileSync(`${__dirname}/exit-interview.html`).toString() +} diff --git a/packages/scheduler/src/Domain/Email/encourage-email-backups.html b/packages/scheduler/src/Domain/Email/encourage-email-backups.html new file mode 100644 index 000000000..b35b867c9 --- /dev/null +++ b/packages/scheduler/src/Domain/Email/encourage-email-backups.html @@ -0,0 +1,18 @@ +
+

+ Did you know you can enable daily email backups for your account? This free feature sends an + email to your inbox with an encrypted backup file including all your notes and tags. +

+

+ Email backups are an important feature that help protect you against worst-case scenarios. Your backups can be + used to restore your account to a previous state, or to import old versions of notes into your present + account. +

+

+ To enable free email backups, use the Standard Notes web or desktop app, and open Preferences > Backups > Email Backups. +

+ + + Learn more about daily email backups → + +
diff --git a/packages/scheduler/src/Domain/Email/encourage-subscription-purchasing.html b/packages/scheduler/src/Domain/Email/encourage-subscription-purchasing.html new file mode 100644 index 000000000..33a11faf6 --- /dev/null +++ b/packages/scheduler/src/Domain/Email/encourage-subscription-purchasing.html @@ -0,0 +1,83 @@ +
+

Hi there,

+

+ We hope you've been finding great use out of Standard Notes. We built Standard Notes to be a secure place for + your most sensitive notes and files. +

+

+ As a reminder, + + you signed up for the Standard Notes free plan on %%REGISTRATION_DATE%% + + Your free account comes with standard features like end-to-end encryption, multiple-device sync, and + two-factor authentication. +

+

+ If you're ready to advance your usage of Standard Notes, we recommend upgrading to one of our more powerful + plans. +

+ +

+ Professional comes with a 90-day money back guarantee, so if you're not completely satisfied, we're happy to + refund your full purchase amount. +

+

+ + Upgrade your plan → + +

+

+ + Learn more about the features → + +

+

+ Questions & Answers +

+

+ How does Standard Notes compare with conventional note-taking apps? +

+

+ Data you store with Standard Notes is encrypted with end-to-end encryption using a key only you know. Because + of this, we can't read your notes, and neither can anyone else. +

+

+ What kind of notes should I store in Standard Notes? +

+

+ This question can be reframed as: "What shouldn't I store in non-private services?" This would include + sensitive/sensual data related to your health and wellness, secrets and keys, notes and documents with + personally identifiable information that, if leaked, would lead to the theft of your identity, and business, + financial, or legal information which cover non-public or confidential information. +

+

+ Where can I access my notes? +

+

+ Providing you with easy access to your notes and files on all your devices is a key focus for us. We provide + secure and well-designed applications for your web browser, desktop (macOS, Windows, Linux,) and mobile + (Android and iOS). +

+

+ I have more questions. +

+

+ We love questions. Head over to our Help page to see if your question is answered there. If not, reply + directly to this email or send an email to help@standardnotes.com and + we'd be happy to help. +

+
, diff --git a/packages/scheduler/src/Domain/Email/exit-interview.html b/packages/scheduler/src/Domain/Email/exit-interview.html new file mode 100644 index 000000000..275c3ce24 --- /dev/null +++ b/packages/scheduler/src/Domain/Email/exit-interview.html @@ -0,0 +1,28 @@ +
+

+ We're truly sad to see you leave. Our mission is simple: build the best, most private, and most secure + note-taking app available. It's clear we've fallen short of your expectations somewhere along the way. +

+

+ We just want you to know—if price was the reason you canceled, we're not willing to lose you. That's no issue + for us and we're happy to work out something that fits better with your budget. If price is your primary + concern, please click the link below, and we'll get in touch with some options. +

+ Apply For A Limited Discount Offer → +

+ If you canceled for another reason, such as a missing feature, or a feature that wasn't behaving or working as + you expected, please let us know! We build this product for you, and feedback from customers like yourself who + are willing to pay for a product is most crucial for us as we continue to evolve and iterate on Standard + Notes. +

+

If you have a minute, please fill out this brief exit interview:

+ Short Exit Interview → +

+ Our team reads every single response, and your feedback will be shared with the relevant department within our + team. +

+

+ If you have any other thoughts or questions, please feel free to reply directly to this email, and a member of + our support team will be in touch with you. +

+
diff --git a/packages/scheduler/src/Domain/Event/DomainEventFactory.spec.ts b/packages/scheduler/src/Domain/Event/DomainEventFactory.spec.ts deleted file mode 100644 index e05c52f3e..000000000 --- a/packages/scheduler/src/Domain/Event/DomainEventFactory.spec.ts +++ /dev/null @@ -1,223 +0,0 @@ -import 'reflect-metadata' - -import { EmailMessageIdentifier } from '@standardnotes/common' -import { TimerInterface } from '@standardnotes/time' - -import { DomainEventFactory } from './DomainEventFactory' -import { PredicateAuthority, PredicateName } from '@standardnotes/predicates' -import { Job } from '../Job/Job' -import { Predicate } from '../Predicate/Predicate' - -describe('DomainEventFactory', () => { - let timer: TimerInterface - - const createFactory = () => new DomainEventFactory(timer) - - beforeEach(() => { - timer = {} as jest.Mocked - timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1) - timer.getUTCDate = jest.fn().mockReturnValue(new Date(1)) - }) - - it('should create a DISCOUNT_APPLY_REQUESTED event', () => { - expect( - createFactory().createDiscountApplyRequestedEvent({ - userEmail: 'test@test.te', - discountCode: 'off-10', - }), - ).toEqual({ - createdAt: expect.any(Date), - meta: { - correlation: { - userIdentifier: 'test@test.te', - userIdentifierType: 'email', - }, - origin: 'scheduler', - }, - payload: { - userEmail: 'test@test.te', - discountCode: 'off-10', - }, - type: 'DISCOUNT_APPLY_REQUESTED', - }) - }) - - it('should create a DISCOUNT_WITHDRAW_REQUESTED event', () => { - expect( - createFactory().createDiscountWithdrawRequestedEvent({ - userEmail: 'test@test.te', - discountCode: 'off-10', - }), - ).toEqual({ - createdAt: expect.any(Date), - meta: { - correlation: { - userIdentifier: 'test@test.te', - userIdentifierType: 'email', - }, - origin: 'scheduler', - }, - payload: { - userEmail: 'test@test.te', - discountCode: 'off-10', - }, - type: 'DISCOUNT_WITHDRAW_REQUESTED', - }) - }) - - it('should create a EXIT_DISCOUNT_WITHDRAW_REQUESTED event', () => { - expect( - createFactory().createExitDiscountWithdrawRequestedEvent({ - userEmail: 'test@test.te', - discountCode: 'exit-20', - }), - ).toEqual({ - createdAt: expect.any(Date), - meta: { - correlation: { - userIdentifier: 'test@test.te', - userIdentifierType: 'email', - }, - origin: 'scheduler', - }, - payload: { - userEmail: 'test@test.te', - discountCode: 'exit-20', - }, - type: 'EXIT_DISCOUNT_WITHDRAW_REQUESTED', - }) - }) - - it('should create a EMAIL_MESSAGE_REQUESTED event', () => { - expect( - createFactory().createEmailMessageRequestedEvent({ - userEmail: 'test@test.te', - messageIdentifier: EmailMessageIdentifier.ENCOURAGE_EMAIL_BACKUPS, - context: { - foo: 'bar', - }, - }), - ).toEqual({ - createdAt: expect.any(Date), - meta: { - correlation: { - userIdentifier: 'test@test.te', - userIdentifierType: 'email', - }, - origin: 'scheduler', - }, - payload: { - messageIdentifier: 'ENCOURAGE_EMAIL_BACKUPS', - userEmail: 'test@test.te', - context: { - foo: 'bar', - }, - }, - type: 'EMAIL_MESSAGE_REQUESTED', - }) - }) - - it('should create a PREDICATE_VERIFICATION_REQUESTED event dedicated for auth', () => { - expect( - createFactory().createPredicateVerificationRequestedEvent( - { - uuid: '1-2-3', - userIdentifier: '2-3-4', - userIdentifierType: 'uuid', - } as jest.Mocked, - { - authority: PredicateAuthority.Auth, - name: PredicateName.EmailBackupsEnabled, - status: 'pending', - } as jest.Mocked, - ), - ).toEqual({ - createdAt: expect.any(Date), - meta: { - correlation: { - userIdentifier: '2-3-4', - userIdentifierType: 'uuid', - }, - origin: 'scheduler', - target: 'auth', - }, - payload: { - predicate: { - authority: 'auth', - jobUuid: '1-2-3', - name: 'email-backups-enabled', - }, - }, - type: 'PREDICATE_VERIFICATION_REQUESTED', - }) - }) - - it('should create a PREDICATE_VERIFICATION_REQUESTED event dedicated for syncing server', () => { - expect( - createFactory().createPredicateVerificationRequestedEvent( - { - uuid: '1-2-3', - userIdentifier: '2-3-4', - userIdentifierType: 'uuid', - } as jest.Mocked, - { - authority: PredicateAuthority.SyncingServer, - name: PredicateName.EmailBackupsEnabled, - status: 'pending', - } as jest.Mocked, - ), - ).toEqual({ - createdAt: expect.any(Date), - meta: { - correlation: { - userIdentifier: '2-3-4', - userIdentifierType: 'uuid', - }, - origin: 'scheduler', - target: 'syncing-server', - }, - payload: { - predicate: { - authority: 'syncing-server', - jobUuid: '1-2-3', - name: 'email-backups-enabled', - }, - }, - type: 'PREDICATE_VERIFICATION_REQUESTED', - }) - }) - - it('should create a PREDICATE_VERIFICATION_REQUESTED event dedicated for unknown target', () => { - expect( - createFactory().createPredicateVerificationRequestedEvent( - { - uuid: '1-2-3', - userIdentifier: '2-3-4', - userIdentifierType: 'uuid', - } as jest.Mocked, - { - authority: 'foobar' as PredicateAuthority, - name: PredicateName.EmailBackupsEnabled, - status: 'pending', - } as jest.Mocked, - ), - ).toEqual({ - createdAt: expect.any(Date), - meta: { - correlation: { - userIdentifier: '2-3-4', - userIdentifierType: 'uuid', - }, - origin: 'scheduler', - }, - payload: { - predicate: { - authority: 'foobar', - jobUuid: '1-2-3', - name: 'email-backups-enabled', - }, - }, - type: 'PREDICATE_VERIFICATION_REQUESTED', - }) - }) -}) diff --git a/packages/scheduler/src/Domain/Event/DomainEventFactory.ts b/packages/scheduler/src/Domain/Event/DomainEventFactory.ts index b499d20fb..6f2dd8b6f 100644 --- a/packages/scheduler/src/Domain/Event/DomainEventFactory.ts +++ b/packages/scheduler/src/Domain/Event/DomainEventFactory.ts @@ -1,9 +1,8 @@ -import { EmailMessageIdentifier } from '@standardnotes/common' import { DiscountApplyRequestedEvent, DiscountWithdrawRequestedEvent, DomainEventService, - EmailMessageRequestedEvent, + EmailRequestedEvent, ExitDiscountWithdrawRequestedEvent, PredicateVerificationRequestedEvent, } from '@standardnotes/domain-events' @@ -70,13 +69,15 @@ export class DomainEventFactory implements DomainEventFactoryInterface { } } - createEmailMessageRequestedEvent(dto: { + createEmailRequestedEvent(dto: { userEmail: string - messageIdentifier: EmailMessageIdentifier - context: Record - }): EmailMessageRequestedEvent { + messageIdentifier: string + level: string + body: string + subject: string + }): EmailRequestedEvent { return { - type: 'EMAIL_MESSAGE_REQUESTED', + type: 'EMAIL_REQUESTED', createdAt: this.timer.getUTCDate(), meta: { correlation: { diff --git a/packages/scheduler/src/Domain/Event/DomainEventFactoryInterface.ts b/packages/scheduler/src/Domain/Event/DomainEventFactoryInterface.ts index d375e8d6c..3244c425f 100644 --- a/packages/scheduler/src/Domain/Event/DomainEventFactoryInterface.ts +++ b/packages/scheduler/src/Domain/Event/DomainEventFactoryInterface.ts @@ -1,8 +1,7 @@ -import { EmailMessageIdentifier } from '@standardnotes/common' import { DiscountApplyRequestedEvent, DiscountWithdrawRequestedEvent, - EmailMessageRequestedEvent, + EmailRequestedEvent, ExitDiscountWithdrawRequestedEvent, PredicateVerificationRequestedEvent, } from '@standardnotes/domain-events' @@ -12,11 +11,13 @@ import { Predicate } from '../Predicate/Predicate' export interface DomainEventFactoryInterface { createPredicateVerificationRequestedEvent(job: Job, predicate: Predicate): PredicateVerificationRequestedEvent - createEmailMessageRequestedEvent(dto: { + createEmailRequestedEvent(dto: { userEmail: string - messageIdentifier: EmailMessageIdentifier - context: Record - }): EmailMessageRequestedEvent + messageIdentifier: string + level: string + body: string + subject: string + }): EmailRequestedEvent createDiscountApplyRequestedEvent(dto: { userEmail: string; discountCode: string }): DiscountApplyRequestedEvent createDiscountWithdrawRequestedEvent(dto: { userEmail: string; discountCode: string }): DiscountWithdrawRequestedEvent createExitDiscountWithdrawRequestedEvent(dto: { diff --git a/packages/scheduler/src/Domain/Job/JobDoneInterpreter.spec.ts b/packages/scheduler/src/Domain/Job/JobDoneInterpreter.spec.ts index fc8a4b2a7..f0f603a78 100644 --- a/packages/scheduler/src/Domain/Job/JobDoneInterpreter.spec.ts +++ b/packages/scheduler/src/Domain/Job/JobDoneInterpreter.spec.ts @@ -6,6 +6,7 @@ import { ExitDiscountWithdrawRequestedEvent, } from '@standardnotes/domain-events' import { PredicateName } from '@standardnotes/predicates' +import { TimerInterface } from '@standardnotes/time' import 'reflect-metadata' import { Logger } from 'winston' import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface' @@ -26,13 +27,17 @@ describe('JobDoneInterpreter', () => { let domainEventPublisher: DomainEventPublisherInterface let job: Job let logger: Logger + let timer: TimerInterface const createInterpreter = () => - new JobDoneInterpreter(jobRepository, predicateRepository, domainEventFactory, domainEventPublisher, logger) + new JobDoneInterpreter(jobRepository, predicateRepository, domainEventFactory, domainEventPublisher, timer, logger) beforeEach(() => { job = {} as jest.Mocked + timer = {} as jest.Mocked + timer.convertMicrosecondsToDate = jest.fn().mockReturnValue(new Date()) + jobRepository = {} as jest.Mocked jobRepository.findOneByUuid = jest.fn().mockReturnValue(job) @@ -40,7 +45,7 @@ describe('JobDoneInterpreter', () => { predicateRepository.findByJobUuid = jest.fn().mockReturnValue([]) domainEventFactory = {} as jest.Mocked - domainEventFactory.createEmailMessageRequestedEvent = jest + domainEventFactory.createEmailRequestedEvent = jest .fn() .mockReturnValue({} as jest.Mocked) domainEventFactory.createDiscountApplyRequestedEvent = jest @@ -89,11 +94,7 @@ describe('JobDoneInterpreter', () => { await createInterpreter().interpret('1-2-3') - expect(domainEventFactory.createEmailMessageRequestedEvent).toHaveBeenCalledWith({ - context: {}, - messageIdentifier: 'ENCOURAGE_EMAIL_BACKUPS', - userEmail: 'test@test.te', - }) + expect(domainEventFactory.createEmailRequestedEvent).toHaveBeenCalled() expect(domainEventPublisher.publish).toHaveBeenCalled() }) @@ -111,7 +112,7 @@ describe('JobDoneInterpreter', () => { await createInterpreter().interpret('1-2-3') - expect(domainEventFactory.createEmailMessageRequestedEvent).not.toHaveBeenCalled() + expect(domainEventFactory.createEmailRequestedEvent).not.toHaveBeenCalled() expect(domainEventPublisher.publish).not.toHaveBeenCalled() }) @@ -124,7 +125,7 @@ describe('JobDoneInterpreter', () => { await createInterpreter().interpret('1-2-3') - expect(domainEventFactory.createEmailMessageRequestedEvent).not.toHaveBeenCalled() + expect(domainEventFactory.createEmailRequestedEvent).not.toHaveBeenCalled() expect(domainEventPublisher.publish).not.toHaveBeenCalled() }) @@ -143,11 +144,7 @@ describe('JobDoneInterpreter', () => { await createInterpreter().interpret('1-2-3') - expect(domainEventFactory.createEmailMessageRequestedEvent).toHaveBeenCalledWith({ - context: { userRegisteredAt: 123 }, - messageIdentifier: 'ENCOURAGE_SUBSCRIPTION_PURCHASING', - userEmail: 'test@test.te', - }) + expect(domainEventFactory.createEmailRequestedEvent).toHaveBeenCalled() expect(domainEventPublisher.publish).toHaveBeenCalled() }) @@ -160,7 +157,7 @@ describe('JobDoneInterpreter', () => { await createInterpreter().interpret('1-2-3') - expect(domainEventFactory.createEmailMessageRequestedEvent).not.toHaveBeenCalled() + expect(domainEventFactory.createEmailRequestedEvent).not.toHaveBeenCalled() expect(domainEventPublisher.publish).not.toHaveBeenCalled() }) @@ -173,11 +170,7 @@ describe('JobDoneInterpreter', () => { await createInterpreter().interpret('1-2-3') - expect(domainEventFactory.createEmailMessageRequestedEvent).toHaveBeenCalledWith({ - context: {}, - messageIdentifier: 'EXIT_INTERVIEW', - userEmail: 'test@test.te', - }) + expect(domainEventFactory.createEmailRequestedEvent).toHaveBeenCalled() expect(domainEventPublisher.publish).toHaveBeenCalled() }) @@ -190,7 +183,7 @@ describe('JobDoneInterpreter', () => { await createInterpreter().interpret('1-2-3') - expect(domainEventFactory.createEmailMessageRequestedEvent).not.toHaveBeenCalled() + expect(domainEventFactory.createEmailRequestedEvent).not.toHaveBeenCalled() expect(domainEventPublisher.publish).not.toHaveBeenCalled() }) @@ -295,7 +288,7 @@ describe('JobDoneInterpreter', () => { await createInterpreter().interpret('1-2-3') - expect(domainEventFactory.createEmailMessageRequestedEvent).not.toHaveBeenCalled() + expect(domainEventFactory.createEmailRequestedEvent).not.toHaveBeenCalled() expect(domainEventPublisher.publish).not.toHaveBeenCalled() }) }) diff --git a/packages/scheduler/src/Domain/Job/JobDoneInterpreter.ts b/packages/scheduler/src/Domain/Job/JobDoneInterpreter.ts index 1870a6225..6d9aa5ea3 100644 --- a/packages/scheduler/src/Domain/Job/JobDoneInterpreter.ts +++ b/packages/scheduler/src/Domain/Job/JobDoneInterpreter.ts @@ -1,8 +1,9 @@ -import { EmailMessageIdentifier } from '@standardnotes/common' import { DomainEventPublisherInterface } from '@standardnotes/domain-events' import { PredicateName } from '@standardnotes/predicates' import { inject, injectable } from 'inversify' import { Logger } from 'winston' +import { EmailLevel } from '@standardnotes/domain-core' +import { TimerInterface } from '@standardnotes/time' import TYPES from '../../Bootstrap/Types' import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface' @@ -13,6 +14,15 @@ import { Job } from './Job' import { JobDoneInterpreterInterface } from './JobDoneInterpreterInterface' import { JobName } from './JobName' import { JobRepositoryInterface } from './JobRepositoryInterface' +import { getSubject as getExitInterviewSubject, getBody as getExitInterviewBody } from '../Email/ExitInterview' +import { + getSubject as getEncourageEmailBackupsSubject, + getBody as getEncourageEmailBackupsBody, +} from '../Email/EncourageEmailBackups' +import { + getSubject as getEncourageSubscriptionPurchasingSubject, + getBody as getEncourageSubscriptionPurchasingBody, +} from '../Email/EncourageSubscriptionPurchasing' @injectable() export class JobDoneInterpreter implements JobDoneInterpreterInterface { @@ -21,6 +31,7 @@ export class JobDoneInterpreter implements JobDoneInterpreterInterface { @inject(TYPES.PredicateRepository) private predicateRepository: PredicateRepositoryInterface, @inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface, @inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface, + @inject(TYPES.Timer) private timer: TimerInterface, @inject(TYPES.Logger) private logger: Logger, ) {} @@ -81,10 +92,12 @@ export class JobDoneInterpreter implements JobDoneInterpreterInterface { this.logger.debug(`[${job.uuid}]${job.name}: requesting email backup encouragement email.`) await this.domainEventPublisher.publish( - this.domainEventFactory.createEmailMessageRequestedEvent({ + this.domainEventFactory.createEmailRequestedEvent({ userEmail: job.userIdentifier, - messageIdentifier: EmailMessageIdentifier.ENCOURAGE_EMAIL_BACKUPS, - context: {}, + messageIdentifier: 'ENCOURAGE_EMAIL_BACKUPS', + subject: getEncourageEmailBackupsSubject(), + body: getEncourageEmailBackupsBody(), + level: EmailLevel.LEVELS.System, }), ) } @@ -93,12 +106,14 @@ export class JobDoneInterpreter implements JobDoneInterpreterInterface { this.logger.debug(`[${job.uuid}]${job.name}: requesting subscription purchase encouragement email.`) await this.domainEventPublisher.publish( - this.domainEventFactory.createEmailMessageRequestedEvent({ + this.domainEventFactory.createEmailRequestedEvent({ userEmail: job.userIdentifier, - messageIdentifier: EmailMessageIdentifier.ENCOURAGE_SUBSCRIPTION_PURCHASING, - context: { - userRegisteredAt: job.createdAt, - }, + messageIdentifier: 'ENCOURAGE_SUBSCRIPTION_PURCHASING', + subject: getEncourageSubscriptionPurchasingSubject(), + body: getEncourageSubscriptionPurchasingBody( + this.timer.convertMicrosecondsToDate(job.createdAt).toLocaleString(), + ), + level: EmailLevel.LEVELS.System, }), ) } @@ -107,10 +122,12 @@ export class JobDoneInterpreter implements JobDoneInterpreterInterface { this.logger.debug(`[${job.uuid}]${job.name}: requesting exit interview email.`) await this.domainEventPublisher.publish( - this.domainEventFactory.createEmailMessageRequestedEvent({ + this.domainEventFactory.createEmailRequestedEvent({ userEmail: job.userIdentifier, - messageIdentifier: EmailMessageIdentifier.EXIT_INTERVIEW, - context: {}, + messageIdentifier: 'EXIT_INTERVIEW', + subject: getExitInterviewSubject(), + body: getExitInterviewBody(), + level: EmailLevel.LEVELS.System, }), ) } diff --git a/yarn.lock b/yarn.lock index 637689046..fc95c3a4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2284,6 +2284,7 @@ __metadata: "@newrelic/winston-enricher": "npm:^4.0.0" "@sentry/node": "npm:^7.19.0" "@standardnotes/common": "workspace:*" + "@standardnotes/domain-core": "workspace:^" "@standardnotes/domain-events": "workspace:*" "@standardnotes/domain-events-infra": "workspace:*" "@standardnotes/predicates": "workspace:*"