Integrate incoming Twilio SMS to Intercom

I use Twilio often to send automatic text messages, in this case, I was using it to send to clients with a reminder text message for their upcoming webinar. Originally I was having the text message replies give an automatic bounce back and then send me an email with the text message body. The problem with that is I wasn’t able to easily reply to these text messages. Since we already use Intercom chat on our websites I wanted to find a way to integrate replies on the SMS’s to our Intercom chat to allow us to easily respond. To do so I found a repo of someone that came up with a proof of concept how this could be done. The code had been created in 2015 so it no longer worked and lacked some functionality.

It was coded in Ruby in which I have never used until today and it was a good reason to learn it. I started with first modifying the code just to work. As it was written any incoming message would fire a webhook in Twilio that would call the Ruby app and create a new user and conversation in Intercom. When the admin replies the reply would go into a text message back to the user.

What I found orignally was each reply from the user on SMS would create a whole new thread in Intercom which made it really hard to follow. I got around this by first looking for a conversation from the user and if it existed then it would reply to that conversation instead of creating a new one. Another issue I noticed is it would send a SMS to ever user in intercom as long as they had a phone number defined. To get around this I tagged every new user with a SMS tag and that way when we responded we would only send a SMS to a user with that Tag.

Take a look at my code edits in the fork here https://github.com/coreyjansen/intercom-twilio-demo

Installation:

I chose to run this ruby code on a Google Virtual Machine and used a ngrok tunnel to allow it to be accessible from the web over https

Twilio Webhook Setup:

Add your webhook into the Twilio number you want to use under the messaging tab. You can see mine in the image above

Intercom Webhook Setup:

  1. Navigate to the Intercom Developers Hub https://developers.intercom.com/
  2. Create a new Intercom App
  3. Assign your Intercom App to your workspace
  4. Grab your token from the app attached to your workplace and save it, you will need this
  5. Create a webhook like the following

Feel free to critique my code and fork it and make any changes, as I mentioned this is the first time I have used Ruby so I am sure there is alot of best practices that were not followed and I would love to learn how I can improve this!

Continue Reading

Google App Script – Send Email Using Service Account with Domain Wide Delegation

This script allows you to send emails using Google App Script and a domain-wide delegation service account so that the email can come from someone else in your organization rather than just the person who is executing the script. I found this extremely valuable when I am the one setting up automation and triggers but I want them to come from specific people other than myself.

It is required that you create a service account with the proper scopes and to put the credentials from the JSON into the script.

Creating Service Account:

To create a service account with domain wide delegation use the following walkthrough https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority

  • Required scope to add to Domain-wide delegation:
    • https://www.googleapis.com/auth/gmail.send

Download the JSON credentials to get the private key and service acccount email address to put into the script

Required Libraries:

OAuth2

  1. Go to Resources > Libraries
  2. Add a Library 1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF with the latest version
  3. Click Save

Example Code:

 var email = {};  
  email.subject = "Test Subject";
  email.recipient = "[email protected]";
  email.htmlbody = "Hello there, this is an example of how to send a <strong> HTML Email </strong>";
  email.sendername="";
  email.sender = "[email protected]";
  email.sendername = "Corey Jansen"
  email.cc="[email protected]";
  sendEmailGmailAPI(email);

Script Located Here: https://github.com/coreyjansen/Google-App-Script-Send-Email-Service-Account

Continue Reading

Change Template Tracking URL in Google Ads To Include Campaign Name, Medium, MatchType, Keyword, Ad Group… Automatically Through Google Script

Use the following script to change all of your Google Ads Tracking Template to include all the Campaign Name, Ad Group, Medium, MatchType, Keyword Used automatically using Google Script. 

I used this script originally when I was using CallRail and wanted to automatically pull in the Keyword, Campaign Name into the CallRail Report. Instead of sitting there and changing each ad Final URL I used this script. 

