{{announcement.body}}
{{announcement.title}}

Turn Alexa into Your Personal Messaging Assistant

DZone 's Guide to

Turn Alexa into Your Personal Messaging Assistant

Do you want to have an assistant to check for unread SMS messages, read them aloud and let you reply to the sender? Read on!

· IoT Zone ·
Free Resource

Alexa device

Learn more about your Alexa devices!

Do you want to have an assistant to check for unread SMS messages, read them aloud and let you reply to the sender without the need of using your phone or touching a single button on a device? If yes, go on to read this article as we will discuss developing an application to perform such a task.

In this tutorial, we will walk through the necessary steps to develop an Alexa skill that links to your RingCentral account and perform the following tasks.

  1. Ask a user to log in to their RingCentral account.

  2. Fetch the user’s name and phone number.

  3. Fetch unread SMS messages.

  4. Read aloud each SMS message and let the user decide to a) Reply to the sender with a text message. b) Listen to the next message.

  5. Mark an unread message as read after reading the message.

Prerequisites

  • You must have a RingCentral developer account. If you don’t have one, click here to create a free developer account.
  • You must have an AWS developer account. If you don’t have one, click here to create a free account.

Pre-Knowledge Requirements

You must have basic knowledge about how to create an Alexa Skill Kit and AWS Lambda function. If you are new to Alexa Skill development, click here to get started before moving on to read this article.

Note that the code snippets shown in this article are just for illustrations and may not work directly with copy and paste. We recommend you download the entire project from here.

You may also like: Smart Home Tutorial: ESP8266, Alexa, and Amazon Echo

Create a RingCentral App

Log in to your RingCentral developer account and click on the Create App button to create a new app.

RingCentral Developers

Enter the application name and description.

CreateApp - General Settings

Specify the application type and choose the platform type as shown below:

CreateApp - App Type & Platform

Add required permissions and provide the OAuth redirect URI. For now, we can leave the OAuth redirect URI field blank. The redirect URI will be obtained from the Account Linking section below.

CreateApp - OAuth Settings

Finally, click the Create button to complete the app creation process. At the application dashboard, copy the App Key and App Secret credentials and keep them in a safe place so we can use them later.

Sandbox Credentials

If you want to learn more about creating a RingCentral application. Please refer to this getting started document for details.

Create an AWS Lambda Function for Alexa Skill

We assumed that you already know how to create a Lambda function for Alexa skill so we won’t discuss in detailed how to create one in this tutorial. If you don’t know where to start, please refer to this document for detailed instructions.

When creating a Lambda function, provide all the mandatory information and choose Node JS 6.10 for the Runtime option.

You will also need to add the following key/value parameters to the function’s Environment variables.

  • RC_APP_SERVER_URL: https://platform.devtest.ringcentral.com.
  • AppID: the value will be obtained when we create an Alexa Custom Skill in the next section.
  • RC_APP_SECRET: the RingCentral App Secret obtained from the previous section.
  • RC_APP_KEY: the RingCentral App Key obtained from the previous section.

list of Environmental variables

At the Lambda function code section, select “Upload a Zip file” from the Code entry type dropdown list. We will implement the code and upload the files later.

Lambda function code

Create a New Alexa Custom Skill

Log in to your Amazon Developer account and open your skills list. Click on the Add a New Skill button on the upper right corner of the page. On the Create New Skill page, set the Skill Type radio button to the “Custom Interaction Model”. Set the Name to “RingCentral Messaging Skill”, and the Invocation Name to “my assistant”.

Creating a new Alexa skill

Save the app and click Next to move on to the Interaction Model form, then copy the following code block and paste it into the Intent Schema field.

Java




x


 
1
{ "intents": [ { "intent": "GetUnreadTextMessageIntent" }, { "intent": 
2
"ReadTextMessageIntent" }, { "intent": "ReplyTextMessageIntent" }, { "slots": [ 
3
  { "name": "MessageBody", "type": "AMAZON.LITERAL" } ], "intent": 
4
"TextMessageIntent" }, { "intent": "DoneIntent" }, { "intent": 
5
"AMAZON.HelpIntent" }, { "intent": "AMAZON.YesIntent" }, { "intent": 
6
"AMAZON.NoIntent" } ] }



Then define a set of sample utterances. Simply copy the following code block and paste it into the Sample Utterances field.

Java


xxxxxxxxxx
1
 
