codecombat/server/lib/closeIO.coffee
Matt Lott 12baae7acd 🐛Fix sales lead assignments
Lead assignments can be incorrect when the user requests licenses
needed via the contact form before the related emails have been added
to the Close.io lead.

We’ll use a custom field for automation sales contact if there’s no
email activity.
2016-09-20 14:59:51 -07:00

182 lines
8.2 KiB
CoffeeScript

config = require '../../server_config'
log = require 'winston'
request = require 'request'
apiKey = config.closeIO?.apiKey
defaultSalesContactUserID = 'user_Fh0uLUkRIKMk2to61ISq8PneyQonuD2i7hes6RhZgDX'
module.exports =
logError: (msg) ->
log.error("Close.io Error: #{msg}")
createSalesLead: (user, email, newLeadData) ->
return @logError('No API key available') unless apiKey
@getLead email, (error, lead) =>
return @logError(JSON.stringify(error)) if error
return if lead
@createLead(user, email, newLeadData)
createLead: (user, email, newLeadData) ->
name = newLeadData.name ? email
postData =
display_name: newLeadData.organization ? name
name: newLeadData.organization ? name
contacts: [{
emails: [{email: email}]
name: name
}]
custom: {}
postData.contacts[0].phones = [phone: newLeadData.phone] if newLeadData.phone
for key, val of newLeadData
continue if key in ['name', 'organization', 'phone']
continue if _.isEmpty(val)
postData.custom[key] = val
postData.custom['userID'] = user.get('_id').valueOf() if user
options =
uri: 'https://' + apiKey + ':X@app.close.io/api/v1/lead/',
body: JSON.stringify(postData)
request.post options, (error, response, body) =>
return @logError(JSON.stringify(error)) if error
getLead: (email, done) ->
uri = 'https://' + apiKey + ':X@app.close.io/api/v1/lead/?query=email_address:' + email
request.get uri, (error, response, body) =>
return done(error) if error
leads = JSON.parse(body)
return done("Unexpected leads format: " + body) unless leads.data?
if leads.data?.length is 1
return done(null, leads.data[0])
else if leads.data?.length > 1
return done('ERROR: multiple leads returned for ' + email + ' ' + leads.data.length)
return done()
getSalesContactEmail: (userEmail, done) ->
# Sales contact email precedence: previous email to contact, previous email to lead, lead custom field, lead status default
try
# NOTE: does not work on + email addresses due to Close.io API bug
uri = "https://#{apiKey}:X@app.close.io/api/v1/lead/?query=email_address:#{userEmail}"
request.get uri, (error, response, body) =>
return done(error) if error
leads = JSON.parse(body)
return done("Unexpected Close leads format: " + body) unless leads.data?
return done("No existing Close.IO lead found for #{userEmail}") unless leads.data?.length > 0
lead = leads.data[0]
uri = "https://#{apiKey}:X@app.close.io/api/v1/activity/?lead_id=#{lead.id}"
request.get uri, (error, response, body) =>
return done(error) if error
activities = JSON.parse(body)
return done("Unexpected activities format: " + body) unless activities.data?
activityForThisContact = null
activityForThisLead = null
for activity in activities.data when activity?._type is 'Email'
continue unless /@codecombat\.(?:com)|(?:nl)/ig.test(activity.sender)
continue if activity.sender.indexOf('brian@codecombat.com') >= 0
continue if activity.sender.indexOf(config.mail.username) >= 0
activityForThisLead ?= activity
for email in activity.to or [] when email?.toLowerCase() is userEmail?.toLowerCase()
activityForThisContact ?= activity
if activityForThisContact
return done(null, activityForThisContact.sender, activityForThisContact.user_id, lead.id)
else if activityForThisLead
return done(null, activityForThisLead.sender, activityForThisLead.user_id, lead.id)
if email = lead.custom?['auto_sales_email']
# Have to lookup Close user Id if email from lead custom field
uri = "https://#{apiKey}:X@app.close.io/api/v1/user/?_fields=id,email"
request.get uri, (error, response, body) =>
return done(error) if error
users = JSON.parse(body)
return done("Unexpected Close users format: " + body) unless users.data?
userID = null
for user in users.data or [] when user.email?.toLowerCase() is email.toLowerCase()
userID = user.id
break
if userID
return done(null, email, userID, lead.id)
else
@logError("No user found for leadID=#{lead.id} user=#{userEmail} auto_sales_email=#{lead.custom?['auto_sales_email']}")
return done(null, config.mail.supportSchools, defaultSalesContactUserID, lead.id)
else
return done(null, config.mail.supportSchools, defaultSalesContactUserID, lead.id)
catch error
log.error("closeIO.getSalesContactEmail Error for #{userEmail}: #{JSON.stringify(error)}")
return done(error)
sendMail: (fromAddress, subject, content, salesContactEmail, leadID, done) ->
# log.info("DEBUG: closeIO.sendMail #{fromAddress} #{subject} #{salesContactEmail} #{leadID}")
matches = salesContactEmail.match(/^[a-zA-Z_]+ <(\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3})>$|(\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3})/i)
salesContactEmail = matches?[1] ? matches?[2] ? config.mail.supportSchools
salesContactEmail = config.mail.supportSchools if salesContactEmail?.indexOf('brian@codecombat.com') >= 0
postData =
to: [salesContactEmail]
sender: config.mail.username
subject: subject
body_text: content
lead_id: leadID
status: 'outbox'
options =
uri: "https://#{apiKey}:X@app.close.io/api/v1/activity/email/"
body: JSON.stringify(postData)
request.post options, (error, response, body) =>
return done(error) if error
result = JSON.parse(body)
if result.errors or result['field-errors']
errorMessage = "Close.io Send email POST error for #{fromAddress} #{JSON.stringify(result.errors)} #{JSON.stringify(result['field-errors'])}"
return done(errorMessage)
return done()
processLicenseRequest: (teacherEmail, userID, leadID, licensesRequested, amount, done) ->
# log.info("DEBUG: closeIO.processLicenseRequest #{teacherEmail} #{userID} #{leadID} #{licensesRequested} #{amount}")
# Update lead with licenses requested
licensesRequested = parseInt(licensesRequested)
putData = 'custom.licensesRequested': licensesRequested
options =
uri: "https://#{apiKey}:X@app.close.io/api/v1/lead/#{leadID}/"
body: JSON.stringify(putData)
request.put options, (error, response, body) =>
return done(error) if error
result = JSON.parse(body)
if result.errors or result['field-errors']
errorMessage = "Update Close.io lead PUT error for #{teacherEmail} #{leadID}"
return done(errorMessage)
# Create call task
postData =
_type: "lead"
lead_id: leadID
assigned_to: userID
text: "Call license inquiry #{teacherEmail}"
is_complete: false
options =
uri: "https://#{apiKey}:X@app.close.io/api/v1/task/"
body: JSON.stringify(postData)
request.post options, (error, response, body) =>
return done(error) if error
result = JSON.parse(body)
if result.errors or result['field-errors']
errorMessage = "Create Close.io call task POST error for #{teacherEmail} #{leadID}"
return done(errorMessage)
# Create opportunity
dateWon = new Date()
dateWon.setUTCMonth(dateWon.getUTCMonth() + 2)
postData =
note: "#{licensesRequested} licenses requested"
confidence: 5
date_won: dateWon.toISOString().substring(0, 10)
lead_id: leadID
status: 'Active'
value: licensesRequested * amount
value_period: "annual"
options =
uri: "https://#{apiKey}:X@app.close.io/api/v1/opportunity/"
body: JSON.stringify(postData)
request.post options, (error, response, body) =>
return done(error) if error
result = JSON.parse(body)
if result.errors or result['field-errors']
errorMessage = "Create Close.io opportunity POST error for #{teacherEmail} #{leadID}"
return done(errorMessage)
return done()