Use IBM Tone Analyzer to automate your sales pipeline

Sales Automation


Sales automation is a process of automating everyday time consuming manual tasks which your sales team perform on a daily basis, goal is always to save valuable time of your sales team to be more efficient generating more revenue with same resources.


Our Sales Pipeline Bottleneck

In our weekly audit report

Our sales manager identified we are spending a lot of manual resources on classifying leads after an email campaign

Whenever we launch an email campaign our sales staff have to put extra hours for next 3 days. Primary reason for this is people tend to response within 1 day after they receive any kind of email, what this means is we have a lot of work to do in very short amount of time. Our sales team access if a person is interested in our product once they evaluate the response manually and then we assign lead score to it.

Solution

We sit and thought about the above mentioned problem so we came up with a following solution.

An ideal scenario would allow us to digest a lot of information generated after a large email campaign using automation and efficiently identify sales qualified leads with minimal human intervention possibly in real time. On top of that list needs to be sorted.

Now I know you guys are thinking just sentiment analysis is not enough for this kind of work we need something more, something which tells us about user tone.

IBM Watson Tone Analyzer API to the rescue

IBM Watson is a game changer in the field of AI for businesses and it doesn’t matter whether you are SME or a large corporation you can count on benefiting from their platform and their awesome APIs.
For our problem we have selected to use IBM tone analyzer API to automatically evaluate the interest of customer in our services.

Here is what IBM says about their tone analyzer.

Understand emotions and communication style in text.

By using tone analyzer API we are making sure we are not just considering sentiment but real human emotion

Enough Talk Lets Code

First setup our project skeleton using node express generator

Now we need to identify the email responses being collected in our HubSpot CRM for this we plan to use HubSpot engagement API

After that we have created a incoming email object, email meta object, lead score object and incoming email factory respectively

class CrmIncomingEmail {

    constructor(appId, id, contactId, salesRepId, message, messageHtml, time, possibleLastReply, leadDetails = {}, repDetails = {}) {
        this.appId = appId;
        this.id = id;
        this.contactId = contactId;
        this.salesRepId = salesRepId;
        this.message = message;
        this.messageHtml = messageHtml;
        this.time = time;
        this.possibleLastReply = possibleLastReply;
        this.meta = {};
        this.meta.leadDetail = leadDetails;
        this.meta.repDetails = repDetails;
    }
}

module.exports = CrmIncomingEmail;

Incoming email object holds important information regarding contact and email content.

class EmailMeta{

    constructor(email, firstName, lastName){
        this.email = email;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    static createEmailMetaFromHubspotEngagement(obj){
        return new EmailMeta(obj.email, obj.firstName, obj.lastName)
    }
}

module.exports = EmailMeta;

Email meta is value object used in incoming email object.

class CrmAddLeadScore {

    static async addLeadScore(contactId, score){
        return true;
    }
}

module.exports = CrmAddLeadScore;

CRM lead score will be final output of IBM tone analyzer API.

const request = require('request-promise');
const log = require('../../log');
const CrmIncomingEmail = require('./crmIncomingEmail');
const EmailContentHelpers = require('../emailParsing/emailContentHelpers');
const EmailMeta = require('./emailMeta');

class CrmIncomingEmailFactory {


    static async getRecentIncomingEmails() {

        try {
            const option = {
                uri: `https://api.hubapi.com/engagements/v1/engagements/recent/modified?hapikey=${YOUR_HUBSPOT_API_KEY}&count=200`,
                json: true
            };
            return request.get(option).then((response) => {
                log.debug( "Successfully got engagement data from hubspot api");
                return response['results']
                    .filter(eo => eo.engagement.type == "INCOMING_EMAIL")
                    .filter(eo => EmailContentHelpers.hasErrorsInEmail(eo.metadata.text) === false)
                    .map(
                    eo => new CrmIncomingEmail(
                        eo.engagement.portalId,
                        eo.engagement.uid,
                        eo.associations.contactIds[0],
                        eo.engagement.ownerId,
                        eo.metadata.text ,
                        eo.metadata.html,
                        eo.engagement.timestamp ,
                        EmailContentHelpers.getLastReply(eo.metadata.text),
                        EmailMeta.createEmailMetaFromHubspotEngagement(eo.metadata.from),
                        EmailMeta.createEmailMetaFromHubspotEngagement(eo.metadata.to[0])
                    )
                );
            });
        } catch (err) {
            log.error(err);
            throw err;
        }

    }
}

module.exports = CrmIncomingEmailFactory;

One thing you might have noticed above is we have included email content helper to get last reply in emails this is very important as replies usually include all of the previous email thread.

There is an excellent package node-email-reply-parser

npm install node-email-reply-parser

Also don't forget to add your Hubspot API key into incoming email factory.

Now we want to analyze sentiment against the incoming email using Ibm Watson tone analyzer API, One important thing to note here is tone analyzer helps you identify the significant tones not the actual sentiment, for that we are gonna assign weight to each tone based on our target audience and then normalize the data to range of 1-10.

For this we are are gonna create analyzeText and sentimentScorecalculator respectively.

const ToneAnalyzerV3 = require('watson-developer-cloud/tone-analyzer/v3');
const SentimentScoreCalculator = require('./sentimentScoreCalculator');
const log = require('../../../log');
const util = require('util');
const user = '${YOUR_USER}';
const pass = '${YOUR_PASS}';

class AnalyzeTextWatson {

