diff --git a/scripts/updateCloseIoLeads.js b/scripts/updateCloseIoLeads.js index 72cacda50..2baf78525 100644 --- a/scripts/updateCloseIoLeads.js +++ b/scripts/updateCloseIoLeads.js @@ -11,6 +11,7 @@ if (process.argv.length !== 7) { // TODO: Update notes with new data (e.g. coco user or intercom url) // TODO: Find/fix case-sensitive bugs // TODO: Use generators and promises +// TODO: Reduce response data via _fields param // Save as custom fields instead of user-specific lead notes const commonTrialProperties = ['organization', 'city', 'state', 'country']; @@ -139,7 +140,7 @@ class Lead { const props = this.contacts[email].trial.properties; if (props) { for (const prop in props) { - if (commonTrialProperties.indexOf(prop) >= 0 && currentCustom[`demo_${prop}`] !== props[prop]) { + if (commonTrialProperties.indexOf(prop) >= 0 && currentCustom[`demo_${prop}`] !== props[prop] && currentCustom[`demo_${prop}`].indexOf(props[prop]) < 0) { putData[`custom.demo_${prop}`] = props[prop]; } } @@ -458,7 +459,33 @@ function saveNewLead(lead, done) { }); } -function createUpdateLeadFn(lead) { +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) { return (done) => { // console.log('DEBUG: updateLead', lead.name); const query = `name:"${lead.name}"`; @@ -468,10 +495,18 @@ function createUpdateLeadFn(lead) { try { const data = JSON.parse(body); if (data.total_results === 0) { + 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(); + } return saveNewLead(lead, done); } if (data.total_results > 1) { - // console.error(`${data.total_results} leads found for ${lead.name}`); + console.error(`ERROR: ${data.total_results} leads found for ${lead.name}`); return done(); } return updateExistingLead(lead, data.data[0], done); @@ -542,6 +577,16 @@ function getRandomEmailTemplate(templates) { return templates[Math.floor(Math.random() * templates.length)]; } +function isSameEmailTemplateType(template1, template2) { + if (createTeacherEmailTemplates.indexOf(template1) >= 0 && createTeacherEmailTemplates.indexOf(template2) >= 0) { + return true; + } + if (demoRequestEmailTemplates.indexOf(template1) >= 0 && demoRequestEmailTemplates.indexOf(template2) >= 0) { + return true; + } + return false; +} + function getRandomEmailApiKey() { if (closeIoMailApiKeys.length < 0) return; return closeIoMailApiKeys[Math.floor(Math.random() * closeIoMailApiKeys.length)]; @@ -555,42 +600,79 @@ function createSendEmailFn(email, leadId, contactId, template) { function sendMail(toEmail, leadId, contactId, template, done) { // console.log('DEBUG: sendMail', toEmail, leadId, contactId, template); - 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) => { + + // 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) => { 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); + 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(); + } + } + } } - return done(); + catch (err) { + console.log(err); + console.log(body); + return done(); + } + + // 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(); + }); }); } function updateLeads(leads, done) { - const tasks = [] + // Lookup existing leads via email to protect against direct lead name querying later + // Querying via lead name is unreliable + const existingLeads = {}; + const tasks = []; for (const name in leads) { if (leadsToSkip.indexOf(name) >= 0) continue; - tasks.push(createUpdateLeadFn(leads[name])); + for (const email in leads[name].contacts) { + tasks.push(createFindExistingLeadFn(email.toLowerCase(), name.toLowerCase(), existingLeads)); + } } async.parallel(tasks, (err, results) => { - return done(err); + 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); + }); }); }