Twilio: Responding to user input with multiple distinct messages - ruby-on-rails

I'm working on a twilio-programable-sms chatbot that needs to provide a good chunk of information to a user at the outset of the first conversation. Currently, what we've written is about 562 characters. For some of our users, this gets broken up into chunks of 160 characters that do not necessarily show up in their SMS app in the right order.
To account for this, we're trying to break our message down into 160 character or less distinct messages that each send one-after-the-other.
However, my teammates and I are currently unsure how to accomplish this. Our application is currently written to provide a twiml response for each message that is received from a user. I've been unable to find a way to create a twiml response that indicates a number of consecutive messages, and the theoretical solutions we've come up with feel hacky and flawed.
To demonstrate, currently our code looks like this. As you can see, when a new user sends in the keyword "start" we join 4 messages together in one long text response. However we'd like each message to be sent individually, one after the other, about a second or two apart.
case #body
when "start"
if !!#user
CreateMessage::SubscriptionMessage.triage_subscribable_type(!!#user)
else
[
CreateMessage::AlphaMessage.personalized_welcome(#conversation.from, true),
CreateMessage::SubscriptionMessage.introduce_bcd,
CreateMessage::SubscriptionMessage.for_example,
CreateMessage::SubscriptionMessage.intvite_to_start
].join("\n\n")
end
We'd like to avoid creating a background worker/cron job, if possible - but welcome any and all suggested solutions.

I think your question is more on how to design synchronous(webhook response) vs asynchronous responses/messages. I have not used twiml but the concepts are same.
If you don't want to use a background job, then send fir N-1 messages using API with time delay in between, and the last message as response.
If you are OK with using background jobs, then send 1st message as response and queue a job for sending the remaining messages using API.

Related

Twilio conversation JS SDK - a proper way of fetching user conversations

I'm trying to display all user's conversations sorted by last message creation date and I'm a little bit confused.
I see getSubscribedConversation method in docs (https://media.twiliocdn.com/sdk/js/conversations/releases/1.1.0/docs/Client.html#getSubscribedConversations__anchor) but it says nothing about page size and sorting. It returns paginator so I assume it doesn't return all conversations at once.
On the other hand I see some examples in twilio github projects where conversations are added to the list only by listening for conversationAdded event (which indeed fires even for previously created conversations) but it doesn't seem like a clean solution - if user belongs to 50 conversation then I should handle every single event and rerender the list 50 times?
To sum up, I have following questions:
Does getSubscribedConversation returns all user's conversations at once?
If no, then what is default page size and is it possible to change it (together with sorting)
If getSubscribedConversation return paginator indeed - wouldn't it break if I add conversation from conversationAdded event in the meantime?
I can't answer all your questions but I can give some insight on a couple -
From what I can tell, getSubscribedConversations returns 50 Conversations. I have not found a way to change that limit or sort it (I'm not entirely sure in what order Twilio returns them even).
For a project I'm working on we need Conversations sorted in order of recent message. The way I'm currently dealing with it is by storing the most recent message on an attribute on the Conversation. I also initialize the app by loading all the conversations with a recursive function.
Hope that sheds some light for you.

How to remove Ephemeral Messages

I'm trying to figure out the mechanism to post an ephemeral message to a user and then remove it and replace it with a message visible to all. Similar behavior to giphy in which the Slash Command shows an interactive ephemeral message and creates a channel message once the user decides which gif to send. I'm also curious about updating the ephemeral message. I assume this can be done by the response_url if we use an interactive ephemeral message.
I initially figured I'd just create a ephemeral message using chat.postEphemeral and then call chat.delete on it, but it seems chat.delete and chat.update can't be called on a message created using chat.postEphemeral.
The Slack message guidelines seems to suggest that a multi-step interactive flow should always be handled in an ephemeral way so that other channel user don't see all intermediate messages before the result but I'm having bad luck figuring out how to get rid of the ephemeral when done. Probably just being bad at reading but any help appreciated.
Edit with more details:
The documentation around using response_url and postEphemeral states
As you replace messages using chat.update or the replace_original
option, you cannot change a message's type from ephemeral to
in_channel. Once a message has been issued, it will retain its
visibility quality for life.
The message guidelines suggest:
If a user has initiated an action that has multiple steps, those steps
should be shown as ephemeral messages visible only to that user until
the entire action is complete to avoid cluttering the channel for
everyone.
Presumably, I should be able to create an interaction in which I first send an in_channel interactive message.
When a user initiates an action, I should be able to send them a series of ephemeral messages using the response_url and passing response_type: 'ephemeral' and replace_original: false?
A new ephemeral interactive message created this way will have its own response_url for making edits, right?
Once I am done with the interactive flow via ephemeral messages, I can modify the original interactive message using its original response_url?
Lastly, how do I get rid of the last ephemeral edit? Or do I just change it to something like "Workflow completed" and hope for the best? I'm asking because Slash commands obviously seem to have a way to essentially replace the ephemeral message for an in_channel message and I'm trying to figure this kind of workflow out.
I searched high and low on how to do this and finally came across the answer.
Your ephemeral message must trigger an action, i.e. button click.
Your response to the action must use the following body
{
'response_type': 'ephemeral',
'text': '',
'replace_original': true,
'delete_original': true
}
'delete_original': true is the key here, which as far as I can tell is not mentioned in any of the API guides, however it is present in the API field guide under Top-level message fields
If you wish to change the response_type of your message instead of deleting it, you must do so by first deleting the ephemeral message and then posting the same message with 'response_type': 'in_channel'.
In my use case I wanted to take an ephemeral message and repost it with the exact same message body as an in-channel message. I have not found a way to retrieve the content of your ephemeral message, so the best method I've found is to pass whatever necessary data spawned your ephemeral message in the button's value so that your action handler can read this data and dynamically recreate the message body.
In my case, this was the user input being used to perform a query. On the off chance that data in the database changes between the time the original ephemeral message is posted and the in-channel version is posted they will be different. You may be able to send a JSON string directly through this value field and avoid making additional database calls and running the risk of messages changing when posted to the channel. The character limit of value is 2000 so JSON passing is extremely limited.
Assuming you use the same code to generate this body when initially creating the ephemeral message and also when recreating it in-channel, you should receive the same body and essentially are able to change an ephemeral message to in-channel message.
Some ephemeral messages can be "soft" deleted/replaced but only when posted as part of a message with interactive features like buttons or menus. When a button is clicked or a menu selection made, you have a chance to instruct Slack to either "delete" the original message, or replace it with a new one. These docs detail using responses and response_url to accomplish that.
A message created with chat.postEphemeral that itself has no interactive features can never be explicitly deleted. Once it's delivered, it's like a ghost and will disappear following a restart or refresh.
Answering your bulleted questions in order:
Correct, you essentially start a new chain of interactivity with net new ephemeral message you post to that user
Each interactive message interaction will have its own response URL. The new ephemeral message won't have a response_url you can use until the end user presses a button, selects a menu item, etc.
response_url will eventually expire ("using the response_url, your app can continue interacting with users up to 5 times within 30 minutes of the action invocation.") If the original message is non-ephemeral, using chat.update is a better strategy for longer timelines. With ephemeral messages, it's more of a "do your best" strategy. They'll eventually get cleaned up for the user after a refresh.
I think you have a good handle on what's best. Personally, I think it's easier to kick off a new "in_channel" message by using chat.postMessage instead of as a chain effect directly from a slash command or interaction.
The Kotlin/Java version for this solution using the Bolt API as shown below
import com.slack.api.bolt.handler.builtin.BlockActionHandler
import com.slack.api.bolt.request.builtin.BlockActionRequest
import com.slack.api.app_backend.interactive_components.response.ActionResponse
import com.slack.api.bolt.response.Response
import com.slack.api.bolt.context.builtin.ActionContext
object Handler : BlockActionHandler {
override fun apply(req: BlockActionRequest,
context: ActionContext): Response {
val response = ActionResponse
.builder()
.deleteOriginal(true)
.replaceOriginal(true)
.responseType("ephemeral")
.blocks(listOf())
.text("")
.build()
context.respond(response)
return context.ack()
}
}
If you are using Python and Flask the following code should work when you respond to a button click in the ephemeral message:
from flask import jsonify
response = jsonify({
'response_type': 'ephemeral',
'text': '',
'replace_original': 'true',
'delete_original':'true'
})
return make_response(response, 200)

Customize Twilio's click-to-call, transcribe and collect statistics

I'm using Java but the question is language-agnostic, so I posted it under twilio-PHP tag too.
My application needs to connect two customers: A and B. I want to transcribe the conversation and find out whether one of the parties did not pick up and screened the other to voicemail.
I'm following the steps in the click-to-call-tutorial
However, it looks like the Rest API supports recording but not transcription. I successfully can do:
Map<String, String> params = new HashMap<String, String>();
params.put("From", myTwilioNumber);
params.put("To", customerAPhoneNumber);
params.put("Url", "http://MyHandler.jsp");
params.put("IfMachine", "Hangup");
params.put("Record", "true");
Call call = client.getAccount().getCallFactory().create(params);
which gets the entire conversation recorded, but not transcribed!
As a side note -
params.put("IfMachine", "Hangup");
indeed hangs up, when reaches voicemail, but not before leaving a voicemail with random noise. Looks like Twilio's "probing" the response, and by the time it understands it got to voicemail, background noises have been recorded. Which is terrible user experience. Any advice?
Additionally, my call handling servlet does:
TwiMLResponse twimlResponse = new TwiMLResponse();
Say sayMessage = new Say(
"Hi, customer A, stay on line to speak with customer B?");
twimlResponse.append(sayMessage);
Dial dial = new Dial(customerBPhoneNumber);
twimlResponse.append(dial);
But when I'm looking at the TwiML Verbs , there is no place where I can set params.put("IfMachine", "Continue") . So the field call.getAnsweredBy() is null for the second call. In other words, I cannot know whether conversation between customer A and B ever happened.
Additionally, [TwiML Verb Record] ( https://www.twilio.com/docs/api/twiml/record ) does allow transcription, but if I do
twimlResponse.append(new Record());
it stops the conversation and records one of the customers.
So I cannot direct the REST API to transcribe, and TwiML Verbs does not even record the conversation in a way I want.
Can anyone help?
Thanks.
Twilio developer evangelist here.
I'm afraid that when recording both legs of a call, using the REST API, then transcription is not available. Transcription is only available on single messages recorded by the <Record> verb when the recording is under 2 minutes long.
I'd recommend recording the call and downloading the file to be sent off to a third party transcription service in order to transcribe longer, 2 legged calls like that.
In terms of the answering machine detection, it is an experimental feature and requires Twilio to listen to the first few seconds of a call to work out whether it is a machine or not. A good alternative, which can work as part of your <Dial> verb as well, is to use call screening. This is a good article on the pros and cons of AMD, as well as a good description of using human detection by call screening.
Let me know if this helps at all.

Twilio connection parameters missing "To" parameter

I'm developing a Rails Twilio-based application and want to show to my user the number that the caller is calling (because in my application, a user can be associated with multiple numbers).
Here's the code:
Twilio.Device.incoming (conn) ->
$("#log").text("Receiving call from:" + conn.parameters.From)
$("#log").text("Calling to:" + conn.parameters.To)
ringtone.play()
$('.answer').click ->
ringtone.pause()
# accept the incoming connection and start two-way audio
conn.accept()
However, the conn.parameters object does not have a To parameter as the documentation says.
For now, I can get the number being called only server side, with params["Called"], but that is not what I need.
Any ideas ?
Megan from Twilio here. I'd like to share some of what I learned after recently getting Twilio Client up and running.
The way I imagine your call flow is that a 'user' in your application represents an agent with multiple sales lines connected to Twilio numbers configured to a TwiML app. When a customer or the 'caller' finds one of those numbers and dials it, you want to display to the user-agent which of the Twilio numbers the customer is calling.
In the code above conn.parameters.To likely represents your default_client, if you specified one. But conn.parameters.From should actually display the number the caller is calling, which if I am understanding correctly is the desired behavior.
Hope this helps!

Broadcasting to a subset of subscribers in Atmosphere

What I'm trying to do:
Be able to have users subscribed to a number of different 'chat rooms' and use reverse AJAX / comet to send messages from a chat room to everyone logged into that room. (a bit more complicated but this is a similar use case).
What I'm doing:
Using Grails with JMS and Atmosphere. When a message is sent, I'm using JMS to send the message object which is received by a Grails service which is then broadcasted to the atmosphere URL (i.e. atmosphere/messages).
Obviously JMS is a bit redundant there but I though I could use it to help me filter who should retrieve the message although that doesn't really look it'll work (given that the subscriber is basically a singleton service...).
Anyway, what I need to be able to do is only send out a message to the correct subset of people listening to atmosphere/messages. A RESTful-type URL will be perfect here (i.e. atmosphere/messages/* where * is the room ID) however I have no idea how to do that with Atmosphere.
Any ideas / suggestions on how I can achieve what I want? Nothing is concrete at all here so feel free to suggest almost anything. I've even been thinking (based on the response to another question), for example, if I could do something like send out messages to a Node.js server and have that handle the reverse AJAX / comet part.
If I understand your requirements correctly the following should work (jax-rs + scala code):
1) Everyone who wants to get messages from a chat room registers for it:
#GET
#Path(choose/a/path)
def register(#QueryParam("chatroomId") chatroomId: Broadcaster) {
// alternatively, the Suspend annotation can be used
new SuspendResponse.SuspendResponseBuilder[String]()
.resumeOnBroadcast(false).broadcaster(chatroomId).scope(SCOPE.REQUEST)
.period(suspendTimeout, TimeUnit.MINUTES)
.addListener(new AtmosphereEventsLogger()).build
}
2) To broadcast a message for all the registered users, call the following method:
#POST
#Broadcast
#Path(choose/a/path/{chatroomId})
def broadcast(#PathParam("chatroomId") id: String) {
// first find your broadcaster with the BroadcasterFactory
BroadcasterFactory.getDefault().lookupAll() // or maybe there is a find by id?
broadcaster = ...
broadcaster.broadcast(<your message>)
}
I also recommend reading the atmosphere whitepaper, have a look at the mailing list and at Jeanfrancois Arcand's blog.
Hope that helps.
There is a misunderstaning of the concept of comet. Its just another publish/subscribe implementation. If you have multiple chat-rooms, then you need to have multiple "topics", i.e. multiple channels the user can register to. E.g.:
broadcaster['/atmosphere/chatRoom1'].broadcast('Hello world!')
broadcaster['/atmosphere/chatRoom2'].broadcast('Hello world!')
So I would advance you to creaet multiple channels and do not filter manually the set of users, which should retrieve messages (which is definitely not the way it should be done). You do not need to create anything on the server side on this, since the user will just register for a specific channel and receive messages, which anyone is putting into it.
I would recommend you create an AtmosphereHandler for one URL like /atmosphere/chat-room and then use the AtmosphereResource and bind an BroadcastFilter with it, lets say name it ChatRoomBroadcastFilter.
Whenever a user subscribes to a new chat room, a message would be sent to the server (from the client) telling the server about the subscription. Once subscribed, maintain the list of users <> chat room bindings somewhere on the server.
Whenever a message is broadcasted, broadcast it with the chat room id with it. The in the ChatRoomBroadcastFilter (You probably need to make this a PerRequestBroacastFilter) propagate the message to the user only if the user subscribed to the chat room. I am not sure if this clears it out. If you need code example please mention in the comments. I'll put that but that needs some time so ain't putting it right now ;).

Resources