    static async analyzeText(msg) {

        const toneAnalyzerV3 = new ToneAnalyzerV3({
            version: '2017-09-21',
            username: user,
            password: pass
        });

        let params = {
            'tone_input': {'text': msg },
            'content_type': 'application/json'
        };
        toneAnalyzerV3.tone = util.promisify(toneAnalyzerV3.tone);

        return 1;
        return new Promise((resolve, reject) => {
            toneAnalyzerV3.tone(params, function (error, response) {
                if (error) {
                    console.log(error);
                    log.error('Error, please try again');
                    reject(new Error("Error, please try again"));
                }
                else {
                    let msg = response;
                    console.log(msg.document_tone);
                    if (msg && msg.document_tone.tones && msg.document_tone.tones) {
                        let tones = msg.document_tone.tones;
                        return resolve(SentimentScoreCalculator.calculateScore(tones))
                    } else {
                        log.error('Error, please try again.');
                        reject(new Error("Error, please try again."));
                    }
                }

            });
        });
    }
}

module.exports = AnalyzeTextWatson;


const round = (num) => {
    return Math.round(num * 100) / 100
};
const scoreNormalizer = (x) => {
    const currentRangeMin = -.6;
    const currentRangeMax = .6;
    const rangeMax = 10;
    const rangeMin = 1;
    return  1 + (x- currentRangeMin)*(rangeMax-rangeMin)/(currentRangeMax-currentRangeMin);
};

class SentimentScoreCalculator {

    static calculateScore(toneObject) {

        let sentimentWeight = {
            "anger": -0.6,
            "fear": -0.25,
            "joy": 1,
            "sadness": -0.4,
            "analytical": 0.55,
            "confident": 0.3,
            "tentative": 0.6
        };
        console.log(toneObject);
        const toneWeights = [];
        toneObject.forEach( x => {
            toneWeights.push( x.score * sentimentWeight[x.tone_id])
        });
        if(toneWeights.length !== 0){
            const sentimentScore =  toneWeights.reduce((a, b) => a + b, 0) / toneWeights.length;
            return round(scoreNormalizer(sentimentScore));
        }else{
            return 0;
        }
    }

}

module.exports = SentimentScoreCalculator;

Now moving to the final step we have to create a service which make use of all of the code we have created above and analyze emails.

const IncomingEmailFactory = require('./crmIncomingEmailFactory');
const LeadScoreByEmailContent = require('./leadScoreByEmailContent');
const WatsonSentimentAnalyzer = require('../sentimentAnalysis/ibmWatson/analyzeText');


class ScoreCalculationService{

    static async calculateAndUpdateScore(){
        const incomingEmails = 
		await IncomingEmailFactory.getRecentIncomingEmails();
        return Promise.all(incomingEmails.map( async (ie) => {
            const sentimentScore = 
		await WatsonSentimentAnalyzer.analyzeText(ie.possibleLastReply);
            const leadScore = 
		new LeadScoreByEmailContent(ie.contactId, sentimentScore );
            return { 'incomingEmailObject': ie, 'leadScore' : leadScore};
        }));
    }
}

module.exports = ScoreCalculationService;

References

HubSpot Engagement API
IBM Watson API
IBM Tone Analyzer node SDK
Tone Analyzer API Docs

Conclusion


In retrospect we have only scraped the surface of a very big topic. A lot can be done using IBM Watson API but one important thing we want our readers to take away from this whole article is in this overwhelming race of new AI products hitting market every year don’t lose perspective, First step of solving the problem is always identifying it and we at Carbonteq help you ask right questions about your business.

Please checkout out our Big Data and ERP service page if you are unsure how we can help you.