Skip to content

Commit

Permalink
Website: add automated MDM nurture emails (#20760)
Browse files Browse the repository at this point in the history
Changes:
- Added three new emails for MDM-focused users who are psystage 3, 4
,and 5.
- Added a new email layout
- Added a script to send nurture emails to users with an MDM primary
buying situation who signed up after 2024-07-22 that
   - have been in psychological stage 3 for 24 hours
   - have been in psychological stage 4 for 24 hours
   - have been in psychological stage 5 for six weeks
- Updated the USer model to have. four new attributes:
   - `psychologicalStageLastChangedAt`
   - `stageThreeNurtureEmailSentAt`
   - `stageFourNurtureEmailSentAt`
   - `stageFiveNurtureEmailSentAt`
- Added a new custom config variable
`sails.config.custom.contactEmailForNutureEmails`
  • Loading branch information
eashaw committed Jul 27, 2024
1 parent 2cb422a commit ddc0cdb
Show file tree
Hide file tree
Showing 10 changed files with 219 additions and 1 deletion.
18 changes: 18 additions & 0 deletions website/api/controllers/admin/view-email-template-preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
6 changes: 5 additions & 1 deletion website/api/controllers/save-questionnaire-progress.js
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand Down
22 changes: 22 additions & 0 deletions website/api/models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.'
},


// ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗
// ║╣ ║║║╠╩╗║╣ ║║╚═╗
// ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions website/config/custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,11 @@ module.exports.custom = {
// customerWorkspaceOneOauthSecret: '…',
// customerMigrationWebhookSecret: '…',

// For nurture emails:
// contactEmailForNutureEmails: '…',
// activityCaptureEmailForNutureEmails: '…',
// contactNameForNurtureEmails: '…',

// Deal registration form
// dealRegistrationContactEmailAddress: '…',

Expand Down
128 changes: 128 additions & 0 deletions website/scripts/deliver-nurture-emails.js
Original file line number Diff line number Diff line change
@@ -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,
});

}


};

6 changes: 6 additions & 0 deletions website/views/emails/email-nurture-stage-five.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<% /* Note: This is injected into `views/layouts/layout-nurture-email.ejs` */ %>
<div style="padding: 0px 0px 0px 0px">
<p style=" font-size: 16px; line-height: 24px; margin-bottom: 24px; margin-top: 0px">Hi <%- firstName %>,</p>
<p style=" font-size: 16px; line-height: 24px; margin-bottom: 48px; margin-top: 0px">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: <a style="color: #6A67FE; text-decoration: none; word-break: break-word;" href="https://calendly.com/fleetdm/chat" target="_blank">https://calendly.com/fleetdm/chat</a></p>

</div>
6 changes: 6 additions & 0 deletions website/views/emails/email-nurture-stage-four.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<% /* Note: This is injected into `views/layouts/layout-nurture-email.ejs` */ %>
<div style="padding: 0px 0px 0px 0px">
<p style=" font-size: 16px; line-height: 24px; margin-bottom: 24px; margin-top: 0px">Hi <%- firstName %>,</p>
<p style=" font-size: 16px; line-height: 24px; margin-bottom: 48px; margin-top: 0px">There’s never a good time to migrate your MDM, but Fleet makes it easier. Here’s what the deployment process looks like: <a style="color: #6A67FE; text-decoration: none; word-break: break-word;" href="https://fleetdm.com/docs/deploy/deploy-fleet" target="_blank">https://fleetdm.com/docs/deploy/deploy-fleet</a></p>

</div>
7 changes: 7 additions & 0 deletions website/views/emails/email-nurture-stage-three.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<% /* Note: This is injected into `views/layouts/layout-nurture-email.ejs` */ %>
<div style="padding: 0px 0px 0px 0px">
<p style=" font-size: 16px; line-height: 24px; margin-bottom: 24px; margin-top: 0px">Hi <%- firstName %>,</p>
<p style=" font-size: 16px; line-height: 24px; margin-bottom: 24px; margin-top: 0px">We noticed that you were checking out Fleet recently. What did you think?</p>
<p style=" font-size: 16px; line-height: 24px; margin-bottom: 48px; margin-top: 0px">PS. One of our founders is holding a small group call this Friday to help folks get set up. Feel free to join us: <a style="color: #6A67FE; text-decoration: none; word-break: break-word;" href="https://calendly.com/fleetdm/chat" target="_blank">https://calendly.com/fleetdm/chat</a></p>

</div>
22 changes: 22 additions & 0 deletions website/views/layouts/layout-nurture-email.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<% /* Default layout for email templates */ %>
<div style="width: 100%; max-width: 600px; font-family: 'Inter','Helvetica','arial', sans-serif; box-sizing: border-box; padding: 0; margin: auto; border-radius: 8px; border: 1px solid #E2E4EA; background: #FFF;">
<div style="background: transparent; text-align: left;">
<a href="https://fleetdm.com"><img style="display: inline-block; width: 100%; max-width: 100%; border-top-left-radius: 8px; border-top-right-radius: 8px;" alt="Logo" src="<%= url.resolve(sails.config.custom.baseUrl,'images/email-header-959x452@2x.png')%>"/></a>
</div>
<div style="color: #192147; font-size: 16px; box-sizing: border-box; padding: 32px; width: 100%; max-width: 600px; margin-left: auto; margin-right: auto;">
<div>
<%- body %>
<hr style="color: #E2E4EA;"/>
<div style="display: inline-flex; padding-top: 32px;">
<a href="https://fleetdm.com/slack" target="_blank"><img style="height: 20px; width: 20px; margin-right: 24px;" alt="Join the osquery Slack community" src="<%= url.resolve(sails.config.custom.baseUrl,'images/logo-slack-dark-20x20@2x.png')%>"></a>
<a href="https://github.com/fleetdm/fleet" target="_blank"><img style="height: 20px; width: 20px; margin-right: 24px;" alt="View Fleet's GitHub repo" src="<%= url.resolve(sails.config.custom.baseUrl,'images/logo-github-dark-24x24@2x.png')%>"></a>
<a href="https://fleetdm.com/slack" target="_blank"><img style="height: 20px; width: 20px; margin-right: 24px;" alt="Conenct with Fleet on LinkedIn" src="<%= url.resolve(sails.config.custom.baseUrl,'images/logo-linkedin-24x24@2x.png')%>"></a>
<a href="https://twitter.com/fleetctl" target="_blank"><img style="height: 20px; width: 24px; margin-right: 24px;" alt="Follow Fleet on Twitter" src="<%= url.resolve(sails.config.custom.baseUrl,'images/logo-x-24x24@2x.png')%>"></a>
<a href="https://www.youtube.com/channel/UCZyoqZ4exJvoibmTKJrQ-dQ" target="_blank"><img style="width: 20px; margin-top: 3px; margin-right: 24px;" alt="Watch Fleet's Youtube videos" src="<%= url.resolve(sails.config.custom.baseUrl,'images/logo-youtube-29x20@2x.png')%>"></a>
</div>
<div style="text-align: left; padding-top: 15px; font-size: 12px; color: #3E4771;">
<p<%= (new Date()).getFullYear() %> Fleet Inc.<br> All trademarks are the property of their respective owners.</p>
</div>
</div>
</div>
</div>

0 comments on commit ddc0cdb

Please sign in to comment.