2016-03-14 12:35:54 -04:00
// Upsert new lead data into Close.io
'use strict' ;
2016-07-12 09:32:50 -04:00
if ( process . argv . length !== 10 ) {
log ( "Usage: node <script> <Close.io general API key> <Close.io mail API key1> <Close.io mail API key2> <Close.io mail API key3> <Close.io mail API key4> <Close.io EU mail API key> <Intercom 'App ID:API key'> <mongo connection Url>" ) ;
2016-03-14 12:35:54 -04:00
process . exit ( ) ;
}
2016-03-31 12:23:33 -04:00
// TODO: Test multiple contacts
// TODO: Support multiple emails for the same contact (i.e diff trial and coco emails)
// TODO: Update notes with new data (e.g. coco user or intercom url)
2016-04-23 19:47:10 -04:00
// TODO: Reduce response data via _fields param
2016-05-03 18:52:53 -04:00
// TODO: Assumes 1:1 contact:email relationship (Close.io supports multiple emails for a single contact)
2016-05-24 19:10:29 -04:00
// TODO: Cleanup country/status lookup code
2016-09-19 19:58:39 -04:00
// TODO: automation states should be driven at contact-level
// TODO: unclear when we stop execution for an error vs. print it and continue
2016-03-31 12:23:33 -04:00
2016-05-11 13:21:53 -04:00
// Save as custom fields instead of user-specific lead notes (also saving nces_ props)
2016-08-05 12:08:37 -04:00
const commonTrialProperties = [ 'organization' , 'district' , 'city' , 'state' , 'country' ] ;
2016-03-31 12:23:33 -04:00
// Old properties which are deprecated or moved
const customFieldsToRemove = [
'coco_name' , 'coco_firstName' , 'coco_lastName' , 'coco_gender' , 'coco_numClassrooms' , 'coco_numStudents' , 'coco_role' , 'coco_schoolName' , 'coco_stats' , 'coco_lastLevel' ,
'email' , 'intercom_url' , 'name' ,
'trial_created' , 'trial_educationLevel' , 'trial_phoneNumber' , 'trial_email' , 'trial_location' , 'trial_name' , 'trial_numStudents' , 'trial_role' , 'trial_userID' , 'userID' , 'trial_organization' , 'trial_city' , 'trial_state' , 'trial_country' ,
'demo_request_organization' , 'demo_request_city' , 'demo_request_state' , 'demo_request_country'
] ;
2016-05-03 18:52:53 -04:00
const createTeacherEmailTemplatesAuto1 = [ 'tmpl_i5bQ2dOlMdZTvZil21bhTx44JYoojPbFkciJ0F560mn' , 'tmpl_CEZ9PuE1y4PRvlYiKB5kRbZAQcTIucxDvSeqvtQW57G' ] ;
2016-08-24 17:46:15 -04:00
const demoRequestEmailTemplatesAuto1 = [
'tmpl_cGb6m4ssDvqjvYd8UaG6cacvtSXkZY3vj9b9lSmdQrf' , // (Auto1) Demo Request Short
'tmpl_2hV6OdOXtsObLQK9qlRdpf0C9QKbER06T17ksGYOoUE' , // (Auto1) Demo Request With Questions
'tmpl_Q0tweZ5H4xs2E489KwdYj3HET9PpzkQ7jgDQb9hOMTR' , // (Auto1) Demo Request Without Questions
] ;
2016-05-19 17:07:58 -04:00
const createTeacherInternationalEmailTemplateAuto1 = 'tmpl_8vsXwcr6dWefMnAEfPEcdHaxqSfUKUY8UKq6WfReGqG' ;
const demoRequestInternationalEmailTemplateAuto1 = 'tmpl_nnH1p3II7G7NJYiPOIHphuj4XUaDptrZk1mGQb2d9Xa' ;
2016-05-24 19:10:29 -04:00
const createTeacherNlEmailTemplatesAuto1 = [ 'tmpl_yf9tAPasz8KV7L414GhWWIclU8ewclh3Z8lCx2mCoIU' , 'tmpl_OgPCV2p59uq0daVuUPF6r1rcQkxJbViyZ1ZMtW45jY8' ] ;
const demoRequestNlEmailTemplatesAuto1 = [ 'tmpl_XGKyZm6gcbqZ5jirt7A54Vu8p68cLxAsKZtb9QBABUE' , 'tmpl_xcfgQjUHPa6LLsbPWuPvEUElFXHmIpLa4IZEybJ0b0u' ] ;
2016-04-26 13:31:08 -04:00
// Prioritized Close.io lead status match list
const closeIoInitialLeadStatuses = [
{ status : 'Inbound UK Auto Attempt 1' , regex : /^uk$|\.uk$/ } ,
{ status : 'Inbound Canada Auto Attempt 1' , regex : /^ca$|\.ca$/ } ,
{ status : 'Inbound AU Auto Attempt 1' , regex : /^au$|\.au$/ } ,
{ status : 'Inbound NZ Auto Attempt 1' , regex : /^nz$|\.nz$/ } ,
{ status : 'New US Schools Auto Attempt 1' , regex : /^us$|\.us$|\.gov$|k12|sd/ } ,
{ status : 'Inbound International Auto Attempt 1' , regex : /^[A-Za-z]{2}$|\.[A-Za-z]{2}$/ } ,
{ status : 'Auto Attempt 1' , regex : /^[A-Za-z]*$/ }
] ;
2016-05-19 17:07:58 -04:00
const defaultLeadStatus = 'Auto Attempt 1' ;
const defaultInternationalLeadStatus = 'Inbound International Auto Attempt 1' ;
2016-05-24 19:10:29 -04:00
const defaultEuLeadStatus = 'Inbound EU Auto Attempt 1' ;
2016-05-19 17:07:58 -04:00
2016-05-24 19:10:29 -04:00
const usSchoolStatuses = [ 'Auto Attempt 1' , 'New US Schools Auto Attempt 1' , 'New US Schools Auto Attempt 1 Low' ] ;
2016-04-26 13:31:08 -04:00
2016-04-08 00:23:39 -04:00
const emailDelayMinutes = 27 ;
2016-03-14 12:35:54 -04:00
2016-09-02 13:47:03 -04:00
const closeParallelLimit = 10 ;
2016-09-19 19:58:39 -04:00
const intercomParallelLimit = 100 ;
2016-08-25 13:21:36 -04:00
2016-03-14 12:35:54 -04:00
const scriptStartTime = new Date ( ) ;
2016-09-07 18:59:05 -04:00
const closeIoApiKey = process . argv [ 2 ] ; // Matt
2016-05-19 17:07:58 -04:00
// Automatic mails sent as API owners, first key assumed to be primary and gets 50% of the leads
2016-09-07 18:59:05 -04:00
// Names in comments are for reference, but Source of Truth is updateSalesLeads.sh on the analytics server
2016-06-14 19:44:51 -04:00
const closeIoMailApiKeys = [
{
2016-09-07 18:59:05 -04:00
apiKey : process . argv [ 3 ] , // Lisa
2016-07-21 16:23:46 -04:00
weight : . 8
2016-06-14 19:44:51 -04:00
} ,
{
2016-09-07 18:59:05 -04:00
apiKey : process . argv [ 4 ] , // Elliot
weight : . 15
2016-06-14 19:44:51 -04:00
} ,
{
2016-09-07 18:59:05 -04:00
apiKey : process . argv [ 5 ] , // Nolan
2016-06-14 19:44:51 -04:00
weight : . 05
} ,
2016-07-12 09:32:50 -04:00
{
2016-09-07 18:59:05 -04:00
apiKey : process . argv [ 6 ] , // Sean
weight : 0
2016-07-12 09:32:50 -04:00
} ,
2016-06-14 19:44:51 -04:00
] ;
2016-09-07 18:59:05 -04:00
const closeIoEuMailApiKey = process . argv [ 7 ] ; // Jurian
2016-07-12 09:32:50 -04:00
const intercomAppIdApiKey = process . argv [ 8 ] ;
2016-03-14 12:35:54 -04:00
const intercomAppId = intercomAppIdApiKey . split ( ':' ) [ 0 ] ;
const intercomApiKey = intercomAppIdApiKey . split ( ':' ) [ 1 ] ;
2016-07-12 09:32:50 -04:00
const mongoConnUrl = process . argv [ 9 ] ;
2016-03-14 12:35:54 -04:00
const MongoClient = require ( 'mongodb' ) . MongoClient ;
2016-03-31 12:23:33 -04:00
const async = require ( 'async' ) ;
2016-05-24 19:10:29 -04:00
const countryData = require ( 'country-data' ) ;
2016-05-19 17:07:58 -04:00
const countryList = require ( 'country-list' ) ( ) ;
2016-04-26 13:31:08 -04:00
const parseDomain = require ( 'parse-domain' ) ;
2016-03-14 12:35:54 -04:00
const request = require ( 'request' ) ;
const earliestDate = new Date ( ) ;
earliestDate . setUTCDate ( earliestDate . getUTCDate ( ) - 10 ) ;
2016-04-26 13:31:08 -04:00
// ** Main program
2016-05-03 18:52:53 -04:00
async . series ( [
upsertLeads
] ,
( err , results ) => {
if ( err ) console . error ( err ) ;
log ( "Script runtime: " + ( new Date ( ) - scriptStartTime ) ) ;
}
) ;
function upsertLeads ( done ) {
// log('DEBUG: Finding leads..');
2016-09-19 19:58:39 -04:00
findCocoContacts ( ( err , contacts ) => {
2016-05-03 18:52:53 -04:00
if ( err ) return done ( err ) ;
2016-09-19 19:58:39 -04:00
log ( ` Num contacts ${ Object . keys ( contacts ) . length } ` ) ;
2016-05-03 18:52:53 -04:00
// log('DEBUG: Adding Intercom data..');
2016-09-19 19:58:39 -04:00
addIntercomData ( contacts , ( err ) => {
2016-05-03 18:52:53 -04:00
if ( err ) return done ( err ) ;
2016-09-19 19:58:39 -04:00
// log('DEBUG: Updating contacts..');
updateCloseLeads ( contacts , ( err ) => {
2016-05-03 18:52:53 -04:00
return done ( err ) ;
} ) ;
2016-03-14 12:35:54 -04:00
} ) ;
} ) ;
2016-05-03 18:52:53 -04:00
}
2016-03-14 12:35:54 -04:00
2016-04-26 13:31:08 -04:00
// ** Utilities
2016-05-24 19:10:29 -04:00
function getCountryCode ( country , emails ) {
// console.log(`DEBUG: getCountryCode ${country} ${emails.length}`);
if ( country ) {
2016-06-06 16:41:49 -04:00
if ( country . indexOf ( 'Nederland' ) >= 0 ) return 'NL' ;
2016-05-24 19:10:29 -04:00
let countryCode = countryList . getCode ( country ) ;
if ( countryCode ) return countryCode ;
}
for ( const email of emails ) {
2016-09-02 13:47:03 -04:00
const domain = parseDomain ( email ) ;
if ( ! domain ) continue ;
const tld = domain . tld ;
2016-05-24 19:10:29 -04:00
if ( tld ) {
const matches = /^[A-Za-z]*\.?([A-Za-z]{2})$/ig . exec ( tld ) ;
if ( matches && matches . length === 2 ) {
return matches [ 1 ] . toUpperCase ( ) ;
}
}
}
}
2016-05-12 13:54:39 -04:00
function getInitialLeadStatusViaCountry ( country , trialRequests ) {
2016-05-24 19:10:29 -04:00
// console.log(`DEBUG: getInitialLeadStatusViaCountry ${country} ${trialRequests.length}`);
2016-05-19 17:07:58 -04:00
if ( /^u\.s\.?(\.a)?\.?$|^us$|usa|america|united states/ig . test ( country ) ) {
2016-05-12 13:54:39 -04:00
const status = 'New US Schools Auto Attempt 1'
2016-05-24 19:10:29 -04:00
return isLowValueUsLead ( status , trialRequests ) ? ` ${ status } Low ` : status ;
2016-04-26 13:31:08 -04:00
}
2016-05-24 19:10:29 -04:00
const highValueLead = isHighValueLead ( trialRequests ) ;
if ( /^england$|^uk$|^united kingdom$/ig . test ( country ) && highValueLead ) {
2016-05-19 17:07:58 -04:00
return 'Inbound UK Auto Attempt 1' ;
}
if ( /^ca$|^canada$/ig . test ( country ) ) {
return 'Inbound Canada Auto Attempt 1' ;
}
if ( /^au$|^australia$/ig . test ( country ) ) {
return 'Inbound AU Auto Attempt 1' ;
}
if ( /^nz$|^new zealand$/ig . test ( country ) ) {
return 'Inbound AU Auto Attempt 1' ;
}
if ( /bolivia|iran|korea|macedonia|taiwan|tanzania|^venezuela$/ig . test ( country ) ) {
return defaultInternationalLeadStatus ;
}
2016-05-24 19:10:29 -04:00
const countryCode = countryList . getCode ( country ) ;
if ( countryCode ) {
2016-05-24 19:54:38 -04:00
if ( countryCode === 'NL' || countryCode === 'BE' ) {
return defaultEuLeadStatus ;
}
2016-05-24 19:10:29 -04:00
if ( isEuCountryCode ( countryCode ) ) {
2016-05-24 19:54:38 -04:00
return highValueLead ? 'Inbound EU Auto Attempt 1 High' : defaultEuLeadStatus ;
2016-05-24 19:10:29 -04:00
}
2016-05-19 17:07:58 -04:00
return defaultInternationalLeadStatus ;
}
return null ;
2016-04-26 13:31:08 -04:00
}
2016-05-12 13:54:39 -04:00
function getInitialLeadStatusViaEmails ( emails , trialRequests ) {
2016-05-24 19:10:29 -04:00
// console.log(`DEBUG: getInitialLeadStatusViaEmails ${emails.length} ${trialRequests.length}`);
2016-04-26 13:31:08 -04:00
let currentStatus = null ;
let currentRank = closeIoInitialLeadStatuses . length ;
for ( const email of emails ) {
let tld = parseDomain ( email ) . tld ;
tld = tld ? tld . toLowerCase ( ) : '' ;
for ( let rank = 0 ; rank < currentRank ; rank ++ ) {
if ( closeIoInitialLeadStatuses [ rank ] . regex . test ( tld ) ) {
currentStatus = closeIoInitialLeadStatuses [ rank ] . status ;
currentRank = rank ;
}
}
}
2016-05-24 19:10:29 -04:00
if ( ! currentStatus || [ defaultLeadStatus , defaultInternationalLeadStatus ] . indexOf ( currentStatus ) >= 0 ) {
// Look for a better EU match
const countryCode = getCountryCode ( null , emails ) ;
2016-05-24 19:54:38 -04:00
if ( countryCode === 'NL' || countryCode === 'BE' ) {
return defaultEuLeadStatus ;
}
2016-05-24 19:10:29 -04:00
if ( isEuCountryCode ( countryCode ) ) {
2016-05-24 19:54:38 -04:00
return isHighValueLead ( trialRequests ) ? 'Inbound EU Auto Attempt 1 High' : defaultEuLeadStatus ;
2016-05-24 19:10:29 -04:00
}
}
2016-05-19 17:07:58 -04:00
currentStatus = currentStatus ? currentStatus : defaultLeadStatus ;
2016-05-24 19:10:29 -04:00
return isLowValueUsLead ( currentStatus , trialRequests ) ? ` ${ currentStatus } Low ` : currentStatus ;
}
function isEuCountryCode ( countryCode ) {
if ( countryData . regions . northernEurope . countries . indexOf ( countryCode ) >= 0 ) {
return true ;
}
if ( countryData . regions . southernEurope . countries . indexOf ( countryCode ) >= 0 ) {
return true ;
}
if ( countryData . regions . easternEurope . countries . indexOf ( countryCode ) >= 0 ) {
return true ;
}
if ( countryData . regions . westernEurope . countries . indexOf ( countryCode ) >= 0 ) {
return true ;
}
return false ;
2016-05-12 13:54:39 -04:00
}
2016-05-24 19:10:29 -04:00
function isLowValueUsLead ( status , trialRequests ) {
2016-05-19 17:07:58 -04:00
if ( isUSSchoolStatus ( status ) ) {
2016-05-12 13:54:39 -04:00
for ( const trialRequest of trialRequests ) {
if ( parseInt ( trialRequest . properties . nces _district _students ) < 5000 ) {
return true ;
}
2016-05-24 19:10:29 -04:00
else if ( parseInt ( trialRequest . properties . nces _district _students ) >= 5000 ) {
return false ;
}
2016-05-12 13:54:39 -04:00
}
for ( const trialRequest of trialRequests ) {
// Must match these values: https://github.com/codecombat/codecombat/blob/master/app/templates/teachers/request-quote-view.jade#L159
if ( [ '1-500' , '500-1,000' ] . indexOf ( trialRequest . properties . numStudentsTotal ) >= 0 ) {
return true ;
}
}
}
return false ;
2016-04-26 13:31:08 -04:00
}
2016-05-24 19:10:29 -04:00
function isHighValueLead ( trialRequests ) {
for ( const trialRequest of trialRequests ) {
// Must match these values: https://github.com/codecombat/codecombat/blob/master/app/templates/teachers/request-quote-view.jade#L159
if ( [ '5,000-10,000' , '10,000+' ] . indexOf ( trialRequest . properties . numStudentsTotal ) >= 0 ) {
return true ;
}
}
return false ;
}
2016-05-19 17:07:58 -04:00
function isUSSchoolStatus ( status ) {
return usSchoolStatuses . indexOf ( status ) >= 0 ;
}
2016-05-24 19:10:29 -04:00
function getEmailApiKey ( leadStatus ) {
2016-05-24 19:54:38 -04:00
if ( leadStatus === defaultEuLeadStatus ) return closeIoEuMailApiKey ;
2016-04-26 13:31:08 -04:00
if ( closeIoMailApiKeys . length < 0 ) return ;
2016-06-14 19:44:51 -04:00
const weightedList = [ ] ;
for ( let closeIoMailApiKey of closeIoMailApiKeys ) {
const multiples = closeIoMailApiKey . weight * 100 ;
for ( let i = 0 ; i < multiples ; i ++ ) {
weightedList . push ( closeIoMailApiKey . apiKey ) ;
}
}
return weightedList [ Math . floor ( Math . random ( ) * weightedList . length ) ] ;
2016-04-26 13:31:08 -04:00
}
function getRandomEmailTemplate ( templates ) {
if ( templates . length < 0 ) return '' ;
return templates [ Math . floor ( Math . random ( ) * templates . length ) ] ;
}
2016-05-24 19:10:29 -04:00
function getEmailTemplate ( siteOrigin , leadStatus , countryCode ) {
// console.log(`DEBUG: getEmailTemplate ${siteOrigin} ${leadStatus} ${countryCode}`);
2016-05-19 17:07:58 -04:00
if ( isUSSchoolStatus ( leadStatus ) ) {
if ( [ 'create teacher' , 'convert teacher' ] . indexOf ( siteOrigin ) >= 0 ) {
return getRandomEmailTemplate ( createTeacherEmailTemplatesAuto1 ) ;
}
return getRandomEmailTemplate ( demoRequestEmailTemplatesAuto1 ) ;
}
2016-05-24 19:10:29 -04:00
if ( leadStatus === defaultEuLeadStatus && ( countryCode === 'NL' || countryCode === 'BE' ) ) {
if ( [ 'create teacher' , 'convert teacher' ] . indexOf ( siteOrigin ) >= 0 ) {
return getRandomEmailTemplate ( createTeacherNlEmailTemplatesAuto1 ) ;
}
return getRandomEmailTemplate ( demoRequestNlEmailTemplatesAuto1 ) ;
}
2016-05-19 17:07:58 -04:00
if ( [ 'create teacher' , 'convert teacher' ] . indexOf ( siteOrigin ) >= 0 ) {
return createTeacherInternationalEmailTemplateAuto1 ;
}
return demoRequestInternationalEmailTemplateAuto1 ;
}
2016-04-26 13:31:08 -04:00
function isSameEmailTemplateType ( template1 , template2 ) {
2016-05-19 17:07:58 -04:00
if ( template1 == template2 ) {
return true ;
}
2016-05-03 18:52:53 -04:00
if ( createTeacherEmailTemplatesAuto1 . indexOf ( template1 ) >= 0 && createTeacherEmailTemplatesAuto1 . indexOf ( template2 ) >= 0 ) {
2016-04-26 13:31:08 -04:00
return true ;
}
2016-05-03 18:52:53 -04:00
if ( demoRequestEmailTemplatesAuto1 . indexOf ( template1 ) >= 0 && demoRequestEmailTemplatesAuto1 . indexOf ( template2 ) >= 0 ) {
2016-04-26 13:31:08 -04:00
return true ;
}
return false ;
}
function log ( str ) {
console . log ( new Date ( ) . toISOString ( ) + " " + str ) ;
}
2016-03-14 12:35:54 -04:00
2016-04-26 13:31:08 -04:00
// ** Coco data collection methods and class
2016-03-14 12:35:54 -04:00
2016-09-19 19:58:39 -04:00
function findCocoContacts ( done ) {
2016-04-26 13:31:08 -04:00
MongoClient . connect ( mongoConnUrl , ( err , db ) => {
if ( err ) return done ( err ) ;
// Recent trial requests
const query = { $and : [ { created : { $gte : earliestDate } } , { type : 'course' } ] } ;
db . collection ( 'trial.requests' ) . find ( query ) . toArray ( ( err , trialRequests ) => {
if ( err ) {
db . close ( ) ;
return done ( err ) ;
}
2016-09-19 19:58:39 -04:00
const contacts = { } ;
2016-04-26 13:31:08 -04:00
for ( const trialRequest of trialRequests ) {
if ( ! trialRequest . properties || ! trialRequest . properties . email ) continue ;
const email = trialRequest . properties . email . toLowerCase ( ) ;
2016-09-19 19:58:39 -04:00
if ( contacts [ email ] ) {
console . log ( ` ERROR: found additional course trial requests for email ${ email } , skipping. ` ) ;
continue ;
}
contacts [ email ] = new CocoContact ( email , trialRequest ) ;
2016-04-26 13:31:08 -04:00
}
// Users for trial requests
const query = { $and : [
2016-09-19 19:58:39 -04:00
{ emailLower : { $in : Object . keys ( contacts ) } } ,
2016-04-26 13:31:08 -04:00
{ anonymous : false }
] } ;
db . collection ( 'users' ) . find ( query ) . toArray ( ( err , users ) => {
if ( err ) {
db . close ( ) ;
return done ( err ) ;
}
const userIDs = [ ] ;
2016-09-19 19:58:39 -04:00
const userContactMap = { } ;
2016-04-26 13:31:08 -04:00
const userEmailMap = { } ;
for ( const user of users ) {
const email = user . emailLower ;
2016-09-19 19:58:39 -04:00
contacts [ email ] . addUser ( user ) ;
2016-04-26 13:31:08 -04:00
userIDs . push ( user . _id ) ;
2016-09-19 19:58:39 -04:00
userContactMap [ user . _id . valueOf ( ) ] = contacts [ email ] ;
2016-04-26 13:31:08 -04:00
userEmailMap [ user . _id . valueOf ( ) ] = email ;
}
// Classrooms for users
const query = { ownerID : { $in : userIDs } } ;
db . collection ( 'classrooms' ) . find ( query ) . toArray ( ( err , classrooms ) => {
if ( err ) {
db . close ( ) ;
return done ( err ) ;
}
for ( const classroom of classrooms ) {
2016-09-19 19:58:39 -04:00
userContactMap [ classroom . ownerID . valueOf ( ) ] . addClassroom ( classroom ) ;
2016-04-26 13:31:08 -04:00
}
db . close ( ) ;
2016-09-19 19:58:39 -04:00
return done ( null , contacts ) ;
2016-04-26 13:31:08 -04:00
} ) ;
} ) ;
} ) ;
} ) ;
}
2016-09-19 19:58:39 -04:00
function createAddIntercomDataFn ( contact ) {
2016-04-26 13:31:08 -04:00
return ( done ) => {
const options = {
2016-09-19 19:58:39 -04:00
url : ` https://api.intercom.io/users?email= ${ encodeURIComponent ( contact . email ) } ` ,
2016-04-26 13:31:08 -04:00
auth : {
user : intercomAppId ,
pass : intercomApiKey
} ,
headers : {
'Accept' : 'application/json'
}
} ;
request . get ( options , ( error , response , body ) => {
if ( error ) return done ( error ) ;
try {
const user = JSON . parse ( body ) ;
2016-09-19 19:58:39 -04:00
contact . addIntercomUser ( user ) ;
2016-04-26 13:31:08 -04:00
}
catch ( err ) {
console . log ( err ) ;
console . log ( body ) ;
}
return done ( ) ;
} ) ;
} ;
}
2016-09-19 19:58:39 -04:00
function addIntercomData ( contacts , done ) {
2016-04-26 13:31:08 -04:00
const tasks = [ ]
2016-09-19 19:58:39 -04:00
for ( const email in contacts ) {
tasks . push ( createAddIntercomDataFn ( contacts [ email ] ) ) ;
2016-04-26 13:31:08 -04:00
}
2016-09-19 19:58:39 -04:00
async . parallelLimit ( tasks , intercomParallelLimit , done ) ;
2016-04-26 13:31:08 -04:00
}
2016-09-19 19:58:39 -04:00
class CocoContact {
constructor ( email , trialRequest ) {
this . email = email ;
this . name = email ;
this . trialRequest = trialRequest ;
if ( this . trialRequest . properties . firstName && this . trialRequest . properties . lastName ) {
this . name = ` ${ this . trialRequest . properties . firstName } ${ this . trialRequest . properties . lastName } ` ;
}
else if ( this . trialRequest . properties . name ) {
this . name = this . trialRequest . properties . name ;
}
this . leadName = trialRequest . properties . nces _name || trialRequest . properties . organization
|| trialRequest . properties . school || trialRequest . properties . district
|| trialRequest . properties . nces _district || email ;
}
addClassroom ( classroom ) {
this . numClassrooms = this . numClassrooms ? this . numClassrooms + 1 : 1 ;
2016-03-14 12:35:54 -04:00
if ( classroom . members && classroom . members . length ) {
2016-09-19 19:58:39 -04:00
this . numStudents = this . numStudents ? this . numStudents + classroom . members . length : classroom . members . length ;
2016-03-14 12:35:54 -04:00
}
}
2016-09-19 19:58:39 -04:00
addIntercomUser ( user ) {
2016-03-14 12:35:54 -04:00
if ( user && user . id ) {
2016-09-19 19:58:39 -04:00
this . intercomUrl = ` https://app.intercom.io/a/apps/ ${ intercomAppId } /users/ ${ user . id } / ` ;
2016-06-26 23:46:03 -04:00
if ( user . last _request _at ) {
2016-09-19 19:58:39 -04:00
this . intercomLastSeen = new Date ( parseInt ( user . last _request _at ) * 1000 ) ;
2016-06-26 23:46:03 -04:00
}
if ( user . session _count ) {
2016-09-19 19:58:39 -04:00
this . intercomSessionCount = parseInt ( user . session _count ) ;
2016-06-26 23:46:03 -04:00
}
2016-03-14 12:35:54 -04:00
}
}
2016-09-19 19:58:39 -04:00
addUser ( user ) {
this . user = user ;
2016-03-31 12:23:33 -04:00
}
2016-04-26 13:31:08 -04:00
getInitialLeadStatus ( ) {
2016-09-19 19:58:39 -04:00
const props = this . trialRequest . properties ;
if ( props && props [ 'country' ] ) {
const status = getInitialLeadStatusViaCountry ( props [ 'country' ] , [ this . trialRequest ] ) ;
if ( status ) return status ;
2016-04-26 13:31:08 -04:00
}
2016-09-19 19:58:39 -04:00
return getInitialLeadStatusViaEmails ( [ this . email ] , [ this . trialRequest ] ) ;
2016-04-26 13:31:08 -04:00
}
2016-03-31 12:23:33 -04:00
getLeadPostData ( ) {
const postData = {
2016-09-19 19:58:39 -04:00
display _name : this . leadName ,
name : this . leadName ,
2016-04-26 13:31:08 -04:00
status : this . getInitialLeadStatus ( ) ,
2016-09-19 19:58:39 -04:00
contacts : [ this . getContactPostData ( ) ] ,
2016-03-31 12:23:33 -04:00
custom : {
lastUpdated : new Date ( ) ,
2016-04-08 00:23:39 -04:00
'Lead Origin' : this . getLeadOrigin ( )
2016-03-14 12:35:54 -04:00
}
2016-03-31 12:23:33 -04:00
} ;
2016-09-19 19:58:39 -04:00
const props = this . trialRequest . properties ;
if ( props ) {
for ( const prop in props ) {
if ( commonTrialProperties . indexOf ( prop ) >= 0 || /nces_/ig . test ( prop ) ) {
postData . custom [ ` demo_ ${ prop } ` ] = props [ prop ] ;
2016-03-31 12:23:33 -04:00
}
2016-03-14 12:35:54 -04:00
}
2016-09-19 19:58:39 -04:00
}
if ( this . intercomLastSeen && ( this . intercomLastSeen > ( postData . custom [ 'intercom_lastSeen' ] || 0 ) ) ) {
postData . custom [ 'intercom_lastSeen' ] = this . intercomLastSeen ;
}
if ( this . intercomSessionCount && ( this . intercomSessionCount > ( postData . custom [ 'intercom_sessionCount' ] || 0 ) ) ) {
postData . custom [ 'intercom_sessionCount' ] = this . intercomSessionCount ;
2016-03-14 12:35:54 -04:00
}
2016-03-31 12:23:33 -04:00
return postData ;
2016-03-14 12:35:54 -04:00
}
2016-09-19 19:58:39 -04:00
getLeadPutData ( closeLead ) {
// console.log('DEBUG: getLeadPutData', closeLead.id);
2016-03-31 12:23:33 -04:00
const putData = { } ;
2016-09-19 19:58:39 -04:00
const currentCustom = closeLead . custom || { } ;
2016-04-08 00:23:39 -04:00
if ( ! currentCustom [ 'Lead Origin' ] ) {
putData [ 'custom.Lead Origin' ] = this . getLeadOrigin ( ) ;
2016-03-31 12:23:33 -04:00
}
2016-09-19 19:58:39 -04:00
const props = this . trialRequest . properties ;
if ( props ) {
for ( const prop in props ) {
if ( ! currentCustom [ ` demo_ ${ prop } ` ] && ( commonTrialProperties . indexOf ( prop ) >= 0 || /nces_/ig . test ( prop ) ) ) {
putData [ ` custom.demo_ ${ prop } ` ] = props [ prop ] ;
2016-03-31 12:23:33 -04:00
}
2016-03-14 12:35:54 -04:00
}
2016-09-19 19:58:39 -04:00
}
if ( this . intercomLastSeen && ( this . intercomLastSeen > ( currentCustom [ 'intercom_lastSeen' ] || 0 ) ) ) {
putData [ 'custom.intercom_lastSeen' ] = this . intercomLastSeen ;
}
if ( this . intercomSessionCount && ( this . intercomSessionCount > ( currentCustom [ 'intercom_sessionCount' ] || 0 ) ) ) {
putData [ 'custom.intercom_sessionCount' ] = this . intercomSessionCount ;
2016-03-31 12:23:33 -04:00
}
for ( const field of customFieldsToRemove ) {
if ( currentCustom [ field ] ) {
putData [ ` custom. ${ field } ` ] = null ;
2016-03-14 12:35:54 -04:00
}
}
2016-03-31 12:23:33 -04:00
if ( Object . keys ( putData ) . length > 0 ) {
putData [ ` custom.lastUpdated ` ] = new Date ( ) ;
}
return putData ;
}
2016-04-08 00:23:39 -04:00
getLeadOrigin ( ) {
2016-09-19 19:58:39 -04:00
const props = this . trialRequest . properties ;
switch ( props . siteOrigin ) {
case 'create teacher' :
return 'Create Teacher' ;
case 'convert teacher' :
return 'Convert Teacher' ;
2016-04-08 00:23:39 -04:00
}
return 'Demo Request' ;
}
2016-09-19 19:58:39 -04:00
getContactPostData ( existingLead ) {
const data = {
emails : [ { email : this . email } ] ,
name : this . name
}
2016-03-31 12:23:33 -04:00
if ( existingLead ) {
2016-09-19 19:58:39 -04:00
data . lead _id = existingLead . id ;
}
const props = this . trialRequest . properties ;
if ( props . nces _phone ) {
data . phones = [ { phone : props . nces _phone } ] ;
}
else if ( props . phoneNumber ) {
data . phones = [ { phone : props . phoneNumber } ] ;
2016-03-31 12:23:33 -04:00
}
2016-09-19 19:58:39 -04:00
if ( props . role ) {
data . title = props . role ;
}
else if ( this . user && this . user . role ) {
data . title = this . user . role ;
}
return data ;
}
getNotePostData ( currentNotes ) {
// Post activity notes for each contact
for ( const note of currentNotes || [ ] ) {
if ( note . note . indexOf ( this . email ) >= 0 ) {
return [ ] ;
2016-03-31 12:23:33 -04:00
}
2016-09-19 19:58:39 -04:00
}
let noteData = "" ;
if ( this . trialRequest . properties ) {
const props = this . trialRequest . properties ;
if ( props . name ) {
noteData += ` ${ props . name } \n ` ;
2016-03-31 12:23:33 -04:00
}
2016-09-19 19:58:39 -04:00
if ( props . email ) {
noteData += ` demo_email: ${ props . email . toLowerCase ( ) } \n ` ;
2016-03-31 12:23:33 -04:00
}
2016-09-19 19:58:39 -04:00
if ( this . trialRequest . created ) {
noteData += ` demo_request: ${ this . trialRequest . created } \n ` ;
2016-03-31 12:23:33 -04:00
}
2016-09-19 19:58:39 -04:00
if ( props . educationLevel ) {
noteData += ` demo_educationLevel: ${ props . educationLevel . join ( ', ' ) } \n ` ;
2016-03-31 12:23:33 -04:00
}
2016-09-19 19:58:39 -04:00
for ( const prop in props ) {
if ( [ 'email' , 'educationLevel' , 'created' ] . indexOf ( prop ) >= 0 ) continue ;
noteData += ` demo_ ${ prop } : ${ props [ prop ] } \n ` ;
2016-03-31 12:23:33 -04:00
}
}
2016-09-19 19:58:39 -04:00
if ( this . intercomUrl ) noteData += ` intercom_url: ${ this . intercomUrl } \n ` ;
if ( this . intercomLastSeen ) noteData += ` intercom_lastSeen: ${ this . intercomLastSeen } \n ` ;
if ( this . intercomSessionCount ) noteData += ` intercom_sessionCount: ${ this . intercomSessionCount } \n ` ;
if ( this . user ) {
const user = this . user
noteData += ` coco_userID: ${ user . _id } \n ` ;
if ( user . firstName ) noteData += ` coco_firstName: ${ user . firstName } \n ` ;
if ( user . lastName ) noteData += ` coco_lastName: ${ user . lastName } \n ` ;
if ( user . name ) noteData += ` coco_name: ${ user . name } \n ` ;
if ( user . emaillower ) noteData += ` coco_email: ${ user . emailLower } \n ` ;
if ( user . gender ) noteData += ` coco_gender: ${ user . gender } \n ` ;
if ( user . lastLevel ) noteData += ` coco_lastLevel: ${ user . lastLevel } \n ` ;
if ( user . role ) noteData += ` coco_role: ${ user . role } \n ` ;
if ( user . schoolName ) noteData += ` coco_schoolName: ${ user . schoolName } \n ` ;
if ( user . stats && user . stats . gamesCompleted ) noteData += ` coco_gamesCompleted: ${ user . stats . gamesCompleted } \n ` ;
noteData += ` coco_preferredLanguage: ${ user . preferredLanguage || 'en-US' } \n ` ;
}
if ( this . numClassrooms ) {
noteData += ` coco_numClassrooms: ${ this . numClassrooms } \n `
}
if ( this . numStudents ) {
noteData += ` coco_numStudents: ${ this . numStudents } \n `
}
return noteData ;
2016-03-14 12:35:54 -04:00
}
}
2016-04-26 13:31:08 -04:00
// ** Upsert Close.io methods
2016-03-14 12:35:54 -04:00
2016-09-19 19:58:39 -04:00
function updateCloseLead ( cocoContact , closeLead , userApiKeyMap , done ) {
// console.log('DEBUG: updateCloseLead', cocoContact.email, closeLead.id);
const putData = cocoContact . getLeadPutData ( closeLead ) ;
2016-03-31 12:23:33 -04:00
const options = {
2016-09-19 19:58:39 -04:00
uri : ` https:// ${ closeIoApiKey } :X@app.close.io/api/v1/lead/ ${ closeLead . id } / ` ,
2016-03-31 12:23:33 -04:00
body : JSON . stringify ( putData )
} ;
request . put ( options , ( error , response , body ) => {
if ( error ) return done ( error ) ;
const result = JSON . parse ( body ) ;
if ( result . errors || result [ 'field-errors' ] ) {
2016-09-19 19:58:39 -04:00
console . error ( ` Update existing lead PUT error for ${ cocoContact . leadName } ` ) ;
2016-03-31 12:23:33 -04:00
return done ( ) ;
}
2016-03-14 12:35:54 -04:00
2016-09-19 19:58:39 -04:00
// Check for existing contact
const existingContacts = closeLead . contacts || [ ] ;
for ( const contact of existingContacts ) {
const emails = contact . emails || [ ] ;
for ( const email of emails ) {
if ( email . email . toLowerCase ( ) === cocoContact . email ) {
// console.log(`DEBUG: contact ${cocoContact.email} already exists on ${closeLead.id}`);
return done ( ) ;
}
}
2016-03-14 12:35:54 -04:00
}
2016-09-19 19:58:39 -04:00
// Add Close contact
addContact ( cocoContact , closeLead , userApiKeyMap , ( err , results ) => {
2016-03-31 12:23:33 -04:00
if ( err ) return done ( err ) ;
2016-09-19 19:58:39 -04:00
// Add Close note
const url = ` https:// ${ closeIoApiKey } :X@app.close.io/api/v1/activity/note/?lead_id= ${ closeLead . id } ` ;
2016-03-31 12:23:33 -04:00
request . get ( url , ( error , response , body ) => {
if ( error ) return done ( error ) ;
const currentNotes = JSON . parse ( body ) . data ;
2016-09-19 19:58:39 -04:00
addNote ( cocoContact , closeLead , currentNotes , done ) ;
2016-03-31 12:23:33 -04:00
} ) ;
} ) ;
} ) ;
}
2016-09-19 19:58:39 -04:00
function saveNewCloseLead ( cocoContact , userApiKeyMap , done ) {
const postData = cocoContact . getLeadPostData ( ) ;
// console.log(`DEBUG: saveNewCloseLead ${cocoContact.email} ${postData.status}`);
2016-03-31 12:23:33 -04:00
const options = {
uri : ` https:// ${ closeIoApiKey } :X@app.close.io/api/v1/lead/ ` ,
body : JSON . stringify ( postData )
} ;
request . post ( options , ( error , response , body ) => {
if ( error ) return done ( error ) ;
2016-09-19 19:58:39 -04:00
const newCloseLead = JSON . parse ( body ) ;
if ( newCloseLead . errors || newCloseLead [ 'field-errors' ] ) {
console . error ( ` New lead POST error for ${ cocoContact . email } ` ) ;
console . error ( newCloseLead . errors || newCloseLead [ 'field-errors' ] ) ;
2016-03-31 12:23:33 -04:00
return done ( ) ;
2016-03-14 12:35:54 -04:00
}
2016-03-31 12:23:33 -04:00
2016-09-19 19:58:39 -04:00
// Add contact note
addNote ( cocoContact , newCloseLead , null , ( err , results ) => {
2016-04-08 00:23:39 -04:00
if ( err ) return done ( err ) ;
2016-09-19 19:58:39 -04:00
// Send email to new contact
let newContact = null ;
for ( const contact of newCloseLead . contacts ) {
2016-04-08 00:23:39 -04:00
for ( const email of contact . emails ) {
2016-09-19 19:58:39 -04:00
if ( email . email === cocoContact . email ) {
newContact = contact ;
break ;
}
2016-04-08 00:23:39 -04:00
}
2016-09-19 19:58:39 -04:00
if ( newContact ) break ;
2016-04-08 00:23:39 -04:00
}
2016-09-19 19:58:39 -04:00
if ( ! newContact ) {
console . error ( ` ERROR: Could not find contact ${ cocoContact . email } in new lead ${ newCloseLead . id } ` ) ;
return done ( ) ;
}
const countryCode = getCountryCode ( cocoContact . trialRequest . properties . country , [ cocoContact . email ] ) ;
const emailTemplate = getEmailTemplate ( cocoContact . trialRequest . properties . siteOrigin , postData . status , countryCode ) ;
sendMail ( cocoContact . email , newCloseLead , newContact . id , emailTemplate , userApiKeyMap , emailDelayMinutes , done ) ;
2016-03-31 12:23:33 -04:00
} ) ;
} ) ;
}
2016-09-19 19:58:39 -04:00
function createFindExistingLeadFn ( email , existingLeads ) {
2016-04-23 19:47:10 -04:00
return ( done ) => {
// console.log('DEBUG: findEmailLead', email);
2016-09-19 19:58:39 -04:00
const query = ` email_address:" ${ email } " ` ;
2016-04-23 19:47:10 -04:00
const url = ` https:// ${ closeIoApiKey } :X@app.close.io/api/v1/lead/?query= ${ encodeURIComponent ( query ) } ` ;
request . get ( url , ( error , response , body ) => {
if ( error ) return done ( error ) ;
try {
const data = JSON . parse ( body ) ;
if ( data . total _results > 0 ) {
2016-09-19 19:58:39 -04:00
if ( ! existingLeads [ email ] ) existingLeads [ email ] = [ ] ;
2016-04-23 19:47:10 -04:00
for ( const lead of data . data ) {
2016-09-19 19:58:39 -04:00
existingLeads [ email ] . push ( lead ) ;
2016-04-23 19:47:10 -04:00
}
}
return done ( ) ;
} catch ( error ) {
2016-09-19 19:58:39 -04:00
console . log ( ` ERROR: failed to parse email lead search for ${ email } ` ) ;
2016-05-03 18:52:53 -04:00
console . log ( error ) ;
return done ( error ) ;
2016-04-23 19:47:10 -04:00
}
} ) ;
} ;
}
2016-09-19 19:58:39 -04:00
function createUpdateCloseLeadFn ( cocoContact , existingLeads , userApiKeyMap ) {
2016-08-25 13:21:36 -04:00
// New contact lead matching algorithm:
// 1. New contact email exists
// 2. New contact NCES school id exists
// 3. New contact NCES district id and no NCES school id
// 4. New contact school name and no NCES data
// 5. New contact district name and no NCES data
2016-03-31 12:23:33 -04:00
return ( done ) => {
2016-09-19 19:58:39 -04:00
// console.log('DEBUG: createUpdateCloseLeadFn', cocoContact.email);
2016-08-25 13:21:36 -04:00
2016-09-19 19:58:39 -04:00
if ( existingLeads [ cocoContact . email ] ) {
if ( existingLeads [ cocoContact . email ] . length === 1 ) {
// console.log(`DEBUG: Using lead from email lookup: ${cocoContact.email}`);
return updateCloseLead ( cocoContact , existingLeads [ cocoContact . email ] [ 0 ] , userApiKeyMap , done ) ;
2016-08-25 13:21:36 -04:00
}
2016-09-19 19:58:39 -04:00
console . error ( ` ERROR: ${ existingLeads [ cocoContact . email ] . length } email leads found for ${ cocoContact . email } ` ) ;
2016-08-25 13:21:36 -04:00
return done ( ) ;
}
2016-09-19 19:58:39 -04:00
let nces _district _id = null , nces _school _id = null ;
if ( cocoContact . trialRequest . properties . nces _district _id ) {
nces _district _id = cocoContact . trialRequest . properties . nces _district _id ;
}
if ( cocoContact . trialRequest . properties . nces _id ) {
nces _school _id = cocoContact . trialRequest . properties . nces _id ;
2016-08-25 13:21:36 -04:00
}
2016-09-19 19:58:39 -04:00
// console.log(`DEBUG: updateCloseLead district ${nces_district_id} school ${nces_school_id}`);
2016-08-25 13:21:36 -04:00
2016-09-19 19:58:39 -04:00
let query = ` name:" ${ cocoContact . leadName } " ` ;
2016-08-25 13:21:36 -04:00
if ( nces _school _id ) {
query = ` custom.demo_nces_id:" ${ nces _school _id } " ` ;
}
else if ( nces _district _id ) {
query = ` custom.demo_nces_district_id:" ${ nces _district _id } " custom.demo_nces_id:"" ` ;
}
2016-04-22 13:23:40 -04:00
const url = ` https:// ${ closeIoApiKey } :X@app.close.io/api/v1/lead/?query= ${ encodeURIComponent ( query ) } ` ;
2016-03-31 12:23:33 -04:00
request . get ( url , ( error , response , body ) => {
if ( error ) return done ( error ) ;
2016-04-04 21:37:04 -04:00
try {
const data = JSON . parse ( body ) ;
if ( data . total _results > 1 ) {
2016-09-19 19:58:39 -04:00
console . error ( ` ERROR: ${ data . total _results } leads found for ${ cocoContact . email } nces_district_id= ${ nces _district _id } nces_school_id= ${ nces _school _id } ` ) ;
2016-04-04 21:37:04 -04:00
return done ( ) ;
}
2016-08-25 13:21:36 -04:00
if ( data . total _results === 1 ) {
2016-09-19 19:58:39 -04:00
return updateCloseLead ( cocoContact , data . data [ 0 ] , userApiKeyMap , done ) ;
2016-08-25 13:21:36 -04:00
}
2016-09-19 19:58:39 -04:00
return saveNewCloseLead ( cocoContact , userApiKeyMap , done ) ;
2016-04-04 21:37:04 -04:00
} catch ( error ) {
2016-09-19 19:58:39 -04:00
console . log ( ` ERROR: createUpdateCloseLeadFn ${ cocoContact . email } ` ) ;
console . log ( error ) ;
2016-03-31 12:23:33 -04:00
return done ( ) ;
}
} ) ;
} ;
}
2016-09-19 19:58:39 -04:00
function addContact ( cocoContact , closeLead , userApiKeyMap , done ) {
// console.log('DEBUG: addContact', closeLead.id, cocoContact.email);
const postData = cocoContact . getContactPostData ( closeLead ) ;
const options = {
uri : ` https:// ${ closeIoApiKey } :X@app.close.io/api/v1/contact/ ` ,
body : JSON . stringify ( postData )
2016-03-31 12:23:33 -04:00
} ;
2016-09-19 19:58:39 -04:00
request . post ( options , ( error , response , body ) => {
if ( error ) return done ( error ) ;
const newContact = JSON . parse ( body ) ;
if ( newContact . errors || newContact [ 'field-errors' ] ) {
console . error ( ` New Contact POST error for ${ postData . lead _id } ` ) ;
2016-03-31 12:23:33 -04:00
return done ( ) ;
2016-09-19 19:58:39 -04:00
}
const countryCode = getCountryCode ( cocoContact . trialRequest . properties . country , [ cocoContact . email ] ) ;
const emailTemplate = getEmailTemplate ( cocoContact . trialRequest . properties . siteOrigin , closeLead . status _label , countryCode ) ;
sendMail ( cocoContact . email , closeLead , newContact . id , emailTemplate , userApiKeyMap , emailDelayMinutes , done ) ;
} ) ;
2016-03-31 12:23:33 -04:00
}
2016-03-14 12:35:54 -04:00
2016-09-19 19:58:39 -04:00
function addNote ( cocoContact , closeLead , currentNotes , done ) {
// console.log('DEBUG: addNote', cocoContact.email, closeLead.id);
const newNote = cocoContact . getNotePostData ( currentNotes ) ;
const notePostData = {
note : newNote ,
lead _id : closeLead . id
} ;
const options = {
uri : ` https:// ${ closeIoApiKey } :X@app.close.io/api/v1/activity/note/ ` ,
body : JSON . stringify ( notePostData )
2016-04-08 00:23:39 -04:00
} ;
2016-09-19 19:58:39 -04:00
request . post ( options , ( error , response , body ) => {
if ( error ) return done ( error ) ;
const result = JSON . parse ( body ) ;
if ( result . errors || result [ 'field-errors' ] ) {
console . error ( ` New note POST error for ${ closeLead . id } ` ) ;
}
return done ( ) ;
} ) ;
2016-04-08 00:23:39 -04:00
}
2016-09-19 19:58:39 -04:00
function sendMail ( toEmail , closeLead , contactId , template , userApiKeyMap , delayMinutes , done ) {
2016-05-03 18:52:53 -04:00
// console.log('DEBUG: sendMail', toEmail, leadId, contactId, template, emailApiKey, delayMinutes);
2016-04-23 19:47:10 -04:00
2016-09-19 19:58:39 -04:00
let emailApiKey = getEmailApiKey ( closeLead . status _label ) ;
2016-04-23 19:47:10 -04:00
// Check for previously sent email
2016-09-19 19:58:39 -04:00
const url = ` https:// ${ closeIoApiKey } :X@app.close.io/api/v1/activity/email/?lead_id= ${ closeLead . id } ` ;
2016-04-23 19:47:10 -04:00
request . get ( url , ( error , response , body ) => {
2016-04-08 00:23:39 -04:00
if ( error ) return done ( error ) ;
2016-04-23 19:47:10 -04:00
try {
const data = JSON . parse ( body ) ;
for ( const emailData of data . data ) {
2016-09-19 19:58:39 -04:00
emailApiKey = userApiKeyMap [ emailData . user _id ] || emailApiKey ;
2016-04-23 19:47:10 -04:00
if ( ! isSameEmailTemplateType ( emailData . template _id , template ) ) continue ;
for ( const email of emailData . to ) {
if ( email . toLowerCase ( ) === toEmail . toLowerCase ( ) ) {
2016-09-19 19:58:39 -04:00
console . error ( "ERROR: sending duplicate email:" , toEmail , closeLead . id , contactId , template , emailData . contact _id ) ;
2016-04-23 19:47:10 -04:00
return done ( ) ;
}
}
}
}
catch ( err ) {
2016-09-19 19:58:39 -04:00
console . error ( ` ERROR: parsing previous email sent GET for ${ toEmail } ${ closeLead . id } ` ) ;
2016-04-23 19:47:10 -04:00
console . log ( err ) ;
return done ( ) ;
2016-04-08 00:23:39 -04:00
}
2016-04-23 19:47:10 -04:00
// Send mail
const dateScheduled = new Date ( ) ;
2016-05-03 18:52:53 -04:00
dateScheduled . setUTCMinutes ( dateScheduled . getUTCMinutes ( ) + delayMinutes ) ;
2016-04-23 19:47:10 -04:00
const postData = {
to : [ toEmail ] ,
contact _id : contactId ,
2016-09-19 19:58:39 -04:00
lead _id : closeLead . id ,
2016-04-23 19:47:10 -04:00
template _id : template ,
status : 'scheduled' ,
date _scheduled : dateScheduled
} ;
const options = {
2016-05-03 18:52:53 -04:00
uri : ` https:// ${ emailApiKey } :X@app.close.io/api/v1/activity/email/ ` ,
2016-04-23 19:47:10 -04:00
body : JSON . stringify ( postData )
} ;
request . post ( options , ( error , response , body ) => {
if ( error ) return done ( error ) ;
const result = JSON . parse ( body ) ;
if ( result . errors || result [ 'field-errors' ] ) {
2016-09-19 19:58:39 -04:00
const errorMessage = ` Send email POST error for ${ toEmail } ${ closeLead . id } ${ contactId } ` ;
2016-04-23 19:47:10 -04:00
console . error ( errorMessage ) ;
return done ( errorMessage ) ;
}
return done ( ) ;
} ) ;
2016-04-08 00:23:39 -04:00
} ) ;
}
2016-09-19 19:58:39 -04:00
function updateCloseLeads ( cocoContacts , done ) {
2016-06-19 02:21:06 -04:00
const userApiKeyMap = { } ;
let createGetUserFn = ( apiKey ) => {
return ( done ) => {
2016-09-20 08:55:18 -04:00
const url = ` https:// ${ apiKey } :X@app.close.io/api/v1/me/?_fields=id ` ;
2016-06-19 02:21:06 -04:00
request . get ( url , ( error , response , body ) => {
if ( error ) return done ( ) ;
const results = JSON . parse ( body ) ;
userApiKeyMap [ results . id ] = apiKey ;
return done ( ) ;
} ) ;
} ;
}
2016-04-23 19:47:10 -04:00
const tasks = [ ] ;
2016-06-19 02:21:06 -04:00
for ( const closeIoMailApiKey of closeIoMailApiKeys ) {
tasks . push ( createGetUserFn ( closeIoMailApiKey . apiKey ) ) ;
2016-03-31 12:23:33 -04:00
}
2016-09-20 08:55:18 -04:00
tasks . push ( createGetUserFn ( closeIoEuMailApiKey ) ) ;
2016-08-25 13:21:36 -04:00
async . parallelLimit ( tasks , closeParallelLimit , ( err , results ) => {
2016-06-19 02:21:06 -04:00
if ( err ) console . log ( err ) ;
// Lookup existing leads via email to protect against direct lead name querying later
// Querying via lead name is unreliable
const existingLeads = { } ;
2016-04-23 19:47:10 -04:00
const tasks = [ ] ;
2016-09-19 19:58:39 -04:00
for ( const email in cocoContacts ) {
tasks . push ( createFindExistingLeadFn ( email , existingLeads ) ) ;
2016-04-23 19:47:10 -04:00
}
2016-08-25 13:21:36 -04:00
async . parallelLimit ( tasks , closeParallelLimit , ( err , results ) => {
2016-06-19 02:21:06 -04:00
if ( err ) return done ( err ) ;
const tasks = [ ] ;
2016-09-19 19:58:39 -04:00
for ( const email in cocoContacts ) {
tasks . push ( createUpdateCloseLeadFn ( cocoContacts [ email ] , existingLeads , userApiKeyMap ) ) ;
2016-06-19 02:21:06 -04:00
}
2016-09-19 19:58:39 -04:00
async . series ( tasks , done ) ;
2016-04-23 19:47:10 -04:00
} ) ;
2016-03-31 12:23:33 -04:00
} ) ;
2016-03-14 12:35:54 -04:00
}