1
GetUnreadTextMessageIntent get unread message GetUnreadTextMessageIntent get 
2
text message ReadTextMessageIntent next ReadTextMessageIntent next message 
3
ReplyTextMessageIntent reply ReplyTextMessageIntent reply message DoneIntent I'm 
4
"done DoneIntent I am done TextMessageIntent message body {short 
5
"message|MessageBody} TextMessageIntent message body {this is for a long text 
6
"message|MessageBody}
7
 
          

Link Your RingCentral Account

The best way to let each user login to RingCentral with their account credentials is to enable the account linking. To enable the feature, we continue from the step above by selecting the Configuration option from the Alexa skill dashboard and under the Account Linking section, select the Yes radio button option and provide the required information as shown and explained below:

Account linking

Follow the steps below to complete the account linking settings:

  • Copy the Redirect URL and paste it to the OAuth redirect URL of the RingCentral app as explained in the last step of the Create a RingCentral App section. Assumed that the user device is registered in the U.S, the https://pitangui.amazon.com/api/skill/link/XXX URL will be used.
  • Set the Auth Code Grant radio button for the Authorization Grant Type
  • Copy this URI and paste it into the Access Token URI field: https://platform.devtest.ringcentral.com/restapi/oauth/token.
  • Copy your RingCentral app’s AppSecret and paste it into the Client Secret field.

Redirecting URLs

Complete your Alexa Skill creation process by providing publishing information and skills beta testing so you can let other users test the skill.

Also, remember to copy the Alexa skill’s App Id (found from the Skill Information form) and paste it into the Lambda function environment variables as discussed earlier in the Create an AWS Lambda function for Alexa Skill section.

Implement Code for a Lambda Function

From a local machine, create a new project named RC-Alexa-skill.

Note: The complete source code for this skill is available for download from here.

Java




xxxxxxxxxx
1


 
1
$ mkdir rc-alexa-skill 
2
$ cd rc-alexa-skill



Then install the Alexa and the RingCentral Node JS SDKs. We save the SDKs locally so that we can zip them and upload the SDKs’ files to the AWS Lambda server later.

Java




xxxxxxxxxx
1


 
1
$ npm install alexa-sdk  save 
2
$ npm install ringcentral –save



To keep things simple, we create a single file named index.js and then complete the code step by step as discussed in the following section.

Java




xxxxxxxxxx
1


 
1
‘use strict’; const Alexa = require(‘alexa-sdk’); const RC = 
2
  require(‘ringcentral’); var rcsdk = new RC({ server: 
3
  process.env.RC_APP_SERVER_URL, appKey: process.env.RC_APP_KEY, appSecret: 
4
  process.env.RC_APP_SECRET }); var platform = rcsdk.platform(); var 
5
  speech_output = "" var reprompt_text = "" exports.handler = function(event, 
6
  context){ var alexa = Alexa.handler(event, context); alexa.appId = 
7
  process.env.AppID; alexa.registerHandlers(handlers); alexa.execute(); };



From the code above, we import the SDKs and create an instance rcsdk of the RingCentral SDK. We will use the RingCentral app’s AppKey and AppSecret specified in the lambda function’s environment variables.

