mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #5154 from overleaf/hb-trial-onboarding-ab
Trial onboarding email GitOrigin-RevId: a9e3ba5a5e333c625b4f983012f81f6fde21b8dc
This commit is contained in:
parent
f64cd511fa
commit
035b803989
6 changed files with 253 additions and 2 deletions
|
@ -1,9 +1,11 @@
|
||||||
const AnalyticsManager = require('../Analytics/AnalyticsManager')
|
const AnalyticsManager = require('../Analytics/AnalyticsManager')
|
||||||
|
const SplitTestV2Handler = require('../SplitTests/SplitTestV2Handler')
|
||||||
|
const SubscriptionEmailHandler = require('./SubscriptionEmailHandler')
|
||||||
|
|
||||||
function sendRecurlyAnalyticsEvent(event, eventData) {
|
function sendRecurlyAnalyticsEvent(event, eventData) {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case 'new_subscription_notification':
|
case 'new_subscription_notification':
|
||||||
_sendSubscriptionStartedEvent(eventData)
|
sendSubscriptionStartedEvent(eventData)
|
||||||
break
|
break
|
||||||
case 'updated_subscription_notification':
|
case 'updated_subscription_notification':
|
||||||
_sendSubscriptionUpdatedEvent(eventData)
|
_sendSubscriptionUpdatedEvent(eventData)
|
||||||
|
@ -39,7 +41,7 @@ function sendRecurlyAnalyticsEvent(event, eventData) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _sendSubscriptionStartedEvent(eventData) {
|
async function sendSubscriptionStartedEvent(eventData) {
|
||||||
const userId = _getUserId(eventData)
|
const userId = _getUserId(eventData)
|
||||||
const { planCode, quantity, state, isTrial } = _getSubscriptionData(eventData)
|
const { planCode, quantity, state, isTrial } = _getSubscriptionData(eventData)
|
||||||
AnalyticsManager.recordEventForUser(userId, 'subscription-started', {
|
AnalyticsManager.recordEventForUser(userId, 'subscription-started', {
|
||||||
|
@ -58,6 +60,18 @@ async function _sendSubscriptionStartedEvent(eventData) {
|
||||||
'subscription-is-trial',
|
'subscription-is-trial',
|
||||||
isTrial
|
isTrial
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// send the trial onboarding email
|
||||||
|
if (isTrial) {
|
||||||
|
const assignment = await SplitTestV2Handler.promises.getAssignment(
|
||||||
|
userId,
|
||||||
|
'trial-onboarding-email'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (assignment.variant === 'send-email') {
|
||||||
|
SubscriptionEmailHandler.sendTrialOnboardingEmail(userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _sendSubscriptionUpdatedEvent(eventData) {
|
async function _sendSubscriptionUpdatedEvent(eventData) {
|
||||||
|
@ -197,4 +211,5 @@ function _getSubscriptionData(eventData) {
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
sendRecurlyAnalyticsEvent,
|
sendRecurlyAnalyticsEvent,
|
||||||
|
sendSubscriptionStartedEvent,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
const EmailBuilder = require('../Email/EmailBuilder')
|
||||||
|
const EmailMessageHelper = require('../Email/EmailMessageHelper')
|
||||||
|
const settings = require('@overleaf/settings')
|
||||||
|
|
||||||
|
EmailBuilder.templates.trialOnboarding = EmailBuilder.NoCTAEmailTemplate({
|
||||||
|
subject() {
|
||||||
|
return `Welcome to your ${settings.appName} Premium Features Trial`
|
||||||
|
},
|
||||||
|
title() {
|
||||||
|
return `Welcome to your Overleaf Premium Features Trial`
|
||||||
|
},
|
||||||
|
greeting() {
|
||||||
|
return 'Hello,'
|
||||||
|
},
|
||||||
|
message(opts, isPlainText) {
|
||||||
|
const invitingNamedCollaborators = EmailMessageHelper.displayLink(
|
||||||
|
'Read More',
|
||||||
|
`${settings.siteUrl}/learn/how-to/Sharing_a_project?utm_source=Overleaf&utm_medium=email&utm_campaign=TrialEmail&utm_content=invitelink#Inviting_named_collaborators`,
|
||||||
|
isPlainText
|
||||||
|
)
|
||||||
|
const increasedCompileTimeout = EmailMessageHelper.displayLink(
|
||||||
|
'Read More',
|
||||||
|
`${settings.siteUrl}/learn/how-to/What_is_the_maximum_compilation_time,_file_number_and_project_size_allowed_on_free_vs_paid_plans%3F?utm_source=Overleaf&utm_medium=email&utm_campaign=TrialEmail&utm_content=compilelink`,
|
||||||
|
isPlainText
|
||||||
|
)
|
||||||
|
const realTimeTrackChanges = EmailMessageHelper.displayLink(
|
||||||
|
'Read More',
|
||||||
|
`${settings.siteUrl}/learn/how-to/Track_Changes_in_Overleaf?utm_source=Overleaf&utm_medium=email&utm_campaign=TrialEmail&utm_content=trackchangeslink`,
|
||||||
|
isPlainText
|
||||||
|
)
|
||||||
|
const history = EmailMessageHelper.displayLink(
|
||||||
|
'Read More',
|
||||||
|
`${settings.siteUrl}/learn/latex/Using_the_History_feature?utm_source=Overleaf&utm_medium=email&utm_campaign=TrialEmail&utm_content=historylink`,
|
||||||
|
isPlainText
|
||||||
|
)
|
||||||
|
const versioning = EmailMessageHelper.displayLink(
|
||||||
|
'Read More',
|
||||||
|
`${settings.siteUrl}/learn/how-to/Can_I_save_versions_of_my_work%3F?utm_source=Overleaf&utm_medium=email&utm_campaign=TrialEmail&utm_content=versioninglink`,
|
||||||
|
isPlainText
|
||||||
|
)
|
||||||
|
const advancedReferenceSearch = EmailMessageHelper.displayLink(
|
||||||
|
'Read More',
|
||||||
|
`${settings.siteUrl}/learn/how-to/How_to_search_for_references_in_an_Overleaf_project?utm_source=Overleaf&utm_medium=email&utm_campaign=TrialEmail&utm_content=adrefsearchlink`,
|
||||||
|
isPlainText
|
||||||
|
)
|
||||||
|
const referenceManagerSync = EmailMessageHelper.displayLink(
|
||||||
|
'Read More',
|
||||||
|
`${settings.siteUrl}/learn/how-to/How_to_link_your_Overleaf_account_to_Mendeley_and_Zotero?utm_source=Overleaf&utm_medium=email&utm_campaign=TrialEmail&utm_content=refmansynclink`,
|
||||||
|
isPlainText
|
||||||
|
)
|
||||||
|
const dropboxSync = EmailMessageHelper.displayLink(
|
||||||
|
'Read More',
|
||||||
|
`${settings.siteUrl}/learn/how-to/Dropbox_Synchronization?utm_source=Overleaf&utm_medium=email&utm_campaign=TrialEmail&utm_content=dropboxlink`,
|
||||||
|
isPlainText
|
||||||
|
)
|
||||||
|
const gitSync = EmailMessageHelper.displayLink(
|
||||||
|
'Read More',
|
||||||
|
`${settings.siteUrl}/learn/how-to/Using_Git_and_GitHub?utm_source=Overleaf&utm_medium=email&utm_campaign=TrialEmail&utm_content=gitgithublink`,
|
||||||
|
isPlainText
|
||||||
|
)
|
||||||
|
const latexTutorials = EmailMessageHelper.displayLink(
|
||||||
|
'Read More',
|
||||||
|
`${settings.siteUrl}/learn/latex/Learn_LaTeX_in_30_minutes?utm_source=Overleaf&utm_medium=email&utm_campaign=TrialEmail&utm_content=latextutorialslink`,
|
||||||
|
isPlainText
|
||||||
|
)
|
||||||
|
const knowledgeBase = EmailMessageHelper.displayLink(
|
||||||
|
'Read More',
|
||||||
|
`${settings.siteUrl}/learn?utm_source=Overleaf&utm_medium=email&utm_campaign=TrialEmail&utm_content=learnlink`,
|
||||||
|
isPlainText
|
||||||
|
)
|
||||||
|
const technicalArticles = EmailMessageHelper.displayLink(
|
||||||
|
'Read More',
|
||||||
|
`${settings.siteUrl}/learn/latex/Articles?utm_source=Overleaf&utm_medium=email&utm_campaign=TrialEmail&utm_content=articleslink`,
|
||||||
|
isPlainText
|
||||||
|
)
|
||||||
|
const webinars = EmailMessageHelper.displayLink(
|
||||||
|
'Read More',
|
||||||
|
`${settings.siteUrl}/events/webinars?utm_source=Overleaf&utm_medium=email&utm_campaign=TrialEmail&utm_content=webinarslink`,
|
||||||
|
isPlainText
|
||||||
|
)
|
||||||
|
|
||||||
|
const cancel = EmailMessageHelper.displayLink(
|
||||||
|
'cancel at any time',
|
||||||
|
`${settings.siteUrl}/learn/how-to/Canceling_Subscription?utm_source=Overleaf&utm_medium=email&utm_campaign=TrialEmail&utm_content=cancellink`,
|
||||||
|
isPlainText
|
||||||
|
)
|
||||||
|
|
||||||
|
const feedback = EmailMessageHelper.displayLink(
|
||||||
|
'hear your feedback',
|
||||||
|
`https://docs.google.com/forms/d/e/1FAIpQLSfMbbh_z-9-dZ3YnrDCyNpNxFPGA492ZSallKOt8WWp2nx7kg/viewform?usp=sf_link/viewform`,
|
||||||
|
isPlainText
|
||||||
|
)
|
||||||
|
|
||||||
|
const unsubscribe = EmailMessageHelper.displayLink(
|
||||||
|
'here',
|
||||||
|
`${settings.siteUrl}/user/settings`,
|
||||||
|
isPlainText
|
||||||
|
)
|
||||||
|
|
||||||
|
return [
|
||||||
|
`Welcome to your Overleaf Premium Features Trial! We really appreciate your support of Overleaf and are excited for you to use our premium features and get the most out of your trial.`,
|
||||||
|
`<b>During your trial period, be sure to check out these premium features: </b>`,
|
||||||
|
|
||||||
|
`1. <b>Invite more collaborators</b>: You can now invite named collaborators to your project via the ‘share’ menu in your project (with read-only or edit access). Simply add their email address and an email invitation will be sent to them. You can remove these named collaborators at any time via the same ‘share’ menu.`,
|
||||||
|
`<ul><li> Inviting Named Collaborators: ${invitingNamedCollaborators}</li></ul>`,
|
||||||
|
|
||||||
|
`2. <b>Increased compile timeout</b>: You now have more time for compilation (to generate a PDF of your document) before receiving a timeout error message.`,
|
||||||
|
`<ul><li> Compile Timeout: ${increasedCompileTimeout}</li></ul>`,
|
||||||
|
|
||||||
|
`3. <b>Real-time track changes</b>: The track changes mode lets you see exactly what has been changed by your collaborators, and allows you to accept or reject each individual change. `,
|
||||||
|
`<ul><li> Track Changes: ${realTimeTrackChanges}</li></ul>`,
|
||||||
|
|
||||||
|
`4. <b>Full document history and versioning</b>: View the entire history of your project with the ability to revert to previous versions of your document from your project history (versus only 24 hours of history availability on a free Overleaf account). No more fear of losing work or making changes you can’t undo. `,
|
||||||
|
`<ul><li> History: ${history}</li>
|
||||||
|
<li>Versioning: ${versioning}</li></ul>`,
|
||||||
|
|
||||||
|
`5. <b>Advanced reference search</b>: You can search by citation key, and our premium feature allows the added ability to search by author, title, year, or journal.`,
|
||||||
|
`<ul><li>Advanced Reference Search: ${advancedReferenceSearch}</li></ul>`,
|
||||||
|
|
||||||
|
`6. <b>Reference manager sync </b>: You can link your Mendeley and Zotero accounts to your Overleaf account, allowing you to import your reference library and keep your Overleaf document in sync with the references stored in Mendeley / Zotero.`,
|
||||||
|
`<ul><li> Reference Manager Sync: ${referenceManagerSync}</li></ul>`,
|
||||||
|
|
||||||
|
`7. <b>Dropbox Sync</b>: You can link your Dropbox account to your Overleaf account, allowing 2-way integration with Dropbox `,
|
||||||
|
`<ul><li> Dropbox Sync: ${dropboxSync}</li></ul>`,
|
||||||
|
|
||||||
|
`8. <b>Git and GitHub integration</b>: You can configure your Overleaf project to sync directly with a repository on GitHub, or you can use raw git access. This allows you to work offline and sync your files whenever you come back online. You can also use our Overleaf Git Bridge integration, which lets you git clone, push and pull changes between the online Overleaf editor, and your local offline git repository.`,
|
||||||
|
`<ul><li> Git, GitHub and Git Bridge: ${gitSync}</li></ul>`,
|
||||||
|
|
||||||
|
`9. <b>Online tutorials and knowledge base</b>: We have an extensive online knowledge base providing a wide range of platform guidance, LaTeX tutorials, technical articles, and webinars.`,
|
||||||
|
`<ul><li>LaTeX tutorials: ${latexTutorials}</li>
|
||||||
|
<li>Knowledge base: ${knowledgeBase}</li>
|
||||||
|
<li>Technical articles: ${technicalArticles}</li>
|
||||||
|
<li>Webinars: ${webinars}</li></ul>`,
|
||||||
|
|
||||||
|
`Your trial will last for seven days from when you started it, and you can ${cancel} via your subscription page on your dashboard. If you’d like to continue your subscription after your trial, you’re all set!`,
|
||||||
|
|
||||||
|
`Please let us know if we can provide any additional support or answer any questions - and we’d love to ${feedback}!`,
|
||||||
|
`Thanks again for supporting Overleaf - Happy TeXing!`,
|
||||||
|
`The Overleaf Team <hr>`,
|
||||||
|
|
||||||
|
`You're receiving this email because you've recently signed up for an Overleaf premium trial. If you've previously subscribed to emails about product offers and company news and events, you can unsubscribe ${unsubscribe}.`,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
})
|
|
@ -0,0 +1,19 @@
|
||||||
|
const EmailHandler = require('../Email/EmailHandler')
|
||||||
|
const UserGetter = require('../User/UserGetter')
|
||||||
|
require('./SubscriptionEmailBuilder')
|
||||||
|
|
||||||
|
const SubscriptionEmailHandler = {
|
||||||
|
async sendTrialOnboardingEmail(userId) {
|
||||||
|
const user = await UserGetter.promises.getUser(userId, {
|
||||||
|
email: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
const emailOptions = {
|
||||||
|
to: user.email,
|
||||||
|
sendingUser_id: userId,
|
||||||
|
}
|
||||||
|
await EmailHandler.promises.sendEmail('trialOnboarding', emailOptions)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = SubscriptionEmailHandler
|
|
@ -26,6 +26,14 @@ describe('RecurlyEventHandler', function () {
|
||||||
|
|
||||||
this.RecurlyEventHandler = SandboxedModule.require(modulePath, {
|
this.RecurlyEventHandler = SandboxedModule.require(modulePath, {
|
||||||
requires: {
|
requires: {
|
||||||
|
'./SubscriptionEmailHandler': (this.SubscriptionEmailHandler = {
|
||||||
|
sendTrialOnboardingEmail: sinon.stub(),
|
||||||
|
}),
|
||||||
|
'../SplitTests/SplitTestV2Handler': (this.SplitTestV2Handler = {
|
||||||
|
promises: {
|
||||||
|
getAssignment: sinon.stub().resolves({ active: false }),
|
||||||
|
},
|
||||||
|
}),
|
||||||
'../Analytics/AnalyticsManager': (this.AnalyticsManager = {
|
'../Analytics/AnalyticsManager': (this.AnalyticsManager = {
|
||||||
recordEventForUser: sinon.stub(),
|
recordEventForUser: sinon.stub(),
|
||||||
setUserPropertyForUser: sinon.stub(),
|
setUserPropertyForUser: sinon.stub(),
|
||||||
|
@ -67,6 +75,30 @@ describe('RecurlyEventHandler', function () {
|
||||||
'subscription-is-trial',
|
'subscription-is-trial',
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
sinon.assert.calledWith(
|
||||||
|
this.SplitTestV2Handler.promises.getAssignment,
|
||||||
|
this.userId,
|
||||||
|
'trial-onboarding-email'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends free trial onboarding email if user in ab group', async function () {
|
||||||
|
this.SplitTestV2Handler.promises.getAssignment = sinon
|
||||||
|
.stub()
|
||||||
|
.resolves({ active: true, variant: 'send-email' })
|
||||||
|
this.userId = '123456789trial'
|
||||||
|
this.eventData.account.account_code = this.userId
|
||||||
|
|
||||||
|
// testing directly on the send subscription started event to ensure the split handler
|
||||||
|
// promise is resolved before checking calls
|
||||||
|
await this.RecurlyEventHandler.sendSubscriptionStartedEvent(this.eventData)
|
||||||
|
|
||||||
|
sinon.assert.calledWith(
|
||||||
|
this.SplitTestV2Handler.promises.getAssignment,
|
||||||
|
this.userId,
|
||||||
|
'trial-onboarding-email'
|
||||||
|
)
|
||||||
|
sinon.assert.called(this.SubscriptionEmailHandler.sendTrialOnboardingEmail)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('with new_subscription_notification - no free trial', function () {
|
it('with new_subscription_notification - no free trial', function () {
|
||||||
|
|
|
@ -150,6 +150,7 @@ describe('SubscriptionController', function () {
|
||||||
'./RecurlyWrapper': (this.RecurlyWrapper = {
|
'./RecurlyWrapper': (this.RecurlyWrapper = {
|
||||||
updateAccountEmailAddress: sinon.stub().yields(),
|
updateAccountEmailAddress: sinon.stub().yields(),
|
||||||
}),
|
}),
|
||||||
|
'./RecurlyEventHandler': { sendRecurlyAnalyticsEvent: sinon.stub() },
|
||||||
'./FeaturesUpdater': (this.FeaturesUpdater = {}),
|
'./FeaturesUpdater': (this.FeaturesUpdater = {}),
|
||||||
'./GroupPlansData': (this.GroupPlansData = {}),
|
'./GroupPlansData': (this.GroupPlansData = {}),
|
||||||
'./V1SubscriptionManager': (this.V1SubscriptionManager = {}),
|
'./V1SubscriptionManager': (this.V1SubscriptionManager = {}),
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
const SandboxedModule = require('sandboxed-module')
|
||||||
|
const sinon = require('sinon')
|
||||||
|
const { expect } = require('chai')
|
||||||
|
const modulePath =
|
||||||
|
'../../../../app/src/Features/Subscription/SubscriptionEmailHandler'
|
||||||
|
|
||||||
|
describe('SubscriptionEmailHandler', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
this.userId = '123456789abcde'
|
||||||
|
this.email = 'test@test.com'
|
||||||
|
|
||||||
|
this.SubscriptionEmailHandler = SandboxedModule.require(modulePath, {
|
||||||
|
requires: {
|
||||||
|
'../Email/EmailHandler': (this.EmailHandler = {
|
||||||
|
promises: {
|
||||||
|
sendEmail: sinon.stub().resolves({}),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'../User/UserGetter': (this.UserGetter = {
|
||||||
|
promises: {
|
||||||
|
getUser: sinon
|
||||||
|
.stub()
|
||||||
|
.resolves({ _id: this.userId, email: 'test@test.com' }),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends trail onboarding email', async function () {
|
||||||
|
await this.SubscriptionEmailHandler.sendTrialOnboardingEmail(this.userId)
|
||||||
|
expect(this.EmailHandler.promises.sendEmail.lastCall.args).to.deep.equal([
|
||||||
|
'trialOnboarding',
|
||||||
|
{
|
||||||
|
to: this.email,
|
||||||
|
sendingUser_id: this.userId,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in a new issue