feat(scheduler): add scheduled emails contents
This commit is contained in:
parent
ec9e9ec387
commit
6e0855f9b3
15 changed files with 221 additions and 270 deletions
1
.pnp.cjs
generated
1
.pnp.cjs
generated
|
@ -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"],\
|
||||
|
|
|
@ -7,4 +7,5 @@ module.exports = {
|
|||
transform: {
|
||||
...tsjPreset.transform,
|
||||
},
|
||||
coveragePathIgnorePatterns: ['/Bootstrap/', '/Infra/', '/Domain/Email/', '/Domain/Event/'],
|
||||
}
|
||||
|
|
|
@ -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:*",
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
9
packages/scheduler/src/Domain/Email/ExitInterview.ts
Normal file
9
packages/scheduler/src/Domain/Email/ExitInterview.ts
Normal file
|
@ -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()
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<div>
|
||||
<p>
|
||||
Did you know you can enable daily email backups for your account? This <strong>free</strong> feature sends an
|
||||
email to your inbox with an encrypted backup file including all your notes and tags.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
To enable free email backups, use the Standard Notes web or desktop app, and open Preferences > Backups > Email Backups.
|
||||
</p>
|
||||
|
||||
<a href="https://standardnotes.com/help/28/how-do-i-enable-daily-email-backups">
|
||||
Learn more about daily email backups →
|
||||
</a>
|
||||
</div>
|
|
@ -0,0 +1,83 @@
|
|||
<div>
|
||||
<p>Hi there,</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
As a reminder,
|
||||
<strong>
|
||||
<em>you signed up for the Standard Notes free plan on %%REGISTRATION_DATE%%</em>
|
||||
</strong>
|
||||
Your free account comes with standard features like end-to-end encryption, multiple-device sync, and
|
||||
two-factor authentication.
|
||||
</p>
|
||||
<p>
|
||||
If you're ready to advance your usage of Standard Notes, we recommend upgrading to one of our more powerful
|
||||
plans.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Productivity</strong> <strong>($59/year)</strong> powers up your editing experience with unique
|
||||
and special-built note-types for markdown, rich text, spreadsheets, todo, and more.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Professional</strong> <strong>($99/year)</strong> gives you all the power of Productivity plus
|
||||
100GB of end-to-end encrypted file storage for your private photos, videos, and documents, plus family
|
||||
subscription sharing with up to 5 people.
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
<strong>
|
||||
<a href="https://standardnotes.com/plans">Upgrade your plan →</a>
|
||||
</strong>
|
||||
</p>
|
||||
<p>
|
||||
<strong>
|
||||
<a href="https://standardnotes.com/features">Learn more about the features →</a>
|
||||
</strong>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Questions & Answers</strong>
|
||||
</p>
|
||||
<p>
|
||||
<em>How does Standard Notes compare with conventional note-taking apps?</em>
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
<em>What kind of notes should I store in Standard Notes?</em>
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
<em>Where can I access my notes?</em>
|
||||
</p>
|
||||
<p>
|
||||
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).
|
||||
</p>
|
||||
<p>
|
||||
<em>I have more questions.</em>
|
||||
</p>
|
||||
<p>
|
||||
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 <a href="help@standardnotes.com">help@standardnotes.com</a> and
|
||||
we'd be happy to help.
|
||||
</p>
|
||||
</div>,
|
28
packages/scheduler/src/Domain/Email/exit-interview.html
Normal file
28
packages/scheduler/src/Domain/Email/exit-interview.html
Normal file
|
@ -0,0 +1,28 @@
|
|||
<div>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<a href="https://app.standardnotes.com/?user-request=exit-discount">Apply For A Limited Discount Offer →</a>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>If you have a minute, please fill out this brief exit interview: </p>
|
||||
<a href="https://standardnotes.typeform.com/to/dX5lzPtm">Short Exit Interview →</a>
|
||||
<p>
|
||||
Our team reads every single response, and your feedback will be shared with the relevant department within our
|
||||
team.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</div>
|
|
@ -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<TimerInterface>
|
||||
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<Job>,
|
||||
{
|
||||
authority: PredicateAuthority.Auth,
|
||||
name: PredicateName.EmailBackupsEnabled,
|
||||
status: 'pending',
|
||||
} as jest.Mocked<Predicate>,
|
||||
),
|
||||
).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<Job>,
|
||||
{
|
||||
authority: PredicateAuthority.SyncingServer,
|
||||
name: PredicateName.EmailBackupsEnabled,
|
||||
status: 'pending',
|
||||
} as jest.Mocked<Predicate>,
|
||||
),
|
||||
).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<Job>,
|
||||
{
|
||||
authority: 'foobar' as PredicateAuthority,
|
||||
name: PredicateName.EmailBackupsEnabled,
|
||||
status: 'pending',
|
||||
} as jest.Mocked<Predicate>,
|
||||
),
|
||||
).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',
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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<string, unknown>
|
||||
}): EmailMessageRequestedEvent {
|
||||
messageIdentifier: string
|
||||
level: string
|
||||
body: string
|
||||
subject: string
|
||||
}): EmailRequestedEvent {
|
||||
return {
|
||||
type: 'EMAIL_MESSAGE_REQUESTED',
|
||||
type: 'EMAIL_REQUESTED',
|
||||
createdAt: this.timer.getUTCDate(),
|
||||
meta: {
|
||||
correlation: {
|
||||
|
|
|
@ -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<string, unknown>
|
||||
}): 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: {
|
||||
|
|
|
@ -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<Job>
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.convertMicrosecondsToDate = jest.fn().mockReturnValue(new Date())
|
||||
|
||||
jobRepository = {} as jest.Mocked<JobRepositoryInterface>
|
||||
jobRepository.findOneByUuid = jest.fn().mockReturnValue(job)
|
||||
|
||||
|
@ -40,7 +45,7 @@ describe('JobDoneInterpreter', () => {
|
|||
predicateRepository.findByJobUuid = jest.fn().mockReturnValue([])
|
||||
|
||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||
domainEventFactory.createEmailMessageRequestedEvent = jest
|
||||
domainEventFactory.createEmailRequestedEvent = jest
|
||||
.fn()
|
||||
.mockReturnValue({} as jest.Mocked<EmailMessageRequestedEvent>)
|
||||
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()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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:*"
|
||||
|
|
Loading…
Reference in a new issue