email2spark bot using O365, MS Flow and Azure Functions

In the early days of Cisco Spark, Cisco built a quick and easy bot for moving email threads into a Spark Space by blind coping an email destination to the standard reply all email. The bot, would then create a space using the email subject as the name and then add everyone from the email thread for you. This was a great feature, but you always had that little voice in your head where you realized that every email you sent to this phantom 3rd party, had all of your email content and contacts. Cisco eventually killed their email to Spark bot due to other security concerns.

At ROVE, we liked the bot so much that we decided to rebuild the same functionality, but this time guard against the security concerns we had. Using a combination of Microsoft tools available on a standard Office365 subscription and borrowing some more advanced server-less functions from Azure, we can piece together a similar bot. This bot uses a mailbox that is ROVE owned so no more worries of a random 3rd party accessing our email threads and we can add logic into the script as needed for more advanced functions. In our case, we simply lock down the feature to ROVE employee use.

Workflow Overview

Step 1: Reply All to an email thread and add the bot’s email address (mail2spark@abc.com) as a BCC.

Step 2: MS Flow tracks the user’s inbox for messages and processes them.

Step 3: Flow checks that the email is in the correct BCC location and replies with an error as needed.

Step 4: Flow converts the email to a json formatted payload and posts the data to our Azure Function.

Step 5: Our Function parses the json payload, checks that the From address meets our criteria.

Step 6: Our Function uses the Spark Bot to create a space basing the space name on the email subject and strips any leading REL or FWD: to make it visually appealing.

Step 7: Our Function adds all email recipients to the space and then leaves the space. Anybody that doesn’t have a Cisco Spark account will get an invite from Spark.

Step 8: Our Function replies to the original email using that formatted response.

Step 9: Flow responds to the Flow request with an HTML formatted response.

Step 10: Flow cleans up by marking the email as read and then moves it to the trash.

Detailed Buildout

Step 1: Make a Spark Bot

Go to developer.ciscospark.com and log in with your Cisco Spark credentials. (You can use a free spark account to manage company bots.)

Go to “My Apps” at the top right and then add a bot.

Building Bots with ROVE - My Apps.png

On the next screen, you will give your bot a name, a Spark address and the opportunity to upload an image.

Building Bots with ROVE - New Bot.png

After you have created your bot, you’ll get some more information. We aren’t too concerned with the Bot ID for this use, but we do need the Bot’s Access Token. This token is used to authenticate the API requests and gives you rights for the bot to do stuff. It’s a username and password all in one, so do not lose it and do not give it out to anyone.

Step 2: Create an Azure Function

Azure Function are server less apps that can be built in an array of different programming languages. Consumption plan’s are billed based on per-second resource consumption and executions. Consumption plan pricing includes a monthly free grant of 1 million requests and 400,000 GB’s of resource consumption per month. So we should be able to fit into the free tier for this use.

Create a new function, select PHP as the language and select all the default options. On the main function screen, you can add in the following script replacing the Spark Token, any withROVE propaganda and bot names as needed. Then click the “Get function URL”  in the top right. We’ll need that later.

Building Bots with ROVE Get function URL.png

<?php

$sparkToken = “<Spark Token Here>”;

