2016-03-14 12:35:54 -04:00
// Upsert new lead data into Close.io
'use strict' ;
2016-04-08 00:23:39 -04:00
if ( process . argv . length !== 7 ) {
log ( "Usage: node <script> <Close.io general API key> <Close.io mail API key1> <Close.io mail API key2> <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)
// TODO: Find/fix case-sensitive bugs
// TODO: Use generators and promises
2016-04-23 19:47:10 -04:00
// TODO: Reduce response data via _fields param
2016-03-31 12:23:33 -04:00
// Save as custom fields instead of user-specific lead notes
const commonTrialProperties = [ 'organization' , 'city' , 'state' , 'country' ] ;
// 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'
] ;
// Skip these problematic leads
2016-04-08 00:23:39 -04:00
const leadsToSkip = [ '6 sı nı flar' , 'fdsafd' , 'ashtasht' , 'matt+20160404teacher3 school' , 'sdfdsf' , 'ddddd' , 'dsfadsaf' , "Nolan's School of Wonders" ] ;
2016-04-26 13:31:08 -04:00
const demoRequestCloseIoEmailTemplates = [ 'tmpl_s7BZiydyCHOMMeXAcqRZzqn0fOtk0yOFlXSZ412MSGm' , 'tmpl_cGb6m4ssDvqjvYd8UaG6cacvtSXkZY3vj9b9lSmdQrf' ] ;
const createTeacherCloseIoEmailTemplates = [ 'tmpl_i5bQ2dOlMdZTvZil21bhTx44JYoojPbFkciJ0F560mn' , 'tmpl_CEZ9PuE1y4PRvlYiKB5kRbZAQcTIucxDvSeqvtQW57G' ] ;
// 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-04-08 00:23:39 -04:00
const emailDelayMinutes = 27 ;
2016-03-14 12:35:54 -04:00
const scriptStartTime = new Date ( ) ;
const closeIoApiKey = process . argv [ 2 ] ;
2016-04-08 00:23:39 -04:00
const closeIoMailApiKeys = [ process . argv [ 3 ] , process . argv [ 4 ] ] ; // Automatic mails sent as API owners
const intercomAppIdApiKey = process . argv [ 5 ] ;
2016-03-14 12:35:54 -04:00
const intercomAppId = intercomAppIdApiKey . split ( ':' ) [ 0 ] ;
const intercomApiKey = intercomAppIdApiKey . split ( ':' ) [ 1 ] ;
2016-04-08 00:23:39 -04:00
const mongoConnUrl = process . argv [ 6 ] ;
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-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-03-31 12:23:33 -04:00
// log('DEBUG: Finding leads..');
2016-04-26 13:31:08 -04:00
findCocoLeads ( ( err , leads ) => {
2016-03-14 12:35:54 -04:00
if ( err ) {
console . error ( err ) ;
return ;
}
log ( ` Num leads ${ Object . keys ( leads ) . length } ` ) ;
2016-03-31 12:23:33 -04:00
// log('DEBUG: Adding Intercom data..');
2016-03-14 12:35:54 -04:00
addIntercomData ( leads , ( err ) => {
if ( err ) {
console . error ( err ) ;
return ;
}
2016-03-31 12:23:33 -04:00
// log('DEBUG: Updating leads..');
updateLeads ( leads , ( err ) => {
2016-03-14 12:35:54 -04:00
if ( err ) {
console . error ( err ) ;
return ;
}
log ( "Script runtime: " + ( new Date ( ) - scriptStartTime ) ) ;
} ) ;
} ) ;
} ) ;
2016-04-26 13:31:08 -04:00
// ** Utilities
function getInitialLeadStatusViaCountry ( country ) {
if ( /usa|america|united states/ig . test ( country ) ) {
return 'New US Schools Auto Attempt 1' ;
}
}
function getInitialLeadStatusViaEmails ( emails ) {
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 ;
}
}
}
return currentStatus ? currentStatus : closeIoInitialLeadStatuses [ closeIoInitialLeadStatuses . length - 1 ] . status ;
}
function getRandomEmailApiKey ( ) {
if ( closeIoMailApiKeys . length < 0 ) return ;
return closeIoMailApiKeys [ Math . floor ( Math . random ( ) * closeIoMailApiKeys . length ) ] ;
}
function getRandomEmailTemplate ( templates ) {
if ( templates . length < 0 ) return '' ;
return templates [ Math . floor ( Math . random ( ) * templates . length ) ] ;
}
function isSameEmailTemplateType ( template1 , template2 ) {
if ( createTeacherCloseIoEmailTemplates . indexOf ( template1 ) >= 0 && createTeacherCloseIoEmailTemplates . indexOf ( template2 ) >= 0 ) {
return true ;
}
if ( demoRequestCloseIoEmailTemplates . indexOf ( template1 ) >= 0 && demoRequestCloseIoEmailTemplates . indexOf ( template2 ) >= 0 ) {
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-04-26 13:31:08 -04:00
function findCocoLeads ( done ) {
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 ) ;
}
const leads = { } ;
const emailLeadMap = { } ;
const emails = [ ] ;
for ( const trialRequest of trialRequests ) {
if ( ! trialRequest . properties || ! trialRequest . properties . email ) continue ;
const email = trialRequest . properties . email . toLowerCase ( ) ;
emails . push ( email ) ;
const name = trialRequest . properties . organization || trialRequest . properties . name || email ;
if ( ! leads [ name ] ) leads [ name ] = new CocoLead ( name ) ;
leads [ name ] . addTrialRequest ( email , trialRequest ) ;
emailLeadMap [ email ] = leads [ name ] ;
}
// Users for trial requests
const query = { $and : [
{ emailLower : { $in : emails } } ,
{ anonymous : false }
] } ;
db . collection ( 'users' ) . find ( query ) . toArray ( ( err , users ) => {
if ( err ) {
db . close ( ) ;
return done ( err ) ;
}
const userIDs = [ ] ;
const userLeadMap = { } ;
const userEmailMap = { } ;
for ( const user of users ) {
const email = user . emailLower ;
emailLeadMap [ email ] . addUser ( email , user ) ;
userIDs . push ( user . _id ) ;
userLeadMap [ user . _id . valueOf ( ) ] = emailLeadMap [ email ] ;
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 ) {
userLeadMap [ classroom . ownerID . valueOf ( ) ] . addClassroom ( userEmailMap [ classroom . ownerID . valueOf ( ) ] , classroom ) ;
}
db . close ( ) ;
return done ( null , leads ) ;
} ) ;
} ) ;
} ) ;
} ) ;
}
function createAddIntercomDataFn ( cocoLead , email ) {
return ( done ) => {
const options = {
url : ` https://api.intercom.io/users?email= ${ encodeURIComponent ( email ) } ` ,
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 ) ;
cocoLead . addIntercomUser ( email , user ) ;
}
catch ( err ) {
console . log ( err ) ;
console . log ( body ) ;
}
return done ( ) ;
} ) ;
} ;
}
function addIntercomData ( leads , done ) {
const tasks = [ ]
for ( const name in leads ) {
for ( const email in leads [ name ] . contacts ) {
tasks . push ( createAddIntercomDataFn ( leads [ name ] , email ) ) ;
}
}
async . parallel ( tasks , ( err , results ) => {
return done ( err ) ;
} ) ;
}
class CocoLead {
2016-03-31 12:23:33 -04:00
constructor ( name ) {
this . contacts = { } ;
this . custom = { } ;
this . name = name ;
2016-03-14 12:35:54 -04:00
}
2016-03-31 12:23:33 -04:00
addClassroom ( email , classroom ) {
if ( ! this . contacts [ email . toLowerCase ( ) ] ) this . contacts [ email . toLowerCase ( ) ] = { } ;
const contact = this . contacts [ email . toLowerCase ( ) ] ;
contact . numClassrooms = contact . numClassrooms ? contact . numClassrooms + 1 : 1 ;
2016-03-14 12:35:54 -04:00
if ( classroom . members && classroom . members . length ) {
2016-03-31 12:23:33 -04:00
contact . numStudents = contact . numStudents ? contact . numStudents + classroom . members . length : classroom . members . length ;
2016-03-14 12:35:54 -04:00
}
}
2016-03-31 12:23:33 -04:00
addIntercomUser ( email , user ) {
2016-03-14 12:35:54 -04:00
if ( user && user . id ) {
2016-03-31 12:23:33 -04:00
if ( ! this . contacts [ email . toLowerCase ( ) ] ) this . contacts [ email . toLowerCase ( ) ] = { } ;
this . contacts [ email . toLowerCase ( ) ] . intercomUrl = ` https://app.intercom.io/a/apps/ ${ intercomAppId } /users/ ${ user . id } / ` ;
2016-03-14 12:35:54 -04:00
}
}
2016-03-31 12:23:33 -04:00
addTrialRequest ( email , trial ) {
if ( ! this . contacts [ email . toLowerCase ( ) ] ) this . contacts [ email . toLowerCase ( ) ] = { } ;
2016-04-08 00:23:39 -04:00
if ( trial . properties . firstName && trial . properties . lastName ) {
this . contacts [ email . toLowerCase ( ) ] . name = ` ${ trial . properties . firstName } ${ trial . properties . lastName } ` ;
}
else if ( trial . properties . name ) {
this . contacts [ email . toLowerCase ( ) ] . name = trial . properties . name ;
}
2016-03-31 12:23:33 -04:00
this . contacts [ email . toLowerCase ( ) ] . trial = trial ;
}
addUser ( email , user ) {
this . contacts [ email . toLowerCase ( ) ] . user = user ;
}
2016-04-26 13:31:08 -04:00
getInitialLeadStatus ( ) {
for ( const email in this . contacts ) {
const props = this . contacts [ email ] . trial . properties ;
if ( props && props [ 'country' ] ) {
const status = getInitialLeadStatusViaCountry ( props [ 'country' ] ) ;
if ( status ) return status ;
}
}
return getInitialLeadStatusViaEmails ( Object . keys ( this . contacts ) ) ;
}
2016-03-31 12:23:33 -04:00
getLeadPostData ( ) {
const postData = {
display _name : this . name ,
name : this . name ,
2016-04-26 13:31:08 -04:00
status : this . getInitialLeadStatus ( ) ,
2016-03-31 12:23:33 -04:00
contacts : this . getContactsPostData ( ) ,
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
} ;
for ( const email in this . contacts ) {
const props = this . contacts [ email ] . trial . properties ;
if ( props ) {
for ( const prop in props ) {
if ( commonTrialProperties . indexOf ( prop ) >= 0 ) {
postData . custom [ ` demo_ ${ prop } ` ] = props [ prop ] ;
}
}
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-03-31 12:23:33 -04:00
getLeadPutData ( currentLead ) {
// console.log('DEBUG: getLeadPutData', currentLead.name);
const putData = { } ;
const currentCustom = currentLead . 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
}
for ( const email in this . contacts ) {
const props = this . contacts [ email ] . trial . properties ;
if ( props ) {
for ( const prop in props ) {
2016-04-23 19:47:10 -04:00
if ( commonTrialProperties . indexOf ( prop ) >= 0 && currentCustom [ ` demo_ ${ prop } ` ] !== props [ prop ] && currentCustom [ ` demo_ ${ prop } ` ] . indexOf ( props [ prop ] ) < 0 ) {
2016-03-31 12:23:33 -04:00
putData [ ` custom.demo_ ${ prop } ` ] = props [ prop ] ;
}
}
2016-03-14 12:35:54 -04:00
}
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 ( ) {
for ( const email in this . contacts ) {
const props = this . contacts [ email ] . trial . properties ;
switch ( props . siteOrigin ) {
case 'create teacher' :
return 'Create Teacher' ;
case 'convert teacher' :
return 'Convert Teacher' ;
}
}
return 'Demo Request' ;
}
2016-03-31 12:23:33 -04:00
getContactsPostData ( existingLead ) {
const postData = [ ] ;
const existingEmails = { } ;
if ( existingLead ) {
const existingContacts = existingLead . contacts || [ ] ;
for ( const contact of existingContacts ) {
const emails = contact . emails || [ ] ;
for ( const email of emails ) {
existingEmails [ email . email . toLowerCase ( ) ] = true ;
}
}
}
for ( const email in this . contacts ) {
if ( existingEmails [ email ] ) continue ;
const contact = this . contacts [ email ] ;
const data = {
emails : [ { email : email } ] ,
name : contact . name
}
const props = contact . trial . properties ;
if ( props . phoneNumber ) {
data . phones = [ { phone : props . phoneNumber } ] ;
}
if ( props . role ) {
data . title = props . role ;
}
else if ( contact . user || contact . user . role ) {
data . title = contact . user . role ;
}
postData . push ( data ) ;
}
return postData ;
}
getNotesPostData ( currentNotes ) {
// Post activity notes for each contact
function noteExists ( email ) {
if ( currentNotes ) {
for ( const note of currentNotes ) {
if ( note . note . indexOf ( email ) >= 0 ) {
return true ;
}
}
}
return false ;
}
const notes = [ ] ;
for ( const email in this . contacts ) {
if ( ! noteExists ( email ) ) {
const contact = this . contacts [ email ] ;
let noteData = "" ;
const trial = contact . trial
if ( trial . properties ) {
const props = trial . properties ;
if ( props . name ) {
noteData += ` ${ props . name } \n ` ;
}
if ( props . email ) {
noteData += ` demo_email: ${ props . email . toLowerCase ( ) } \n ` ;
}
if ( trial . created ) {
noteData += ` demo_request: ${ trial . created } \n ` ;
}
if ( props . educationLevel ) {
noteData += ` demo_educationLevel: ${ props . educationLevel . join ( ', ' ) } \n ` ;
}
for ( const prop in props ) {
if ( [ 'email' , 'educationLevel' , 'created' ] . indexOf ( prop ) >= 0 || commonTrialProperties . indexOf ( prop ) >= 0 ) continue ;
noteData += ` demo_ ${ prop } : ${ props [ prop ] } \n ` ;
}
}
if ( contact . intercomUrl ) noteData += ` intercom_url: ${ contact . intercomUrl } \n ` ;
if ( contact . user ) {
const user = contact . 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 ( contact . numClassrooms ) {
noteData += ` coco_numClassrooms: ${ contact . numClassrooms } \n `
}
if ( contact . numStudents ) {
noteData += ` coco_numStudents: ${ contact . numStudents } \n `
}
notes . push ( noteData ) ;
}
}
return notes ;
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-03-31 12:23:33 -04:00
function updateExistingLead ( lead , existingLead , done ) {
// console.log('DEBUG: updateExistingLead', existingLead.id);
const putData = lead . getLeadPutData ( existingLead ) ;
const options = {
uri : ` https:// ${ closeIoApiKey } :X@app.close.io/api/v1/lead/ ${ existingLead . id } / ` ,
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' ] ) {
console . error ( ` Update existing lead PUT error for ${ lead . name } ` ) ;
console . error ( body ) ;
// console.log(putData);
return done ( ) ;
}
2016-03-14 12:35:54 -04:00
2016-03-31 12:23:33 -04:00
// Add contacts
const newContacts = lead . getContactsPostData ( existingLead ) ;
const tasks = [ ]
for ( const newContact of newContacts ) {
newContact . lead _id = existingLead . id ;
2016-04-08 00:23:39 -04:00
tasks . push ( createAddContactFn ( newContact , lead , existingLead ) ) ;
2016-03-14 12:35:54 -04:00
}
2016-03-31 12:23:33 -04:00
async . parallel ( tasks , ( err , results ) => {
if ( err ) return done ( err ) ;
// Add notes
const url = ` https:// ${ closeIoApiKey } :X@app.close.io/api/v1/activity/note/?lead_id= ${ existingLead . id } ` ;
request . get ( url , ( error , response , body ) => {
if ( error ) return done ( error ) ;
const currentNotes = JSON . parse ( body ) . data ;
const newNotes = lead . getNotesPostData ( currentNotes ) ;
const tasks = [ ]
for ( const newNote of newNotes ) {
tasks . push ( createAddNoteFn ( existingLead . id , newNote ) ) ;
}
async . parallel ( tasks , ( err , results ) => {
return done ( err ) ;
} ) ;
} ) ;
} ) ;
} ) ;
}
function saveNewLead ( lead , done ) {
// console.log('DEBUG: saveNewLead', lead.name);
const postData = lead . getLeadPostData ( ) ;
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 ) ;
const existingLead = JSON . parse ( body ) ;
if ( existingLead . errors || existingLead [ 'field-errors' ] ) {
console . error ( ` New lead POST error for ${ lead . name } ` ) ;
console . error ( body ) ;
// console.error(JSON.stringify(postData, null, 2));
return done ( ) ;
2016-03-14 12:35:54 -04:00
}
2016-03-31 12:23:33 -04:00
// Add notes
const newNotes = lead . getNotesPostData ( ) ;
const tasks = [ ]
for ( const newNote of newNotes ) {
tasks . push ( createAddNoteFn ( existingLead . id , newNote ) ) ;
2016-03-14 12:35:54 -04:00
}
2016-03-31 12:23:33 -04:00
async . parallel ( tasks , ( err , results ) => {
2016-04-08 00:23:39 -04:00
if ( err ) return done ( err ) ;
// Send emails to new contacts
const tasks = [ ] ;
for ( const contact of existingLead . contacts ) {
for ( const email of contact . emails ) {
if ( [ 'create teacher' , 'convert teacher' ] . indexOf ( lead . contacts [ email . email ] . trial . properties . siteOrigin ) >= 0 ) {
2016-04-26 13:31:08 -04:00
tasks . push ( createSendEmailFn ( email . email , existingLead . id , contact . id , getRandomEmailTemplate ( createTeacherCloseIoEmailTemplates ) ) ) ;
2016-04-08 00:23:39 -04:00
}
else {
2016-04-26 13:31:08 -04:00
tasks . push ( createSendEmailFn ( email . email , existingLead . id , contact . id , getRandomEmailTemplate ( demoRequestCloseIoEmailTemplates ) ) ) ;
2016-04-08 00:23:39 -04:00
}
}
}
async . parallel ( tasks , ( err , results ) => {
return done ( err ) ;
} ) ;
2016-03-31 12:23:33 -04:00
} ) ;
} ) ;
}
2016-04-23 19:47:10 -04:00
function createFindExistingLeadFn ( email , name , existingLeads ) {
return ( done ) => {
// console.log('DEBUG: findEmailLead', email);
const query = ` recipient:" ${ email } " ` ;
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 ) {
if ( ! existingLeads [ name ] ) existingLeads [ name ] = [ ] ;
for ( const lead of data . data ) {
existingLeads [ name ] . push ( lead ) ;
}
}
return done ( ) ;
} catch ( error ) {
// console.log(url);
// console.log(error);
// console.log(body);
return done ( ) ;
}
} ) ;
} ;
}
function createUpdateLeadFn ( lead , existingLeads ) {
2016-03-31 12:23:33 -04:00
return ( done ) => {
// console.log('DEBUG: updateLead', lead.name);
2016-04-22 13:23:40 -04:00
const query = ` name:" ${ lead . name } " ` ;
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 === 0 ) {
2016-04-23 19:47:10 -04:00
if ( existingLeads [ lead . name . toLowerCase ( ) ] ) {
if ( existingLeads [ lead . name . toLowerCase ( ) ] . length === 1 ) {
console . log ( ` DEBUG: Using lead from email lookup: ${ lead . name } ` ) ;
return updateExistingLead ( lead , existingLeads [ lead . name . toLowerCase ( ) ] [ 0 ] , done ) ;
}
console . error ( ` ERROR: ${ existingLeads [ lead . name . toLowerCase ( ) ] . length } email leads found for ${ lead . name } ` ) ;
return done ( ) ;
}
2016-04-04 21:37:04 -04:00
return saveNewLead ( lead , done ) ;
}
if ( data . total _results > 1 ) {
2016-04-23 19:47:10 -04:00
console . error ( ` ERROR: ${ data . total _results } leads found for ${ lead . name } ` ) ;
2016-04-04 21:37:04 -04:00
return done ( ) ;
}
return updateExistingLead ( lead , data . data [ 0 ] , done ) ;
} catch ( error ) {
// console.log(url);
// console.log(error);
// console.log(body);
2016-03-31 12:23:33 -04:00
return done ( ) ;
}
} ) ;
} ;
}
2016-04-08 00:23:39 -04:00
function createAddContactFn ( postData , internalLead , externalLead ) {
2016-03-31 12:23:33 -04:00
return ( done ) => {
// console.log('DEBUG: addContact', postData.lead_id);
2016-03-14 12:35:54 -04:00
const options = {
2016-04-08 00:23:39 -04:00
uri : ` https:// ${ closeIoApiKey } :X@app.close.io/api/v1/contact/ ` ,
2016-03-14 12:35:54 -04:00
body : JSON . stringify ( postData )
} ;
request . post ( options , ( error , response , body ) => {
if ( error ) return done ( error ) ;
2016-04-08 00:23:39 -04:00
const newContact = JSON . parse ( body ) ;
if ( newContact . errors || newContact [ 'field-errors' ] ) {
console . error ( ` New Contact POST error for ${ postData . lead _id } ` ) ;
2016-03-14 12:35:54 -04:00
console . error ( body ) ;
2016-04-08 00:23:39 -04:00
return done ( ) ;
}
// Send emails to new contact
const email = postData . emails [ 0 ] . email ;
if ( [ 'create teacher' , 'convert teacher' ] . indexOf ( internalLead . contacts [ email ] . trial . properties . siteOrigin ) >= 0 ) {
2016-04-26 13:31:08 -04:00
return sendMail ( email , externalLead . id , newContact . id , getRandomEmailTemplate ( createTeacherCloseIoEmailTemplates ) , done ) ;
2016-04-08 00:23:39 -04:00
}
else {
2016-04-26 13:31:08 -04:00
return sendMail ( email , externalLead . id , newContact . id , getRandomEmailTemplate ( demoRequestCloseIoEmailTemplates ) , done ) ;
2016-03-14 12:35:54 -04:00
}
} ) ;
2016-03-31 12:23:33 -04:00
} ;
}
function createAddNoteFn ( leadId , newNote ) {
return ( done ) => {
// console.log('DEBUG: addNote', leadId);
const notePostData = {
note : newNote ,
lead _id : leadId
} ;
const options = {
uri : ` https:// ${ closeIoApiKey } :X@app.close.io/api/v1/activity/note/ ` ,
body : JSON . stringify ( notePostData )
} ;
request . post ( options , ( error , response , body ) => {
2016-03-14 12:35:54 -04:00
if ( error ) return done ( error ) ;
2016-03-31 12:23:33 -04:00
const result = JSON . parse ( body ) ;
if ( result . errors || result [ 'field-errors' ] ) {
console . error ( ` New note POST error for ${ leadId } ` ) ;
console . error ( body ) ;
// console.error(notePostData);
2016-03-14 12:35:54 -04:00
}
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-03-14 12:35:54 -04:00
2016-04-08 00:23:39 -04:00
function createSendEmailFn ( email , leadId , contactId , template ) {
return ( done ) => {
return sendMail ( email , leadId , contactId , template , done ) ;
} ;
}
function sendMail ( toEmail , leadId , contactId , template , done ) {
// console.log('DEBUG: sendMail', toEmail, leadId, contactId, template);
2016-04-23 19:47:10 -04:00
// Check for previously sent email
const url = ` https:// ${ closeIoApiKey } :X@app.close.io/api/v1/activity/email/?lead_id= ${ leadId } ` ;
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 ) {
if ( ! isSameEmailTemplateType ( emailData . template _id , template ) ) continue ;
for ( const email of emailData . to ) {
if ( email . toLowerCase ( ) === toEmail . toLowerCase ( ) ) {
console . error ( "ERROR: sending duplicate email:" , toEmail , leadId , contactId , template , emailData . contact _id ) ;
return done ( ) ;
}
}
}
}
catch ( err ) {
console . log ( err ) ;
console . log ( body ) ;
return done ( ) ;
2016-04-08 00:23:39 -04:00
}
2016-04-23 19:47:10 -04:00
// Send mail
const dateScheduled = new Date ( ) ;
dateScheduled . setUTCMinutes ( dateScheduled . getUTCMinutes ( ) + emailDelayMinutes ) ;
const postData = {
to : [ toEmail ] ,
contact _id : contactId ,
lead _id : leadId ,
template _id : template ,
status : 'scheduled' ,
date _scheduled : dateScheduled
} ;
const options = {
uri : ` https:// ${ getRandomEmailApiKey ( ) } :X@app.close.io/api/v1/activity/email/ ` ,
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' ] ) {
const errorMessage = ` Send email POST error for ${ toEmail } ${ leadId } ${ contactId } ` ;
console . error ( errorMessage ) ;
console . error ( body ) ;
// console.error(postData);
return done ( errorMessage ) ;
}
return done ( ) ;
} ) ;
2016-04-08 00:23:39 -04:00
} ) ;
}
2016-03-31 12:23:33 -04:00
function updateLeads ( leads , done ) {
2016-04-23 19:47:10 -04:00
// Lookup existing leads via email to protect against direct lead name querying later
// Querying via lead name is unreliable
const existingLeads = { } ;
const tasks = [ ] ;
2016-03-31 12:23:33 -04:00
for ( const name in leads ) {
if ( leadsToSkip . indexOf ( name ) >= 0 ) continue ;
2016-04-23 19:47:10 -04:00
for ( const email in leads [ name ] . contacts ) {
tasks . push ( createFindExistingLeadFn ( email . toLowerCase ( ) , name . toLowerCase ( ) , existingLeads ) ) ;
}
2016-03-31 12:23:33 -04:00
}
async . parallel ( tasks , ( err , results ) => {
2016-04-23 19:47:10 -04:00
const tasks = [ ] ;
for ( const name in leads ) {
if ( leadsToSkip . indexOf ( name ) >= 0 ) continue ;
tasks . push ( createUpdateLeadFn ( leads [ name ] , existingLeads ) ) ;
}
async . parallel ( tasks , ( err , results ) => {
return done ( err ) ;
} ) ;
2016-03-31 12:23:33 -04:00
} ) ;
2016-03-14 12:35:54 -04:00
}