function  main()
{
  //{CampaignName} ---> Campaignlevel Template
  //{AdGroupName} ----> AdgroupLevel Template
 

 
// Enter your template with at least one of {CampaignName} or {AdGroupName}
  var TrackingTemplate="{lpurl}?matchtype={matchtype}&amp;network={network}&amp;device={device}&amp;adposition={adposition}&amp;keyword={keyword}&amp;utm_source=google&amp;utm_medium=cpc&amp;utm_adgroup={AdGroupName}&amp;utm_campaign={CampaignName}";  //Example
  
var _CAMPAIGN_CONTAINS="";               //Filter by Campaign name   
var _ADGROUP_CONTAINS="";               //Filter by Adgroup name 
var STATUS="ENABLED";                    //ENABLED, PAUSED


// Hit Preview to see the changes/logs. 


//////////////////////////////////////////////  
if(TrackingTemplate.search("{CampaignName}")>0&amp;&amp;TrackingTemplate.search("{AdGroupName}")==-1)
{
var TempSplit=TrackingTemplate.split("&amp;");
for(var i in TempSplit)
{
 if(TempSplit[i].split("=").indexOf("{CampaignName}")>0)
  {var No=i;
   break;
  }
 }
var Temp=TempSplit[No].split("=");

 var campaignIterator=_CAMPAIGN_CONTAINS==""?AdWordsApp.campaigns().withCondition("Status = "+STATUS).get():AdWordsApp.campaigns().withCondition("Name contains '"+_CAMPAIGN_CONTAINS+"'").withCondition("Status = "+STATUS).get();

if(!campaignIterator.hasNext()){Logger.log("No Campaigns matched with this condition")}
while(campaignIterator.hasNext())
  {
  
  var campaign=campaignIterator.next();
  Temp[1]=campaign.getName(); 
  TempSplit.splice(No,1,Temp.join("="));
  var campaigntemplate=TempSplit.join("&amp;");;
  campaign.urls().setTrackingTemplate(campaigntemplate);
  }
  
}  

if(TrackingTemplate.search("{AdGroupName}")>0)
{
var CampaignCondition=false;  
var TempSplit=TrackingTemplate.split("&amp;");
for(var i in TempSplit)
{
 if(TempSplit[i].split("=").indexOf("{AdGroupName}")>0)
  {var No=i;}
  if(TempSplit[i].split("=").indexOf("{CampaignName}")>0)
  {var Cn=i;CampaignCondition=true;}
}
var Temp=TempSplit[No].split("=");
  
if(_ADGROUP_CONTAINS==""&amp;&amp;_CAMPAIGN_CONTAINS=="")
{var adgroupIterator=AdWordsApp.adGroups().withCondition("Status = "+STATUS).get();}
  else if(_ADGROUP_CONTAINS==""&amp;&amp;_CAMPAIGN_CONTAINS!=="")
var adgroupIterator=AdWordsApp.adGroups().withCondition("Name contains '"+_ADGROUP_CONTAINS+"'").withCondition("Status = "+STATUS).get();
 else if(_ADGROUP_CONTAINS!==""&amp;&amp;_CAMPAIGN_CONTAINS!=="")
var adgroupIterator=AdWordsApp.adGroups().withCondition("CampaignName contains '"+_CAMPAIGN_CONTAINS+"'").withCondition("Name contains '"+_ADGROUP_CONTAINS+"'").withCondition("Status = "+STATUS).get();  
  var adgroupIterator=AdWordsApp.adGroups().withCondition("CampaignName contains '"+_CAMPAIGN_CONTAINS+"'").withCondition("Name contains '"+_ADGROUP_CONTAINS+"'").withCondition("Status = "+STATUS).get();
  if(!adgroupIterator.hasNext()){Logger.log("No Campaigns/Adgroups matched with this condition")}
  if(CampaignCondition==false){
  while(adgroupIterator.hasNext())
  {   
  var adgroup=adgroupIterator.next();
  Temp[1]=adgroup.getName();
  TempSplit.splice(No,1,Temp.join("="));
  var adgrouptemplate=TempSplit.join("&amp;");
    adgroup.urls().setTrackingTemplate(adgrouptemplate);
  }
  } else{
    var TempCamp=TempSplit[Cn].split("=");
    while(adgroupIterator.hasNext())
  {   
  var adgroup=adgroupIterator.next();
  Temp[1]=encodeURIC(adgroup.getName());
  TempCamp[1]=encodeURIC(adgroup.getCampaign().getName()); 
  TempSplit.splice(No,1,Temp.join("="));
  TempSplit.splice(Cn,1,TempCamp.join("="));  
  var adgrouptemplate=TempSplit.join("&amp;");
    adgroup.urls().setTrackingTemplate(adgrouptemplate);
  }
  }
} else {Logger.log("Enter at least one of the {CampaignName} or {AdGroupName}")}    

}    


function encodeURIC( r ) {
return r.replace(/\W+/g, "");
}
Continue Reading

How I Automated My Door To Door Sales Job

Where my Love for Botting Started

I have always had a love for automation and “botting” it started in my early teens playing a game called Tibia. Tibia is a 2D MMORPG video game that my friends and I played all the way from grade 8 to grade 12. I say play but what I actually mean is it took over our lives. I would wake up 2 hours before I go to school just to get some “hunting” in on Tibia. So you can get a good understanding of the game that took over my life here is a quick video of me taking part of a quest in this game.

 

This game was extremely repetitive and I knew there had to be a better way. This is when I was introduced to botting, I started botting at this game and letting my computer level my character up and I love the satisfaction of coming home after school to see how many levels I had gained.

Transitioning to Generating Sales Leads Online

Let’s fast forward 5 years out of high school and I am working a door to door job while going to University for business and computer science. I always wanted to find out how to get more sales and more efficiently. So I did some searching online and ended up finding this online forum in which people who post when they had found “Hot Deals”. I found a section there that a couple of customers had posted their telecom deals on and I had begun messaging the customers letting them know what the current promotions were and that I could help them out directly. Sales started coming in and this was just the start.

6 months goes by and I had transitioned to knocking door to door 50% of the time and the other 50% I was working on the forums. By that time 4 other representatives in my same company from across the company were selling on the forums and now effectively my competition. I started bringing my laptop with me everywhere I went. As soon as I got an email notification on my phone that a new customer had posted I would start up the portable hotspot and boot up my laptop to send the customer a private message with the current offers.

Outsourcing the Bot Creation

This was getting ridiculous and I still wasn’t able to get to every customer before the other reps. This is when I knew there must be another way. I was part of this botting forum and saw the two program Ubot Studio and Win Automation coming up over and over. At this time I was being pretty cheap and didn’t want to fork out the $300 to purchase either of the software and met this guy from the Philipines named Ryan. I ended up paying Ryan $50 CAD to create me a bot to automatically respond to these customers that had posted with what my current offer was. This was awesome but just the start.

I used the bot Ryan had created for about a month until I was out of my home and I noticed the bot started spamming the forums I was on. I panicked and quickly deleted all my posts before anyone else had seen it. At this time I realized it was time, I create the bot myself to ensure this never happened again.

Learning to Create the Bot on my Own

I first started by purchasing UBot Studio as this was the software Ryan had used initially to create me this bot. Ubot Studio was great however it wasn’t stable. The bot wouldn’t last a full 8 hours without having to be restarted. I started playing around with the WinAutomation trial and noticed I was able to get the bot running for 72 hours no problem with no crashes and knew I needed to make the switch.

 

winautomation-direct-link-download1

How the Bot Was Programmed

After about a month of programming WinAutomation it was becoming pretty elaborate. First I was having customers respond in the following format

Customer: New
Current Provider:
City/Province:
Looking for: Cable + Internet + Phone

This allowed me to easily parse out which customers were new, what province they were in and what services they were looking for. My bot would first look to see if they are a new customer if this it true then we will proceed. Now we want to know what city they are in, and then we see what services they are looking for. With all this information the bot will look for the best deal for the customer. These deals were all in txt files sorted in their specific city name folder. These folders were in my DropBox which allowed me to change the promotion at any point.

At this time I was responding to the customers faster than the other reps and I had to continuously filter out my triggers to ensure the bot was accurately understanding what the customer was looking for. If the bot was ever confused it would screenshot the message and put it in a folder. Once a week I would go through this folder and tweak the bot to be able to understand more language.

examplemessage

Following Up With Customers

After the bot would send a private message it will wait 48 business hours and if it didn’t receive a response it would send the customer a message following up asking if they had any other questions and they wanted to sign up. At this time I realized I was getting customers from other cities sending me messages back saying they were helped by their “local rep”. At this time I knew I needed to make myself look local. I had changed my signature to include I was a

Closing More Sales by Making Myself Look “Local”

At this time I knew I needed to make myself look local. The first step was changing my signature to include I was a “National Sales Rep” the second step was I had purchased 4 phone numbers that for each of the provinces that I was selling in. These phone numbers would forward directly to my work phone.  I found an awesome provide for these phone numbers for really cheap from FlyNumber.com. Now I modified my bot to identify the customer’s city and then once it scrapes the city it needs to identify what province that city is in and then uses the corresponding phone number in the footer of my message to make myself look “local”.

Fly Number

Top Of The Sales Board

My sales at this point were in the top 30% of my team the bot was tweaked a few times over the next two years. I ended up embedding a picture of my companies mascot on the bottom of the messages but this image was used more than just to spruce up my message. This picture was actually a PHP script that was stored a unique id for each customer that I was able to use to correlate the view with which private message it was. This script allowed me to identify when people that were actually current customers were just trying to fish me for what the current offers were and also allowed me to identify people trying to use multiple accounts to trick me. This code changes the image depending on that specific customer’s ISP when the customers deal I had sent them was expiring. You can find the code below (don’t judge on the code quality, it was a quick and dirty solution I had created)

Getting Analytics Data on Forum Private Messages

<?php