function send_to_spark($method,$uri,$data) {

global $sparkToken;

switch ($method) {
case “get”:
$uri = $uri.”?”.$data;
$options = array(
‘http’ => array(
‘header’  => “Authorization: Bearer “.$sparkToken.” \r\nContent-type: application/x-www-form-urlencoded\r\n”,
‘method’  => ‘GET’,
),
);
break;
case “delete”:
$uri = $uri.”/”.$data;
$options = array(
‘http’ => array(
‘header’  => “Authorization: Bearer “.$sparkToken.” \r\nContent-type: application/x-www-form-urlencoded\r\n”,
‘method’  => ‘DELETE’,
),
);
break;
case “post”:
$options = array(
‘http’ => array(
‘header’  => “Authorization: Bearer “.$sparkToken.” \r\nContent-type: application/json\r\n”,
‘method’  => ‘POST’,
‘content’ => json_encode($data),
),
);
break;
}

$context  = stream_context_create($options);
$result = json_decode(file_get_contents(“https://api.ciscospark.com/v1/”.$uri, false, $context));

return $result;
}
$jsonData = json_decode(file_get_contents(getenv(‘req’)));

“//If from is not a withrove account then send error” == “//If from is not an account in your orginization then send error
if ( preg_match(“/@withrove.com/”,$jsonData->from) && ! preg_match(“/rove2spark@withrove.com/”,$jsonData->to) ) {
//Start building email response
$httpResponse = “<html><body>”;

//Create room using the email subject – strip RE: and FWD:
$roomName = preg_replace(“/^[Ff][Ww][Dd]:/”,””,preg_replace(“/^[Rr][Ee]:/”,””,strip_tags($jsonData->subject)));
$sparkData = array(“title”=>$roomName
,”type”=>”group”
);
$createRoom = send_to_spark(“post”,”rooms”,$sparkData);

//Convert to: from: cc: fields to email list and add to room
$sparkContacts = explode(‘;’,$jsonData->to.’;’.$jsonData->from.’;’.$jsonData->cc.’;’);
array_pop($sparkContacts);
foreach ( $sparkContacts as $userEmail ) {
if ( $userEmail != “rove2spark@withrove.com” && $userEmail != “” ){
$sparkData = array(“roomId”=>$createRoom->id
,”personEmail”=>$userEmail
);
$roomMember = @send_to_spark(“post”,”memberships”,$sparkData);
}
}

//send welcome message
$sparkMessage  = “rove2spark is an internal use Bot for moving email threads to Cisco Spark.\n”;
$sparkMessage .= “For use, reply all and BCC rove2spark@withrove.com.\n”;
$sparkData = array(“roomId”=>$createRoom->id
,”text”=>$sparkMessage
);
send_to_spark(“post”,”messages”,$sparkData);

//Add email body to the room
$emailBody = preg_replace(‘/&nbsp;/’,”,preg_replace(“/[\r\n]{1,}/”,”\n”,strip_tags(base64_decode($jsonData->body))));
$sparkData = array(“roomId”=>$createRoom->id
,”text”=>$emailBody
);
send_to_spark(“post”,”messages”,$sparkData);

//Remove bot from room
$getMembership = send_to_spark(“get”,”memberships”,”personEmail=rove2spark@sparkbot.io&roomId=”.$createRoom->id);
$deleteMembership = send_to_spark(“delete”,”memberships”,$getMembership->items[0]->id);

//Build HTML email response
$httpResponse .= “The space \””.$roomName.”\” has been created and members added.  “;
$httpResponse .= “Follow up the conversation on <a href=’https://web.ciscospark.com’>Cisco Spark</a>.<br><br>”;
$httpResponse .= “To use rove2spark, replay-all to an email thread and BCC rove2spark@withrove.com.<br>”;
$httpResponse .= “</body></html>”;
} else {
$httpResponse = “ERROR”;
}
file_put_contents(getenv(‘res’), $httpResponse);

?>

Under the “Integrate” section of the function we want to set the mode to “Standard” and select “POST” as the only available HTTP method.

Building Bots with ROVE_HTTP method.png

Step 3: Build a Mailbox

Nothing fancy is needed here. Just an email box.

Step 4: Create a Flow

Here is an overview of the Flow we’ll be building.

Building Bots with ROVE Create a Flow.png

In the Office365 web portal when logged in as the user go to Flow and then create a new Flow. Next we select what trigger is used to kick off this Flow. In our case when an email arrives.

Step 1 +Step 2 + Step 3

Step 1

Step 2

Step 3

Flow is used to add a basic outline for the email handling. First, we define when our flow is triggered. In our case, we are going to filter anything that hits our user’s inbox, but we can get more specific if needed.

Building Bots with ROVE When a New Email Arrives.png

Next, we create a condition to check that our email bot is not in the To or CC field. If this happens, the bot would continue to build spaces every time someone hit reply all to an email thread which would decrease the willingness to use the bot pretty greatly. If they are not in the BCC field we end the flow by replying to the email explaining how to correctly use the bot and delete the email.

@not(contains(concat(triggerBody()?[‘To’], triggerBody()?[‘CC’]), ‘mail2spark’))

Building Bots with ROVE BCC Email.png

If the email passes that check then we build a HTTP step that triggers a REST API to our Azure Function. We take the email and send it as a json formatted post for our function to process.

Building Bots with ROVE Azure Function.png

To format the body, we use this long confusing string to concatename the different fields together and add formatting. Base64 encoding of the body protects us from any special characters and stuff that might mess with the json formatting.

concat(‘{

“to”:”‘,triggerBody()?[‘To’],'”,

“bcc”:”‘,triggerBody()?[‘BCC’],'”,

“cc”:”‘,triggerBody()?[‘CC’],'”,

“from”:”‘,triggerBody()?[‘From’],'”,

“subject”:”‘,replace(triggerBody()?[‘subject’],'”‘,

“body”:”‘,base64(triggerBody()?[‘Body’]),'”,),

”'”}’)

The Azure Function will reply back with one of two responses. A simple “ERROR” response will tell flow to reply back with a generic email saying that the process failed and explaining how to use the bot. If the room has been built correctly, then the response will be an HTML formatted message that we will use to send as a Reply All to the original email thread.

Building Bots with ROVE Reply All Email Thread.png

At the end of each condition flow, we do some cleanup and mark the message as read and delete the original email which keeps our mailbox nice and clean.

Step 5: Test and Tune

At this point, your bot and email scripts should be functional.