const _ = require('lodash')
const settings = require('@overleaf/settings')
const moment = require('moment')
const EmailMessageHelper = require('./EmailMessageHelper')
const StringHelper = require('../Helpers/StringHelper')
const BaseWithHeaderEmailLayout = require('./Layouts/BaseWithHeaderEmailLayout')
const SpamSafe = require('./SpamSafe')
const ctaEmailBody = require('./Bodies/cta-email')
const NoCTAEmailBody = require('./Bodies/NoCTAEmailBody')
function _emailBodyPlainText(content, opts, ctaEmail) {
let emailBody = `${content.greeting(opts, true)}`
emailBody += `\r\n\r\n`
emailBody += `${content.message(opts, true).join('\r\n\r\n')}`
if (ctaEmail) {
emailBody += `\r\n\r\n`
emailBody += `${content.ctaText(opts, true)}: ${content.ctaURL(opts, true)}`
}
if (
content.secondaryMessage(opts, true) &&
content.secondaryMessage(opts, true).length > 0
) {
emailBody += `\r\n\r\n`
emailBody += `${content.secondaryMessage(opts, true).join('\r\n\r\n')}`
}
emailBody += `\r\n\r\n`
emailBody += `Regards,\r\nThe ${settings.appName} Team - ${settings.siteUrl}`
if (
settings.email &&
settings.email.template &&
settings.email.template.customFooter
) {
emailBody += `\r\n\r\n`
emailBody += settings.email.template.customFooter
}
return emailBody
}
function ctaTemplate(content) {
if (
!content.ctaURL ||
!content.ctaText ||
!content.message ||
!content.subject
) {
throw new Error('missing required CTA email content')
}
if (!content.title) {
content.title = () => {}
}
if (!content.greeting) {
content.greeting = () => 'Hi,'
}
if (!content.secondaryMessage) {
content.secondaryMessage = () => []
}
if (!content.gmailGoToAction) {
content.gmailGoToAction = () => {}
}
return {
subject(opts) {
return content.subject(opts)
},
layout: BaseWithHeaderEmailLayout,
plainTextTemplate(opts) {
return _emailBodyPlainText(content, opts, true)
},
compiledTemplate(opts) {
return ctaEmailBody({
title: content.title(opts),
greeting: content.greeting(opts),
message: content.message(opts),
secondaryMessage: content.secondaryMessage(opts),
ctaText: content.ctaText(opts),
ctaURL: content.ctaURL(opts),
gmailGoToAction: content.gmailGoToAction(opts),
StringHelper,
})
},
}
}
function NoCTAEmailTemplate(content) {
if (content.greeting == null) {
content.greeting = () => 'Hi,'
}
if (!content.message) {
throw new Error('missing message')
}
return {
subject(opts) {
return content.subject(opts)
},
layout: BaseWithHeaderEmailLayout,
plainTextTemplate(opts) {
return `\
${content.greeting(opts)}
${content.message(opts, true).join('\r\n\r\n')}
Regards,
The ${settings.appName} Team - ${settings.siteUrl}\
`
},
compiledTemplate(opts) {
return NoCTAEmailBody({
title:
typeof content.title === 'function' ? content.title(opts) : undefined,
greeting: content.greeting(opts),
highlightedText:
typeof content.highlightedText === 'function'
? content.highlightedText(opts)
: undefined,
message: content.message(opts),
StringHelper,
})
},
}
}
function buildEmail(templateName, opts) {
const template = templates[templateName]
opts.siteUrl = settings.siteUrl
opts.body = template.compiledTemplate(opts)
return {
subject: template.subject(opts),
html: template.layout(opts),
text: template.plainTextTemplate && template.plainTextTemplate(opts),
}
}
const templates = {}
templates.registered = ctaTemplate({
subject() {
return `Activate your ${settings.appName} Account`
},
message(opts) {
return [
`Congratulations, you've just had an account created for you on ${
settings.appName
} with the email address '${_.escape(opts.to)}'.`,
'Click here to set your password and log in:',
]
},
secondaryMessage() {
return [
`If you have any questions or problems, please contact ${settings.adminEmail}`,
]
},
ctaText() {
return 'Set password'
},
ctaURL(opts) {
return opts.setNewPasswordUrl
},
})
templates.canceledSubscription = ctaTemplate({
subject() {
return `${settings.appName} thoughts`
},
message() {
return [
`We are sorry to see you cancelled your ${settings.appName} premium subscription. Would you mind giving us some feedback on what the site is lacking at the moment via this quick survey?`,
]
},
secondaryMessage() {
return ['Thank you in advance!']
},
ctaText() {
return 'Leave Feedback'
},
ctaURL(opts) {
return 'https://docs.google.com/forms/d/e/1FAIpQLSfa7z_s-cucRRXm70N4jEcSbFsZeb0yuKThHGQL8ySEaQzF0Q/viewform?usp=sf_link'
},
})
templates.reactivatedSubscription = ctaTemplate({
subject() {
return `Subscription Reactivated - ${settings.appName}`
},
message(opts) {
return ['Your subscription was reactivated successfully.']
},
ctaText() {
return 'View Subscription Dashboard'
},
ctaURL(opts) {
return `${settings.siteUrl}/user/subscription`
},
})
templates.passwordResetRequested = ctaTemplate({
subject() {
return `Password Reset - ${settings.appName}`
},
title() {
return 'Password Reset'
},
message() {
return [`We got a request to reset your ${settings.appName} password.`]
},
secondaryMessage() {
return [
"If you ignore this message, your password won't be changed.",
"If you didn't request a password reset, let us know.",
]
},
ctaText() {
return 'Reset password'
},
ctaURL(opts) {
return opts.setNewPasswordUrl
},
})
templates.confirmEmail = ctaTemplate({
subject() {
return `Confirm Email - ${settings.appName}`
},
title() {
return 'Confirm Email'
},
message(opts) {
return [
`Please confirm that you have added a new email, ${opts.to}, to your ${settings.appName} account.`,
]
},
secondaryMessage() {
return [
`If you did not request this, please let us know at ${settings.adminEmail}.`,
`If you have any questions or trouble confirming your email address, please get in touch with our support team at ${settings.adminEmail}.`,
]
},
ctaText() {
return 'Confirm Email'
},
ctaURL(opts) {
return opts.confirmEmailUrl
},
})
templates.confirmCode = NoCTAEmailTemplate({
greeting(opts) {
return ''
},
subject(opts) {
return `Confirm your email address on Overleaf (${opts.confirmCode})`
},
title(opts) {
return 'Confirm your email address'
},
message(opts, isPlainText) {
const msg = opts.isSecondary
? ['Use this 6-digit code to confirm your email address.']
: [
`Welcome to Overleaf! We're so glad you joined us.`,
'Use this 6-digit confirmation code to finish your setup.',
]
if (isPlainText && opts.confirmCode) {
msg.push(opts.confirmCode)
}
return msg
},
highlightedText(opts) {
return opts.confirmCode
},
})
templates.projectInvite = ctaTemplate({
subject(opts) {
const safeName = SpamSafe.isSafeProjectName(opts.project.name)
const safeEmail = SpamSafe.isSafeEmail(opts.owner.email)
if (safeName && safeEmail) {
return `"${_.escape(opts.project.name)}" — shared by ${_.escape(
opts.owner.email
)}`
}
if (safeName) {
return `${settings.appName} project shared with you — "${_.escape(
opts.project.name
)}"`
}
if (safeEmail) {
return `${_.escape(opts.owner.email)} shared an ${
settings.appName
} project with you`
}
return `An ${settings.appName} project has been shared with you`
},
title(opts) {
return 'Project Invite'
},
greeting(opts) {
return ''
},
message(opts, isPlainText) {
// build message depending on spam-safe variables
const message = [`You have been invited to an ${settings.appName} project.`]
if (SpamSafe.isSafeProjectName(opts.project.name)) {
message.push('
Project:')
message.push(`${_.escape(opts.project.name)}`)
}
if (SpamSafe.isSafeEmail(opts.owner.email)) {
message.push(`
Shared by:`)
message.push(`${_.escape(opts.owner.email)}`)
}
if (message.length === 1) {
message.push('
Please view the project to find out more.')
}
return message.map(m => {
return EmailMessageHelper.cleanHTML(m, isPlainText)
})
},
ctaText() {
return 'View project'
},
ctaURL(opts) {
return opts.inviteUrl
},
gmailGoToAction(opts) {
return {
target: opts.inviteUrl,
name: 'View project',
description: `Join ${_.escape(
SpamSafe.safeProjectName(opts.project.name, 'project')
)} at ${settings.appName}`,
}
},
})
templates.reconfirmEmail = ctaTemplate({
subject() {
return `Reconfirm Email - ${settings.appName}`
},
title() {
return 'Reconfirm Email'
},
message(opts) {
return [
`Please reconfirm your email address, ${opts.to}, on your ${settings.appName} account.`,
]
},
secondaryMessage() {
return [
'If you did not request this, you can simply ignore this message.',
`If you have any questions or trouble confirming your email address, please get in touch with our support team at ${settings.adminEmail}.`,
]
},
ctaText() {
return 'Reconfirm Email'
},
ctaURL(opts) {
return opts.confirmEmailUrl
},
})
templates.verifyEmailToJoinTeam = ctaTemplate({
subject(opts) {
return `${opts.reminder ? 'Reminder: ' : ''}${_.escape(
_formatUserNameAndEmail(opts.inviter, 'A collaborator')
)} has invited you to join a group subscription on ${settings.appName}`
},
title(opts) {
return `${opts.reminder ? 'Reminder: ' : ''}${_.escape(
_formatUserNameAndEmail(opts.inviter, 'A collaborator')
)} has invited you to join a group subscription on ${settings.appName}`
},
message(opts) {
return [
`Please click the button below to join the group subscription and enjoy the benefits of an upgraded ${settings.appName} account.`,
]
},
ctaText(opts) {
return 'Join now'
},
ctaURL(opts) {
return opts.acceptInviteUrl
},
})
templates.verifyEmailToJoinManagedUsers = ctaTemplate({
subject(opts) {
return `${
opts.reminder ? 'Reminder: ' : ''
}You’ve been invited by ${_.escape(
_formatUserNameAndEmail(opts.inviter, 'a collaborator')
)} to join an ${settings.appName} group subscription.`
},
title(opts) {
return `${
opts.reminder ? 'Reminder: ' : ''
}You’ve been invited by ${_.escape(
_formatUserNameAndEmail(opts.inviter, 'a collaborator')
)} to join an ${settings.appName} group subscription.`
},
message(opts) {
return [
`By joining this group, you'll have access to ${settings.appName} premium features such as additional collaborators, greater maximum compile time, and real-time track changes.`,
]
},
secondaryMessage(opts, isPlainText) {
const changeProjectOwnerLink = EmailMessageHelper.displayLink(
'change project owner',
`${settings.siteUrl}/learn/how-to/How_to_Transfer_Project_Ownership`,
isPlainText
)
return [
`User accounts in this group are managed by ${_.escape(
_formatUserNameAndEmail(opts.admin, 'an admin')
)}`,
`If you accept, you’ll transfer the management of your ${settings.appName} account to the owner of the group subscription, who will then have admin rights over your account and control over your stuff.`,
`If you have personal projects in your ${settings.appName} account that you want to keep separate, that’s not a problem. You can set up another account under a personal email address and change the ownership of your personal projects to the new account. Find out how to ${changeProjectOwnerLink}.`,
]
},
ctaURL(opts) {
return opts.acceptInviteUrl
},
ctaText(opts) {
return 'Accept invitation'
},
greeting() {
return ''
},
})
templates.inviteNewUserToJoinManagedUsers = ctaTemplate({
subject(opts) {
return `${
opts.reminder ? 'Reminder: ' : ''
}You’ve been invited by ${_.escape(
_formatUserNameAndEmail(opts.inviter, 'a collaborator')
)} to join an ${settings.appName} group subscription.`
},
title(opts) {
return `${
opts.reminder ? 'Reminder: ' : ''
}You’ve been invited by ${_.escape(
_formatUserNameAndEmail(opts.inviter, 'a collaborator')
)} to join an ${settings.appName} group subscription.`
},
message(opts) {
return ['']
},
secondaryMessage(opts) {
return [
`User accounts in this group are managed by ${_.escape(
_formatUserNameAndEmail(opts.admin, 'an admin')
)}.`,
`If you accept, the owner of the group subscription will have admin rights over your account and control over your stuff.`,
`What is ${settings.appName}?`,
`${settings.appName} is the collaborative online LaTeX editor loved by researchers and technical writers. With thousands of ready-to-use templates and an array of LaTeX learning resources you’ll be up and running in no time.`,
]
},
ctaURL(opts) {
return opts.acceptInviteUrl
},
ctaText(opts) {
return 'Accept invitation'
},
greeting() {
return ''
},
})
templates.groupSSOLinkingInvite = ctaTemplate({
subject(opts) {
const subjectPrefix = opts.reminder ? 'Reminder: ' : 'Action required: '
return `${subjectPrefix}Authenticate your Overleaf account`
},
title(opts) {
const titlePrefix = opts.reminder ? 'Reminder: ' : ''
return `${titlePrefix}Single sign-on enabled`
},
message(opts) {
return [
`Hi,