$imageurl = "blank.gif";
$ip = $_SERVER['REMOTE_ADDR'];
$page = $_SERVER['REQUEST_URI'];
$refer = $_SERVER['HTTP_REFERER'];
$date_time = date("l j F Y  g:ia", time() - date("Z")) ;
$agent = $_SERVER['HTTP_USER_AGENT'];
header("content-type: image/gif");
$font = 'Font.otf';
$image = ImageCreateFromGIF($imageurl);
$text = getAutoBotText();
$ip = $_SERVER['REMOTE_ADDR'];
$details = ip_details($ip);
$isp = $details->org;

    //IF ISP OF CUSTOMEr IS TELECOM
    if (strpos(strtolower($isp), 'Telecom') !== false){
        $color = imagecolorresolve ($image, 0, 0, 0);
        $text[1] = "Refer a Friend";
        $text[2] ="to Telecom";
        $text[3] = "Today!";
        imagettftext($image, 12, 0, 30, 28, $color, $font, $text[1]);
        imagettftext($image, 12, 0, 50, 44, $color, $font, $text[2]);
        imagettftext($image, 12, 0, 50, 60, $color, $font, $text[3]);
    //IF MESSAGE WAS SENT THIS MONTH
    }elseif(strpos(strtolower($page), strtolower(date("M"))) == false) {
        $color = imagecolorresolve ($image, 0, 0, 0);
        $text[1] = "Offer Expired";
        $text[2] ="Send Me a PM";
        $text[3] = "For New Offer";
        imagettftext($image, 12, 0, 23, 28, $color, $font, $text[1]);
        imagettftext($image, 12, 0, 24, 44, $color, $font, $text[2]);
        imagettftext($image, 12, 0, 23, 60, $color, $font, $text[3]);
    //IF THIS IS A FOLLOWUP MSG
    }elseif(strpos(strtolower($page), 'followup') !== false) {
        $color = imagecolorresolve ($image, 0, 0, 0);
        imagettftext($image, 12, 0, 23, 28, $color, $font, $text[1]);
        imagettftext($image, 12, 0, 47, 44, $color, $font, $text[2]);
        imagettftext($image, 12, 0, 54, 60, $color, $font, $text[3]);



    }else {
        //IF MESSAGE WAS NOT SENT THIS MONTH
        if($text[4]>7){
        $color = imagecolorresolve ($image, 0, 0, 0);
        imagettftext($image, 12, 0, 23, 28, $color, $font, $text[1]);
        imagettftext($image, 12, 0, 47, 44, $color, $font, $text[2]);
        imagettftext($image, 12, 0, 54, 60, $color, $font, $text[3]);
        } else {

    $color = imagecolorresolve ($image, 255, 0, 0);
    imagettftext($image, 12, 0, 40, 28, $color, $font, $text[1]);
    imagettftext($image, 12, 0, 47, 44, $color, $font, $text[2]);
    imagettftext($image, 12, 0, 54, 60, $color, $font, $text[3]);

}


    }




imagegif($image);

//DONT SAVE MYSELF TO THE DATABASE
if($ip!="255.255.255.255"){
    savetoDatabase($ip,$page,$refer,$agent,$isp,$text[1],$text[2],$text[3],'botlogger');
}
function getAutoBotText(){
        $curMonth = date('n');
        $curYear  = date('Y');
        if ($curMonth == 12){
            $firstDayNextMonth = mktime(0, 0, 0, 0, 0, $curYear+1);
        }else{
            $firstDayNextMonth = mktime(0, 0, 0, $curMonth+1, 1);
        }
        $daysTilNextMonth = round(($firstDayNextMonth - mktime()) / (24 * 3600));
        if ($daysTilNextMonth>1){
        $text[1] ="Promotions are";
        $text[2] = "Expiring in";
        $text[3] = $daysTilNextMonth . " days!";
        } else {
        $text[1] ="Ahh! Promos";
        $text[2] = "are Expiring";
        $text[3] = "Tomorrow!";
        }
        $text[4] = $daysTilNextMonth;
        return $text;
}
function getBotText(){
    try {
    $db = new PDO('mysql:host=localhost;dbname=database;charset=utf8', 'username', 'password');
    foreach($db->query('SELECT * FROM `bottext`') as $row) {
    $text[1] = $row['text1'];
    $text[2] = $row['text2'];
    $text[3] = $row['text3'];
    }
    } catch(PDOException $ex) {
    echo "An Error occured!"; //user friendly message
    echo($ex->getMessage());
}

return $text;
}
function ip_details($ip) {
    $json = file_get_contents("http://ipinfo.io/{$ip}");
    $details = json_decode($json);
    return $details;
}
//
function savetoDatabase($ip,$page,$refer,$agent,$isp,$text1,$text2,$text3,$database){
///FUNCTION TO STORE EACH VIEW OF MY MESSAGE INTO A DATABASE WITH THE CUSTOMERS IP ADDRESS, ISP, USERAGENT

}
ImageDestroy($image);
exit();
?>

This is how I automated my door to door sales job.

Continue Reading