Created
August 9, 2023 22:15
-
-
Save mihairaulea/ed0695ee908198c3a5bff7c07939c492 to your computer and use it in GitHub Desktop.
Blueprint for the data layer used to store cold email marketing campaigns
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ALL table are mongodb collections | |
// does capping this collection give it hashmap-like props? ask mongodb expert | |
// db.createCollection("userData", {capped: true, size: 100000000}) | |
// table | |
let userData = new Map(); | |
// table | |
let emailIdToUserId = new Map(); | |
// table | |
let emailIdToCampaignId = new Map(); | |
// table | |
let emailIdToProfileId = new Map(); | |
// table | |
let contactedIdsForUserId = new Map(); | |
// generates campaign data for a user | |
function createCampaign({ | |
userId, | |
// could have also been called batchId | |
campaignId, | |
emailSubject, | |
emailText, | |
replyToEmail, | |
elasticSearchQuery, | |
noOfEmailsToSend, | |
sendEmailApi | |
}) { | |
let campaignData = new Object(); | |
// this is not 100% necessary, but better safe than sorry | |
campaignData.userId = userId; | |
// this is again not necessary, because the campaignId is what points to this | |
// we reach this by going through campaign = campaigns.get(userId) -> campaign.get(campaignId) | |
campaignData.campaignId = campaignId; | |
// this will be a template-style text that our other systems will personalize | |
campaignData.emailSubject = emailSubject; | |
// same as subject | |
campaignData.emailText = emailText; | |
// this will be used by the smtp server | |
campaignData.replyToEmail = replyToEmail; | |
// this will be used by the data layer, along with the idsContacted to filter and select the data | |
// used to personalize and get the emails to send to | |
campaignData.elasticSearchQuery = elasticSearchQuery; | |
// this is needed because not all entries have an email address. for later. | |
//campaignData.initialDataSet = getNumberOfHits(elasticSearchQuery); | |
// all 4 get updated on events | |
campaignData.noEmailsSent = noOfEmailsToSend; | |
campaignData.noEmailsOpened = 0; | |
campaignData.noOfEmailsClicked = 0; | |
campaignData.emailsOpened = []; | |
campaignData.emailsClicked = []; | |
// this is populated by the resend.com smtp server | |
// for tests, mock this | |
campaignData.emailsSendAndProfileIds = sendEmailApi(emailSubject, emailText, replyToEmail, elasticSearchQuery); | |
campaignData.emailsSendIds.forEach((emailIdAndProfileId) => { | |
// used to update the campaign statistics on each email event(opened, clicked) | |
emailIdToUserId.set(emailIdAndProfileId.emailId, userId); | |
emailIdToCampaignId.set(emailIdAndProfileId.emailId, campaignId); | |
emailIdToProfileId.set(emailIdAndProfileId.emailId, emailIdAndProfileId.profileId); | |
}); | |
// this can be safely performed in a batch | |
if(contactedIdsForUserId.has(userId)!=true) | |
contactedIdsForUserId.set(userId, new Map()); | |
contactedIdsForUserId.get(userIds).set( emailIdAndProfileId.map(t => t.profileId), true ); | |
let campaignsDataEntry = userData.get(userId); | |
campaignsDataEntry.set(campaignId, campaignData); | |
} | |
// we use this to present the user with the performance of their campaigns | |
// and also enable them to re-target users | |
function getCampaigns(userId) { | |
return userData.get(userId); | |
} | |
// used for retargeting from the UI | |
function getEmailsOpenedForRetargetingFromCampaign(userId, campaignId) { | |
return userData.get(userId).get(campaignId).emailsOpened; | |
} | |
// used for retargeting from the UI | |
function getEmailsClickedForRetargetingFromCampaign(userId, campaignId) { | |
return userData.get(userId).get(campaignId).emailsClicked; | |
} | |
// EVENTS | |
// we need to be able to get the userId and campaignId just with this emailId | |
function emailOpened(emailId) { | |
// first get the campaign data | |
userData.get( emailIdToUserId.get(emailId) ).get( emailIdToCampaignId.get(emailId) ).emailsOpened.push(emailId); | |
} | |
function emailClicked(emailId) { | |
userData.get( emailIdToUserId.get(emailId) ).get( emailIdToCampaignId.get(emailId) ).emailsOpened.push(emailId); | |
} | |
// data source | |
// --- CONTACTED IDS | |
// the contacted ids is used by the lead data layer to exclude people already contacted in | |
// a. search | |
// b. when launching a campaign | |
// this is created/added to when the campaign starts | |
// this will be used on every id returned from the elasticsearch query | |
function hasIdBeenContacted(userId, contactedId) { | |
return contactedIdsForUserId.get(userId).has(contactedId) == true; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment