diff --git a/website/api/controllers/admin/view-email-template-preview.js b/website/api/controllers/admin/view-email-template-preview.js index 4fa8a9694e..5692353463 100644 --- a/website/api/controllers/admin/view-email-template-preview.js +++ b/website/api/controllers/admin/view-email-template-preview.js @@ -114,6 +114,24 @@ module.exports = { layout = 'layout-email'; fakeData = {}; break; + case 'email-nurture-stage-three': + layout = 'layout-nurture-email'; + fakeData = { + firstName: 'Sage' + }; + break; + case 'email-nurture-stage-four': + layout = 'layout-nurture-email'; + fakeData = { + firstName: 'Sage' + }; + break; + case 'email-nurture-stage-five': + layout = 'layout-nurture-email'; + fakeData = { + firstName: 'Sage' + }; + break; case 'email-deal-registration': layout = 'layout-email'; fakeData = { diff --git a/website/api/controllers/save-questionnaire-progress.js b/website/api/controllers/save-questionnaire-progress.js index efbf8e1a3e..4f7b3a2922 100644 --- a/website/api/controllers/save-questionnaire-progress.js +++ b/website/api/controllers/save-questionnaire-progress.js @@ -116,6 +116,7 @@ module.exports = { // - nothing » No change (Stage ??) let psychologicalStage = userRecord.psychologicalStage; + let psychologicalStageLastChangedAt = userRecord.psychologicalStageLastChangedAt; // Get the value of the submitted formData, we do this so we only need to check one variable, instead of (formData.attribute === 'foo'); let valueFromFormData = _.values(formData)[0]; if(currentStep === 'start') { @@ -211,6 +212,8 @@ module.exports = { // Only update CRM records if the user's psychological stage changes. if(psychologicalStage !== userRecord.psychologicalStage) { + // Update the psychologicalStageLastChangedAt timestamp if the user's psychological stage + psychologicalStageLastChangedAt = Date.now(); sails.helpers.salesforce.updateOrCreateContactAndAccount.with({ emailAddress: this.req.me.emailAddress, firstName: this.req.me.firstName, @@ -236,7 +239,8 @@ module.exports = { .set({ getStartedQuestionnaireAnswers: questionnaireProgress, lastSubmittedGetStartedQuestionnaireStep: currentStep, - psychologicalStage + psychologicalStage, + psychologicalStageLastChangedAt, }); // Return the JSON dictionary of form data submitted by this user. return getStartedProgress; diff --git a/website/api/models/User.js b/website/api/models/User.js index 80f39ec3e5..72b603338d 100644 --- a/website/api/models/User.js +++ b/website/api/models/User.js @@ -240,6 +240,28 @@ without necessarily having a billing card.` defaultsTo: '2 - Aware' }, + psychologicalStageLastChangedAt: { + type: 'number', + description: 'A JS timestamp of when this user\'s psychological stage changed.', + extendedDescription: 'Used when deciding whether or not to send a nuture email to this user', + }, + + stageThreeNurtureEmailSentAt: { + type: 'number', + description: 'A JS timestamp of when the stage 3 nurture email was sent to the user.' + }, + + stageFourNurtureEmailSentAt: { + type: 'number', + description: 'A JS timestamp of when the stage 4 nurture email was sent to the user.' + }, + + stageFiveNurtureEmailSentAt: { + type: 'number', + description: 'A JS timestamp of when the stage 5 nurture email was sent to the user.' + }, + + // ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗ // ║╣ ║║║╠╩╗║╣ ║║╚═╗ // ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝ diff --git a/website/assets/images/email-header-959x452@2x.png b/website/assets/images/email-header-959x452@2x.png new file mode 100644 index 0000000000..56cbe19b56 Binary files /dev/null and b/website/assets/images/email-header-959x452@2x.png differ diff --git a/website/config/custom.js b/website/config/custom.js index f2b4f7c164..ea67c02aef 100644 --- a/website/config/custom.js +++ b/website/config/custom.js @@ -390,6 +390,11 @@ module.exports.custom = { // customerWorkspaceOneOauthSecret: '…', // customerMigrationWebhookSecret: '…', + // For nurture emails: + // contactEmailForNutureEmails: '…', + // activityCaptureEmailForNutureEmails: '…', + // contactNameForNurtureEmails: '…', + // Deal registration form // dealRegistrationContactEmailAddress: '…', diff --git a/website/scripts/deliver-nurture-emails.js b/website/scripts/deliver-nurture-emails.js new file mode 100644 index 0000000000..5a60804b5e --- /dev/null +++ b/website/scripts/deliver-nurture-emails.js @@ -0,0 +1,128 @@ +module.exports = { + + + friendlyName: 'Deliver nurture emails', + + + description: 'Sends nurture emails to users who have been at psychological stage 3 & 4 for more than a day, and users who have been stage five for six weeks.', + + + fn: async function () { + + sails.log('Running custom shell script... (`sails run deliver-nurture-emails`)'); + + let nowAt = Date.now(); + let nurtureCampaignStartedAt = new Date('07-22-2024').getTime(); + let oneHourAgoAt = nowAt - (1000 * 60 * 60); + let oneDayAgoAt = nowAt - (1000 * 60 * 60); + let sixWeeksAgoAt = nowAt - (1000 * 60 * 60 * 24 * 7 * 6); + // Find user records that are over an hour old that were created after July 22nd. + let usersWithMdmBuyingSituation = await User.find({ + primaryBuyingSituation: 'mdm', + createdAt: { + '>=': nurtureCampaignStartedAt, + '<=': oneHourAgoAt, + }, + }); + + // Only send emails to stage 3 users who have not received a nurture email for this stage, and that have been stage 3 for at least one day. + let stageThreeMdmFocusedUsersWhoHaveNotReceivedAnEmail = _.filter(usersWithMdmBuyingSituation, (user)=>{ + return user.stageThreeNurtureEmailSentAt === 0 + && user.psychologicalStage === '3 - Intrigued'; + }); + + // Only send emails to stage 4 users who have not received a a nurture email for this stage, and that have been stage 4 for at least one day. + let stageFourMdmFocusedUsersWhoHaveNotReceivedAnEmail = _.filter(usersWithMdmBuyingSituation, (user)=>{ + return user.stageFourNurtureEmailSentAt === 0 + && user.psychologicalStage === '4 - Has use case'; + }); + + // Only send emails to stage 5 users who have not received a nurture email for this stage, and that have been stage 5 for at least six weeks. + let stageFiveMdmFocusedUsersWhoHaveNotReceivedAnEmail = _.filter(usersWithMdmBuyingSituation, (user)=>{ + return user.stageFiveNurtureEmailSentAt === 0 + && user.psychologicalStage === '5 - Personally confident'; + }); + + for(let user of stageThreeMdmFocusedUsersWhoHaveNotReceivedAnEmail) { + if(user.psychologicalStageLastChangedAt > oneDayAgoAt) { + continue; + } else { + await sails.helpers.sendTemplateEmail.with({ + template: 'email-nurture-stage-three', + layout: 'layout-nurture-email', + templateData: { + firstName: user.firstName + }, + to: user.emailAddress, + toName: `${user.firstName} ${user.lastName}`, + subject: 'Was it any good?', + bcc: [sails.config.custom.activityCaptureEmailForNutureEmails], + from: sails.config.custom.contactEmailForNutureEmails, + fromName: sails.config.custom.contactNameForNurtureEmails, + ensureAck: true, + }); + } + } + + await User.update({id: {in: _.pluck(stageThreeMdmFocusedUsersWhoHaveNotReceivedAnEmail, 'id')}}) + .set({ + stageThreeNurtureEmailSentAt: nowAt, + }); + + for(let user of stageFourMdmFocusedUsersWhoHaveNotReceivedAnEmail) { + if(user.psychologicalStageLastChangedAt > oneDayAgoAt) { + continue; + } else { + await sails.helpers.sendTemplateEmail.with({ + template: 'email-nurture-stage-four', + layout: 'layout-nurture-email', + templateData: { + firstName: user.firstName + }, + to: user.emailAddress, + toName: `${user.firstName} ${user.lastName}`, + subject: 'Deploy open-source MDM', + bcc: [sails.config.custom.activityCaptureEmailForNutureEmails], + from: sails.config.custom.contactEmailForNutureEmails, + fromName: sails.config.custom.contactNameForNurtureEmails, + ensureAck: true, + }); + } + } + + await User.update({id: {in: _.pluck(stageFourMdmFocusedUsersWhoHaveNotReceivedAnEmail, 'id')}}) + .set({ + stageFourNurtureEmailSentAt: nowAt, + }); + + for(let user of stageFiveMdmFocusedUsersWhoHaveNotReceivedAnEmail) { + if(user.psychologicalStageLastChangedAt > sixWeeksAgoAt) { + continue; + } else { + await sails.helpers.sendTemplateEmail.with({ + template: 'email-nurture-stage-five', + layout: 'layout-nurture-email', + templateData: { + firstName: user.firstName + }, + to: user.emailAddress, + toName: `${user.firstName} ${user.lastName}`, + subject: 'Update', + bcc: [sails.config.custom.activityCaptureEmailForNutureEmails], + from: sails.config.custom.contactEmailForNutureEmails, + fromName: sails.config.custom.contactNameForNurtureEmails, + ensureAck: true, + }); + } + } + + await User.update({id: {in: _.pluck(stageFiveMdmFocusedUsersWhoHaveNotReceivedAnEmail, 'id')}}) + .set({ + stageFiveNurtureEmailSentAt: nowAt, + }); + + } + + +}; + diff --git a/website/views/emails/email-nurture-stage-five.ejs b/website/views/emails/email-nurture-stage-five.ejs new file mode 100644 index 0000000000..a7f912fd37 --- /dev/null +++ b/website/views/emails/email-nurture-stage-five.ejs @@ -0,0 +1,6 @@ +<% /* Note: This is injected into `views/layouts/layout-nurture-email.ejs` */ %> +
Hi <%- firstName %>,
+There’s been some big updates to Fleet and the roadmap. One of our founders is holding a small group call this Friday to help folks get set up. Feel free to join us: https://calendly.com/fleetdm/chat
+ +Hi <%- firstName %>,
+There’s never a good time to migrate your MDM, but Fleet makes it easier. Here’s what the deployment process looks like: https://fleetdm.com/docs/deploy/deploy-fleet
+ +Hi <%- firstName %>,
+We noticed that you were checking out Fleet recently. What did you think?
+PS. One of our founders is holding a small group call this Friday to help folks get set up. Feel free to join us: https://calendly.com/fleetdm/chat
+ +