Merge pull request #2948 from overleaf/hb-onboarding-email
User onboarding email GitOrigin-RevId: 367fd8b6292024bfcad2139728c16cf00f2fb1cb
This commit is contained in:
@@ -563,6 +563,46 @@ manage ${projectName} sharing settings.
|
||||
}
|
||||
})
|
||||
|
||||
templates.userOnboardingEmail = NoCTAEmailTemplate({
|
||||
subject(opts) {
|
||||
return `Getting more out of ${settings.appName}`
|
||||
},
|
||||
greeting(opts) {
|
||||
return ''
|
||||
},
|
||||
title(opts) {
|
||||
return `Getting more out of ${settings.appName}`
|
||||
},
|
||||
message(opts) {
|
||||
return `\
|
||||
Thanks for signing up for ${
|
||||
settings.appName
|
||||
} recently. We hope you've been finding it useful!
|
||||
Here are some key features to help you get the most out of the service:
|
||||
|
||||
<a href="https://www.overleaf.com/learn/latex/Learn_LaTeX_in_30_minutes?utm_source=overleaf&utm_medium=email&utm_campaign=onboarding">Learn LaTeX in 30 minutes</a>: In this tutorial we provide a quick and easy first introduction to LaTeX with no prior knowledge required. By the time you are finished, you will have written your first LaTeX document!
|
||||
|
||||
<a href="https://www.overleaf.com/latex/templates?utm_source=overleaf&utm_medium=email&utm_campaign=onboarding">Find a beautiful template</a>: If you're looking for a template or example to get started, we've a large selection available in our template gallery, including CVs, project reports, journal articles and more.
|
||||
|
||||
<a href="https://www.overleaf.com/learn/how-to/Sharing_a_project?utm_source=overleaf&utm_medium=email&utm_campaign=onboarding">Work with your collaborators</a>: One of the key features of Overleaf is the ability to share projects and collaborate on them with other users. Find out how to share your projecs with your colleagues in this quick how-to guide.
|
||||
|
||||
If you have any questions, please let us know, and thanks again for using Overleaf.
|
||||
|
||||
John
|
||||
|
||||
Dr John Hammersley <br />
|
||||
Co-founder & CEO <br />
|
||||
<a href="http://www.overleaf.com">www.overleaf.com</a>
|
||||
<hr>
|
||||
`
|
||||
},
|
||||
secondaryMessage() {
|
||||
return `Don't want onboarding emails like this from us? Don't worry, this is the only one.
|
||||
If you've previously subscribed to emails about product offers and company news and events,
|
||||
you can unsubscribe <a href="${settings.siteUrl}/user/settings"> here </a>.`
|
||||
}
|
||||
})
|
||||
|
||||
function _formatUserNameAndEmail(user, placeholder) {
|
||||
if (user.first_name && user.last_name) {
|
||||
const fullName = `${user.first_name} ${user.last_name}`
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
const { db, ObjectId } = require('../../infrastructure/mongojs')
|
||||
const UserUpdater = require('./UserUpdater')
|
||||
const EmailHandler = require('../Email/EmailHandler')
|
||||
const logger = require('logger-sharelatex')
|
||||
const async = require('async')
|
||||
const _ = require('underscore')
|
||||
|
||||
module.exports = {
|
||||
sendRecentSignupOnboardingEmails(req, res, next) {
|
||||
// find all the users with no onboardingEmailSentAt and
|
||||
// have signed up in the last 7 days
|
||||
db.users.find(
|
||||
{
|
||||
onboardingEmailSentAt: null,
|
||||
_id: {
|
||||
$gt: ObjectId.createFromTime(Date.now() / 1000 - 7 * 24 * 60 * 60)
|
||||
}
|
||||
},
|
||||
{ email: 1 },
|
||||
function(error, users) {
|
||||
if (error) {
|
||||
return next(error)
|
||||
}
|
||||
const ids = _.map(users, function(user) {
|
||||
return user._id
|
||||
})
|
||||
logger.log('SENDING USER ONBOARDING EMAILS TO: ', ids)
|
||||
async.mapLimit(users, 10, sendOne, function(error) {
|
||||
if (error) {
|
||||
return next(error)
|
||||
}
|
||||
logger.log('DONE SENDING ONBOARDING EMAILS')
|
||||
res.send(ids)
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function sendOne(user, callback) {
|
||||
var opts = {
|
||||
to: user.email
|
||||
}
|
||||
EmailHandler.sendEmail('userOnboardingEmail', opts, function(error) {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
}
|
||||
UserUpdater.updateUser(
|
||||
user._id,
|
||||
{ $set: { onboardingEmailSentAt: new Date() } },
|
||||
function() {
|
||||
callback()
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -149,7 +149,8 @@ const UserSchema = new Schema({
|
||||
createdAt: { type: Date },
|
||||
enrolledAt: { type: Date },
|
||||
secret: { type: String }
|
||||
}
|
||||
},
|
||||
onboardingEmailSentAt: { type: Date }
|
||||
})
|
||||
|
||||
exports.User = mongoose.model('User', UserSchema)
|
||||
|
||||
@@ -18,6 +18,7 @@ const UserInfoController = require('./Features/User/UserInfoController')
|
||||
const UserController = require('./Features/User/UserController')
|
||||
const UserEmailsController = require('./Features/User/UserEmailsController')
|
||||
const UserPagesController = require('./Features/User/UserPagesController')
|
||||
const UserOnboardingController = require('./Features/User/UserOnboardingController')
|
||||
const DocumentController = require('./Features/Documents/DocumentController')
|
||||
const CompileManager = require('./Features/Compile/CompileManager')
|
||||
const CompileController = require('./Features/Compile/CompileController')
|
||||
@@ -240,6 +241,12 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) {
|
||||
UserInfoController.getPersonalInfo
|
||||
)
|
||||
|
||||
privateApiRouter.post(
|
||||
'/user/onboarding_emails',
|
||||
AuthenticationController.httpAuth,
|
||||
UserOnboardingController.sendRecentSignupOnboardingEmails
|
||||
)
|
||||
|
||||
webRouter.get(
|
||||
'/user/reconfirm',
|
||||
UserPagesController.renderReconfirmAccountPage
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
const { expect } = require('chai')
|
||||
const async = require('async')
|
||||
const User = require('./helpers/User')
|
||||
const request = require('./helpers/request')
|
||||
const { db, ObjectId } = require('../../../app/src/infrastructure/mongojs')
|
||||
const _ = require('underscore')
|
||||
|
||||
describe('UserOnboardingTests', function() {
|
||||
beforeEach(function(done) {
|
||||
// 2 new users
|
||||
this.user1 = new User()
|
||||
this.user2 = new User()
|
||||
// 1 older
|
||||
this.user3 = new User()
|
||||
this.user3._id = ObjectId('5d15fca20000000000000000')
|
||||
async.series(
|
||||
[
|
||||
cb => db.users.insert(this.user3, cb),
|
||||
this.user1.ensureUserExists.bind(this.user1),
|
||||
this.user2.ensureUserExists.bind(this.user2)
|
||||
],
|
||||
done
|
||||
)
|
||||
})
|
||||
|
||||
it('should send emails to the new users only', function(done) {
|
||||
request(
|
||||
{
|
||||
method: 'POST',
|
||||
url: '/user/onboarding_emails',
|
||||
auth: {
|
||||
username: 'sharelatex',
|
||||
password: 'password',
|
||||
sendImmediately: true
|
||||
}
|
||||
},
|
||||
(error, response, body) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
}
|
||||
// should have sent two emails to new users
|
||||
expect(response.statusCode).to.equal(200)
|
||||
expect(response.body).to.include(this.user1._id)
|
||||
expect(response.body).to.include(this.user2._id)
|
||||
expect(response.body).to.not.include(this.user3._id)
|
||||
|
||||
// user 3 should still not have had an email sent
|
||||
const user3 = this.user3
|
||||
db.users.find(
|
||||
{
|
||||
onboardingEmailSentAt: null
|
||||
},
|
||||
(error, users) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
}
|
||||
const ids = _.map(users, user => user._id.toString())
|
||||
expect(ids.length).to.equal(1)
|
||||
expect(ids).to.include(user3._id.toString())
|
||||
done()
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,76 @@
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const modulePath =
|
||||
'../../../../app/src/Features/User/UserOnboardingController.js'
|
||||
const { ObjectId } = require('mongojs')
|
||||
const sinon = require('sinon')
|
||||
|
||||
describe('UserOnboardingController', function() {
|
||||
beforeEach(function() {
|
||||
this.date = new Date().getTime()
|
||||
sinon.useFakeTimers(this.date)
|
||||
|
||||
this.users = [
|
||||
{
|
||||
_id: ObjectId('00000001f037be01a0e3a541')
|
||||
},
|
||||
{
|
||||
_id: ObjectId('00000001f037be01a0e3a542')
|
||||
},
|
||||
{
|
||||
_id: ObjectId('00000001f037be01a0e3a543')
|
||||
}
|
||||
]
|
||||
|
||||
this.mongojs = {
|
||||
db: { users: { find: sinon.stub().callsArgWith(2, null, this.users) } },
|
||||
ObjectId: ObjectId
|
||||
}
|
||||
|
||||
this.logger = {
|
||||
log() {}
|
||||
}
|
||||
|
||||
this.UserUpdater = {
|
||||
updateUser: sinon.stub().callsArgWith(2, null)
|
||||
}
|
||||
|
||||
this.EmailHandler = {
|
||||
sendEmail: sinon.stub().callsArgWith(2)
|
||||
}
|
||||
|
||||
this.UserOnboardingController = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'../../infrastructure/mongojs': this.mongojs,
|
||||
'./UserUpdater': this.UserUpdater,
|
||||
'../Email/EmailHandler': this.EmailHandler,
|
||||
'logger-sharelatex': this.logger
|
||||
}
|
||||
})
|
||||
this.req = {}
|
||||
this.res = {}
|
||||
})
|
||||
|
||||
it('sends onboarding emails', function(done) {
|
||||
this.res.send = ids => {
|
||||
ids.length.should.equal(3)
|
||||
this.mongojs.db.users.find.calledOnce.should.equal(true)
|
||||
this.EmailHandler.sendEmail.calledThrice.should.equal(true)
|
||||
this.UserUpdater.updateUser.calledThrice.should.equal(true)
|
||||
for (var i = 0; i < 3; i++) {
|
||||
this.UserUpdater.updateUser
|
||||
.calledWith(
|
||||
this.users[0]._id,
|
||||
sinon.match({
|
||||
$set: { onboardingEmailSentAt: new Date(this.date) }
|
||||
})
|
||||
)
|
||||
.should.equal(true)
|
||||
}
|
||||
done()
|
||||
}
|
||||
this.UserOnboardingController.sendRecentSignupOnboardingEmails(
|
||||
this.req,
|
||||
this.res
|
||||
)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user