Important: When you publish your RingCentral app, don’t forget to change the Lambda function’s environment variables with the production server (https://platform.ringcentral.com) and app’s credentials for production!

We also get the platform instance from the SDK, so we can use it to call RingCentral APIs later.

Then we create and export a Lambda function handler. The next step is to define the handler's object and implement functions to handle Alexa’s requests.

Java




xxxxxxxxxx
1


1
var handlers = { 'LaunchRequest': function () { }, 'GetUnreadTextMessageIntent': 
2
                function () { }, 'ReadNextTextMessageIntent': function () { }, 
3
                'ReplyTextMessageIntent': function () { }, 'TextMessageIntent': 
4
                function () { }, 'AMAZON.YesIntent': function () { }, 
5
                'AMAZON.NoIntent': function () { }, 'DoneIntent': function () { 
6
                }, 'AMAZON.HelpIntent': function () { }, 'Unhandled': function 
7
                  () { } };



Let us now implement each intent function and have an explanation for essential codes. Remember that the code snippets in this section may not be completed. For implementation, use the project’s code available from GitHub instead.

Java




xxxxxxxxxx
1


 
1
'LaunchRequest': function () { if (this.event.session.user.accessToken == 
2
                                   undefined) { 
3
  this.emit(':tellWithLinkAccountCard','to start using my assistant skill, 
4
            "please use the companion app to authenticate on RingCentral'); 
5
            }else{ var data = platform.auth().data(); data.token_type = "bearer" 
6
              data.expires_in = 86400 data.refresh_token_expires_in = 86400 
7
              data.access_token = this.event.session.user.accessToken 
8
              platform.auth().setData(data) ... } }



When a user invokes the skill (by saying “Alexa open my assistant”), the LaunchRequest function is called. First, we check if the user has authorized Amazon to request an OAuth access token for our RingCentral app.

If the access token does not exist, we return a “LinkAccount” card, displayed in the Alexa app. The card will contain a link allowing the user to authenticate on login with RingCentral.

If the access token exists, we will set the access token using the platform.auth().setData(data) method so that we can use the platform instance to call RingCentral APIs.

We continue to implement the LaunchIntent function to fetch the user’s name, direct phone number and we will keep them in the session attributes for later usage. You can save the information into e.g. AWS DynamoDB if you want to avoid calling these codes every time the user invokes the skill.

Java




xxxxxxxxxx
1
10


 
1
var thisHandler = this // Retrieve the user's name and extension number from RC 
2
  //account platform.get('/account/~/extension/~/') .then(function(response) { 
3
  //var obj = response.json(); thisHandler.attributes['extNumber'] = 
4
  //obj.extensionNumber thisHandler.attributes['userName'] = obj.name // 
5
  //Retrieve the user's phone number platform.get('/account/~/extension/' + 
6
  //obj.id + '/phone-number') .then(function(response) { var obj = 
7
  //response.json(); var count = obj.records.length for (var record of 
8
  //obj.records){ // check if the user has a direct number if (record.usageType 
9
  //== "DirectNumber"){ thisHandler.attributes['ownPhoneNumber'] = 
10
  //record.phoneNumber.replace("+", "") break; } } // if there is no direct 
11
  //number if (!thisHandler.attributes['ownPhoneNumber']) { speech_output = "Hi 
12
  //" speech_output += thisHandler.attributes['userName'] speech_output += 
13
  //"Unfortunately, your account does not support SMS message." 
14
  //thisHandler.emit(':tell', speech_output) }else{ speech_output = "Hi " 
15
  //speech_output += thisHandler.attributes['userName'] speech_output += ". How 
16
  //can I help you?" reprompt_text = "How can I help you?" 
17
  //thisHandler.emit(':ask', speech_output, reprompt_text) } }) 
18
  //.catch(function(e) { thisHandler.emit(':tell', "Fail to read your account. 
19
  //Please try again.") }); }) .catch(function(e) { thisHandler.emit(':tell', 
20
  //"Fail to read your account. Please try again.") }); }
13
  //"Unfortunately, your account does not support SMS message." thisHandler.emit(':tell', speech_output) }else{ speech_output = "Hi " speech_output += thisHandler.attributes['userName'] speech_output += ". How can I help you?" reprompt_text = "How can I help you?" thisHandler.emit(':ask', speech_output, reprompt_text) } }) .catch(function(e) { thisHandler.emit(':tell', "Fail to read your account. Please try again.") }); }) .catch(function(e) { thisHandler.emit(':tell', "Fail to read your account. Please try again.") }); }
21
 
          



The GetUnreadTextMessageIntent function is called when the user says e.g. “get the unread message”. Inside the function, we implement code to fetch unread messages from the user’s RingCentral account. We define the params variable and specify the messageType, readStatus and the direction parameters to fetch only inbound and unread SMS messages.

Java




xxxxxxxxxx
1
10


 
1
'GetUnreadTextMessageIntent': function () { this.attributes['index'] = -1 
2
  this.attributes['textMsgs'] = [] var params = {} params['messageType'] = 
3
  "SMS" params['readStatus'] = "Unread" params['direction'] = "Inbound" var 
4
  thisHandler = this // Call to fetch SMS messages 
5
  //platform.get('/account/~/extension/~/sms', params) .then(function 
6
  //(response) { var obj =response.json(); var count = obj.records.length if 
7
  //(count > 0){ // iterate the records array to read each message details. // 
8
  //last message first for (var i=count-1; i>=0; i--) { var record = 
9
  //jsonObj.records[i] var message = {} message['id'] = record.id // check if 
10
  //sender name exists in the message if ("name" in record.from){ 
11
  //message['from'] = record.from.name }else{ // sender's name is not defined. 
12
  //Convert the sender's // number to a string with space between each number 
13
  // so Alexa can read the digit instead of the number message['from'] = 
14
  //getNumberAsString(record.from.phoneNumber) } // keep the number so we can 
15
  //reply to the sender if needed message['fromNumber'] = 
16
  //record.from.phoneNumber // store the message body message['subject'] = 
17
  //record.subject // add the message object to the 'textMsgs' array 
18
  //thisHandler.attributes['textMsgs'].push(message) } // emit the
19
  //ReadTextMessageIntent to read the first message 
20
  //thisHandler.emit('ReadTextMessageIntent') }else{ thisHandler.emit(':tell', 
21
  //"You have no unread message."); } }); }
22
 
          



The ReadTextMessageIntent function is called when the user says “next” or “next message”. Inside the function, we implement code to read an unread message from the “textMsgs” array. We also set the message status to read when reading an unread message.

Java




xxxxxxxxxx
1
10


1
'ReadTextMessageIntent': function () { 
2
  // check if there is any message in the array if (!this.attributes['textMsgs'] 
3
  || this.attributes['textMsgs'].length == 0) { return this.emit(':ask', "Please say get unread message to check for new messages.", "How can I help you?"); } var count = this.attributes['textMsgs'].length var index = this.attributes['index'] if (index >= count-1){ 
4
    // no more message return this.emit(':ask', "There is no more unread 
5
    //message. You can say reply, or say get unread message to check for new 
6
    //messages.", "How can I help you?"); } 
7
    // increase the index and retrieve a message object from the array 
8
    //this.attributes['index']++ var msg = this.attributes['textMsgs']
9
    //[this.attributes['index']] var prefix = "" if (this.attributes['index'] 
10
    //== 0){ if (count == 1) prefix = "You have 1 unread message " else prefix 
11
    //= "You have "+count+" unread messages. First message " }else{ if 
12
    //(this.attributes['index'] == count - 1) prefix = "Last message " else { 
13
    //prefix = convertNumtoOrder(this.attributes['index']) prefix += " unread 
14
    //message " } } speech_output = prefix speech_output += "from " + 
15
    //msg['from'] speech_output += ". Message. " + msg['subject'] + ". " if 
16
    //(this.attributes['index'] < count) { speech_output += "You can say reply 
17
    //or next message. " reprompt_text = "You can say reply or next message." 
18
    //}else { speech_output += "You can say reply or I am done." reprompt_text 
19
    //= "How can I help you?" } // call to set this message's status in the 
20
    //server to "read" platform.put('/account/~/extension/~/message-
21
    //store/'+msg['id'], { readStatus: "Read" }) .then(function (response) { // 
22
    //ask Alexa to read the message this.emit(':ask', speech_output, 
23
    //reprompt_text); }) .catch(function(e) { console.log("Failed to set 
24
    //readStatus") console.error(e); }); }
23
    //reprompt_text); }) .catch(function(e) { console.log("Failed to set //readStatus") console.error(e); }); }



The ReplyTextMessageIntent function is called when the user says “reply” or “reply message”. Inside the function, we implement code to confirm the recipient’s phone number and get ready to intake the text message from the user.

Java




xxxxxxxxxx
1
10


1
'ReplyTextMessageIntent': function () { 
2
  // check if there is any message in the message array if 
3
  //(!this.attributes['textMsgs'] || this.attributes['textMsgs'].length == 0) { 
4
  //return this.emit(':ask', "Please say get unread message to check for new 
5
  //messages, then say reply.", "How can I help you?"); } // retrieve the 
6
  //message object from the array var msg = this.attributes['textMsgs']
7
  //[this.attributes['index']] speech_output = "Reply to " + msg['from'] 
8
  //speech_output += ". Now you can say message body, followed by the message 
9
  //you want to send." this.attributes['message'] = "" 
10
  //this.attributes['toNumber'] = msg['fromNumber']; this.emit(':ask', 
11
  //speech_output, speech_output); }



The TextMessageIntent function is called when the user says “message body” and speak out the words to be sent. Inside the function, we ask Alexa to repeat the user’s text message to confirm if Alexa heard all the words correctly. We then wait for the user to say “yes” to send the message or to say “no” to cancel the action.

Java




xxxxxxxxxx
1


1
'TextMessageIntent': function () { var intent = this.event.request.intent; var 
2
  message = intent.slots.MessageBody.value this.attributes['message'] = message 
3
  speech_output = "I repeat your message. " speech_output += message + ". Do you 
4
  want to send it now?" reprompt_text = "Say yes to send the message or say no 
5
  to cancel." this.emit(':ask', speech_output, reprompt_text); }
6
 
          



The AMAZON.YesIntent function is called when the user says “yes”. Inside the function, we check the values of the recipient’s phone number and the text message before sending the message.

Java




x
10


1
AMAZON.YesIntent': function () { // check if we have the phone number to reply 
2
  a message // check also if we've captured the text message if 
3
  (this.attributes['toNumber']){ if (this.attributes['message']) { var 
4
  thisHandler = this platform.post('/account/~/extension/~/sms', { from: {
5
    'phoneNumber': this.attributes['ownPhoneNumber']}, to: [{'phoneNumber': 
6
this.attributes['toNumber']}], text: this.attributes['message'] }) 
7
  .then(function (response) { speech_output = "Message is sent. " var count = 
8
    thisHandler.attributes['textMsgs'].length if (
9
    thisHandler.attributes['index'] < count - 1) { speech_output += "You can 
10
    say next to listen to the next message" reprompt_text = "You can say next 
11
    to listen to the next message" }else{ speech_output += "No more unread 
12
    message. You can say get message to check for new unread messages." 
13
      reprompt_text = "How can I help you?" } thisHandler.emit(':ask', 
14
      speech_output, 
15
      reprompt_text); }) .catch(function(e) { console.error(e); 
16
      thisHandler.emit(':ask', "Failed to send message. Please try again", "Say 
17
                       message body, followed by the text message you want to 
18
                       send."); }); }else{ // no message is missing, prompt the 
19
                       user to say the message speech_output = "Say message 
20
                       body, followed by the text message you want to send." 
21
                       this.emit(':ask', speech_output, speech_output); } 
22
                       }else{ speech_output = 'Sorry, I don\'t understand what 
23
                         you want me to do. Please say help to hear what you 
24
                           can say.'; 
25
this.response.speak(speech_output).listen(speech_output); 
26
                             this.emit(':responseReady'); } }
26
this.response.speak(speech_output).listen(speech_output); this.emit(':responseReady'); } }
27
 
          



The AMAZON.NoIntent function is called when the user says “no”. Inside the function, we check if the user cancels the action because of the reply message was taken incorrectly. If so, we ask the user to provide the message body again.

Java




xxxxxxxxxx
1


1
AMAZON.NoIntent': function () { if (this.attributes['message'] && 
2
  this.attributes['message'].length > 0){ speech_output = "If the message is 
3
  incorrect, you can say message body, followed by the text message you want to 
4
  send." }else{ speech_output = 'How can I help you?' } this.emit(':ask', 
5
  speech_output, 'How can I help you?') }



The DoneIntent function is called when the user says “I am done”. Inside the function, we say goodbye to the user and terminate the session. We also implement AMAZON.HelpIntent function to tell the user how to use the assistant skill. Finally, we implement the Unhandled function to handle unexpected commands.

Java




xxxxxxxxxx
1


1
'DoneIntent': function () { this.emit(':tell', 'Good bye'); }, 
2
'AMAZON.HelpIntent': function () { speech_output = 'Say get unread message to 
3
  fetch new unread text messages.' reprompt_text = 'How can I help you?' 
4
  this.emit(':ask', speech_output, reprompt_text); }, 'Unhandled': function () { 
5
  speech_output = 'Sorry, I don\'t understand what you want me to do. Please say 
6
    help to hear what you can say.'; 
7
    this.response.speak(speech_output).listen(speech_output); 
8
  this.emit(':responseReady'); }
9
 
          



When you are done with the code, select the index.js file and the node_modules folder to compress a .zip file then upload the compressed file to your AWS Lambda project as described in the Create an AWS Lambda Function for Alexa Skill section.

Congratulations! You have just completed the implementation of a simple Alexa skill for RingCentral. There are many more features you can add to the skill to enhance both usability and functionality.

For example, implement handler states and using dialog interface, and add new intents to check for voicemails, playback voicemails, or to make a ring-out call etcetera. I will leave that to your innovation and imagination to make your messaging assistant more useful.


Further Reading

Developing Location-Aware Alexa Skills

Create Skills for Echo Devices With a Screen

App Integration With Alexa

Topics:
alexa app development ,messaging ,node js ,alexa skill ,iot

Published at DZone with permission of Paco Vu . See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}