I'm a Twilio newbie so please be gentle.
I'm creating an Autopilot bot to be used via Alexa that in in some circumstances needs to transfer the voice to a telephone number. I'm using my mobile as the destination for testing. I have written a Function to transfer the call which then calls my mobile. All good so far, but as soon as the call is made the Twilio session ends - the call is received and I hear on my mobile "I'm sorry an application error has occurred". I'm guessing I'm missing parameter to connect the voice session to the call. Where have I gone wrong?
Thanks in advance.
Task:
{
"actions": [
{
"say": "Connecting you"
},
{
"handoff": {
"method": "POST",
"channel": "voice",
"uri": "https://qwerty.twil.io/call-me"
}
}
]
}
Function code:
exports.handler = function(context, event, callback) {
// Get an initialized Twilio API client
const client = context.getTwilioClient();
// Make a new phone call, using our first function
// to provide the TwiML for the call
client.calls.create({
url: 'https://' + context.DOMAIN_NAME + '/voice',
to: '+44MyMobileNumber',
from: '+44MyTwilioNumber'
}, function(err, result) {
console.log('New phone call started...');
console.log(result);
// End our function
callback();
});
};
The answer was to Handoff the call in Autopilot to a Twiml Bin that makes the call.
Related
Current we send one-way SMS via MS Flow and Twilio which works fine. I have been exploring how to handle incoming SMS, so I followed a guide and managed to utilise Sendgrid to forward incoming SMS to my e-mail address which also works.
However, I am looking to have the original sender receive the SMS via e-mail. I can give each staff member their own phone number which would define each individual but I need a way of Twilio or Sendgrid doing a lookup prior to sending the reply e-mail so it knows where to send it i.e.
User 1 = 01234455678,
User 2 = 01234543245,
User 3 = 06546546445,...etc.
I guess I could re-create the same process for each number but it would require lots of Twilio Functions and Variables which doesn't seem like a great way to accomplish this?
Sorry, I a not much of a coder and try to use on-line guides and forums where I can.
Many Thanks,
JP
You can try something like this, hosting the mapping on Twilio Assets as a Private asset but you could also pull this information into Studio via the HTTP Request Widget if you hosted it on an external server (a bit more advanced). In my case I called my file mapping.json which has the format:
[
{
"name": "John Doe",
"phone": "+14075551212",
"email": "jdoe#example.com"
},
{
"name": "Susan Doe",
"phone": "+19545551212",
"email": "sdoe#example.com"
},
{
"name": "Nadia Doe",
"phone": "+14705551212",
"email": "ndoe#example.com"
},
{
"name": "Carl Doe",
"phone": "+18025551212",
"email": "cdoe#example.com"
}
]
Then you would use the Run Function widget and send in 3 key:value pairs (Function Parameters):
From - {{trigger.message.From}}
To - {{trigger.message.To}}
Body - {{trigger.message.Body}}
Your Twilio Function would then consume these parameters and the contents of the private asset to handle the mapping. Make sure to configure your Twilio Functions environment with the Sendgrid NPM package, #sendgrid/mail version 7.0.1 and you configure the two Sendgrid specific environmental variables below with their respective values (accessed via the context object in the JavaScript):
SENDGRID_API_KEY
FROM_EMAIL_ADDRESS
const fs = require('fs');
const sgMail = require('#sendgrid/mail');
exports.handler = function(context, event, callback) {
let from = event.From;
let to = event.To;
let body = event.Body;
let fileName = 'mapping.json';
let file = Runtime.getAssets()[fileName].path;
let text = fs.readFileSync(file);
let mappings = JSON.parse(text);
// Filter array to match to number
let result = mappings.filter(record => record.phone === to);
if (result.length) {
sgMail.setApiKey(context.SENDGRID_API_KEY);
// Define message params
const msg = {
to: result[0].email,
from: context.FROM_EMAIL_ADDRESS,
text: body,
subject: `New SMS from: ${from}`,
};
// Send message
sgMail.send(msg)
.then(response => {
console.log("Success.");
callback();
})
.catch(err => {
console.log("Not Success.");
callback(err);
});
} else {
console.log("** NO MATCH **");
callback();
}
};
Let me know how it goes.
I'm not a programmer so please forgive me. But I've spent hours and hours of research on the topic of collecting information with Twilio AutoPilot and posted that data to Airtable, which I will then have Zapier do some things with that data. I finally had a breakthrough today and am now able to post data from a call or text to Airtable. The only way I got the ending to work was to send the call or text to Studio to finish up the call. Everything seems to work from the end user standpoint, but I'm getting an error 90100 from Twilio. I'm sure I'm just missing one line of code for this to work, and I'm at the end of my rope.
{
"actions": [
{
"say": "Okay lets get you a new appointment. I just need you to answer a few questions."
},
{
"collect": {
"name": "member",
"questions": [
{
"question": "Please tell me your first name.",
"name": "name",
"type": "Twilio.FIRST_NAME"
},
{
"question": "Thanks, and what is your email address?",
"name": "email",
"type": "Twilio.EMAIL"
}
],
"on_complete": {
"redirect": "task://complete_booking"
}
}
}
]
}
Then i have another task setup to redirect to the Twilio Function. This is probably overkill, but it's what I found in research.
{
"actions": [
{
"redirect": {
"method": "POST",
"uri": "https://TWILIO_FUNCTION_URL/atable_post"
}
}
]
}
Then the function is as follows. Mind you, this is posting correctly to airtable.
exports.handler = function(context, event, callback) {
let memory = JSON.parse(event.Memory);
let name = memory.twilio.collected_data.member.answers.name.answer;
let email = memory.twilio.collected_data.member.answers.email.answer;
console.log(memory);
let member = {
name : memory.twilio.collected_data.member.answers.name.answer,
email : memory.twilio.collected_data.member.answers.email.answer,
date : Date.now()
};
var Airtable = require("airtable");
var base = new Airtable({apikey: context.AIRTABLE_API_KEY}).base("AIRTABLE_ID");
base("Members1").create(member, function(err, record) {
if (err) { console.error(err); return; }
console.log(record.getId());
callback(null, member);
});
};
The call hung up at this point, so I redirected it to a Studio Flow, which does work and the call finishes with the response I'm give it before ending the call. Again, everything is working fine, but I get the following error from twilio, and I have no idea how to resolve it.
Invalid Autopilot Actions JSON: Invalid Autopilot Action
Any help would be greatly appreciated. Thanks!
Nice work James! It looks the the issue is the redirect to your Twilio Function is not returning the expected JSON Action response to execute.
Autopilot - Redirect
https://www.twilio.com/docs/autopilot/actions/redirect
Redirecting to URLs When redirecting to a URL, Redirect will make an
HTTP callback to your application and will expect an Autopilot Actions
JSON as a response. The request will contain all the dialogue
information. This is an example of a dynamic Action since the JSON is
rendered dynamically with a URL or your own endpoint.
Can you modify the Twilio Function to return valid Action JSON to Autopilot which sets the returned data, if needed via the Remember action which you can access from Studio?
I'm building a chatbot with Twilio Autopilot and I want to get the images that I send to the bot, how can I get it? How can I get locations as well?
Thanks a lot.
Twilio developer evangelist here.
There isn't a simple way to do this, but there are a few potential work-arounds. One is to have a webhook endpoint that will get the input and if the payload contains elements of an image, then do whatever you want with it, otherwise if it is just text, then maybe send to Autopilot. That is gone over in this blog post on Autopilot enhancements in Node.js.
Another is to make a Twilio Function that would point to a Twilio Studio flow or Assets if it is media in the first message.
Another is to use Twilio Functions or a similar server. You should have an Autopilot task that redirects to that Function with JSON like this:
{
"actions": [
{
"redirect": {
"uri": "https://REPLACE-WITH-YOUR-FUNCTION-URL.twil.io/auso",
"method": "POST"
}
}
]
}
Then your Twilio Function could get the image URL with something like this in Node.js:
const bodyContent = event.MediaUrl0;
const filename = event.MessageSid + '.png';
Now in a Collect Action you can also specify the Twilio.MEDIA as the type for questions expecting Media and currently we support all media format which are supported by Twilio Messaging.
{
"question": "Please a take a picture of insurance card?",
"name": "insurance_card",
"type": "Twilio.MEDIA",
"validate": {
"allowed_types": {
"list": [
"image/jpeg",
"image/gif",
"image/png",
"image/bmp"
]
},
Lastly, you may be interested in this blog post on building an image classifier with Autopilot and TensorFlow.
Let me know if this helps at all! :D
Regarding images - As seen in this example of an autopilot task program, specify the input type to be an image
{
"actions": [
{
"collect": {
"name": "contact",
"questions": [
{
"question": "Please upload a cool picture of yourself",
"name": "contact_image",
"type": "Twilio.MEDIA"
}
],
"on_complete": {
"redirect": {
"method": "POST",
"uri": "https://url.twil.io/image-processing"
}
}
}
}
]
}
Then you can access the image as seen have done in the following function
exports.handler = function(context, event, callback) {
//we get the Memory from the answered questions.
let memory = JSON.parse(event.Memory);
//set up an array of object "actions" for the autopilot to continue.
let actions = [];
let responseItem;
//print the url of the image
let image_url = memory.twilio.collected_data.contact.answers.contact_image.media.url;
console.log(image_url);
responseItem = {
"redirect": {
"method": "POST",
"uri": "task://next_task"
}
};
actions.push(responseItem);
let respObj = {
"actions": actions
};
callback(null, respObj);
};
Autopilot Troublehooting
Unable to receive picture messages
Autopilot is currently unable to receive messages with pictures or other media types supported by Twilio on any messaging channel and will throw error with code 11200.
I haven't tried WhatsApp location data but there is a blog on the functionality that may help?
New Rich Features Support Deeper Customer Engagement on WhatsApp
I am trying to develop a simple application with Twilio and I have some issues. I have the code made with Python to make the call.
def makeCall(self, phoneNumber, from_num, message):
call = self.client.calls.create(
method='POST',
machine_detection='Enable',
to= phoneNumber,
from_= from_num,
url= self.url + urlencode({'Message' : message}),
status_callback= self.url_callback,
status_callback_method = 'POST'
)
return call.sid
And I also have a Node.js application with the Twiml response.
const express = require('express');
const VoiceResponse = require('twilio').twiml.VoiceResponse;
const urlencoded = require('body-parser').urlencoded;
const app = express();
app.post('/start', (request, response) => {
const twiml = new VoiceResponse();
const gather = twiml.gather({
numDigits: 1,
action: '/confirmation'
});
gather.say({
language:'en-EN'
}, 'Hello, are you ok?' );
gather.pause({
length: 1
});
gather.say({
language:'en-EN'
}, 'If no press one, if yes press two');
response.type('text/xml');
response.send(twiml.toString());
});
app.post('/confirmation', (request, response) => {
const twiml = new VoiceResponse();
if (request.body.Digits) {
switch (request.body.Digits) {
case '2':
twiml.say({
language:'en-EN'
}, 'I am sorry to hear that');
case '1':
twiml.say({
language:'en-EN'
}, 'Perfect!');
default:
twiml.say({
language:'en-EN'
}, 'Sorry, I don't understand you.');
twiml.pause({
length: 1
});
twiml.say({
language: 'en-EN'
}, 'Repeat please');
}
}
response.type('text/xml');
response.send(twiml.toString());
});
app.post('/callback', (request, response) => {
console.log(request.body.CallStatus);
console.log('--------------------');
console.log(request.body.AnsweredBy);
console.log('--------------------');
response.type('text/xml')
response.send(request.AnsweredBy)
});
app.listen(3000);
The problem is that when I execute the python function. If the user reject the call or doesn't answer, it sends a voicemessage to the answering machine and I would like to avoid it. I would also like to detect in the python code, if the call is rejected or not answered.
Thanks in advance
Twilio developer evangelist here.
You can't detect whether the call was answered in your python code that creates the call. That will queue up the call to be dispatched by Twilio, so all further events will happen asynchronously to that API call.
For your Node.js application that is receiving the webhook you can check what the current status of the call is by inspecting the CallStatus parameter that is sent as part of the body of the request. The CallStatus can be one of: "queued", "ringing", "in-progress", "completed", "busy", "failed" or "no-answer" and you can see more about the CallStatus parameter in the documentation.
To read the CallStatus parameter, you'll need to ensure you are using the body-parser middleware properly, urlencoded is a function and you need to set the express app to use it.
const urlencoded = require('body-parser').urlencoded;
const app = express();
app.use(urlencoded({ extended: false });
You can then get the call status in your response function.
app.post('/start', (request, response) => {
console.log(request.body.CallStatus);
// and so on
Then you can handle it from there.
I have created a Twilio function that I would like to use to send my affiliate referral link to subscribers of an application that come through my channel.
It works fine with a static to / from number, however I would like to make the "to" field a dynamic variable that can be manipulated via a HTTP/Webhook POST when a Zapier detects a new subscriber to my Mailchimp mailing list and pass their phone number as the variable.
I am also unclear what I need to do to authenticate the client (Zapier) that is making the POST as I do not want the function open to the world to use, if any insights can be shared on this it would be sincerely appreciated - I am a very inexperienced programmer trying to learn very quickly!
#philnash - thanks for your suggestion, implementing it slowly!
Many thanks in advance!
exports.handler = function(context, event, callback) {
const appCodes = ['code1', 'code2', 'code3', 'code4']
var smsBody = refCode ();
function refCode () {
return appCodes[Math.floor((Math.random() * appCodes.length))];
};
context.getTwilioClient().messages.create({
to: '+11112223333', // How do I make this dynamic from HTTP/Zapier Webhook POST???
from: '+1444555666',
body: `Get the App: ${smsBody}`
}).then(msg => {
callback(null, msg.sid);
}).catch(err => callback(err));
}
Twilio developer evangelist here.
I presume the Zapier webhook is sending the details, including the phone number, as the body of the POST request.
All the parameters in a request body appear on the event object that is passed into your handler. You probably want to run a test where you print out the contents of the event object to see what you are being passed. You can do this with:
exports.handler = function(context, event, callback) {
for (let key in event) {
console.log(`${key}: ${event[key]}`);
}
// ... rest of the function
}
Then, when you figure out what parameter is storing the number, you can use that in the call to create the message.
Let me know if that helps at all.
Try this:
exports.handler = function(context, event, callback) {
for (let key in event) {
console.log(`${key}: ${event[key]}`);
}
// ... rest of the function
callback(null, 'complete');
};
Thanks everyone for your input, it was sincerely appreciated! I was able to solve this with the following code:
exports.handler = function(context, event, callback) {
const appCodes = ['code1', 'code2', 'code3', 'code4']
var smsBody = refCode ();
var subNum = event.primaryPhone || 'There is no subscriber number'; // primaryPhone sent via HTTP post to twilio function
function refCode () {
return appCodes[Math.floor((Math.random() * appCodes.length))];
};
context.getTwilioClient().messages.create({
to: `${subNum}`, // parameters & values recieved from HTTP POST are available within the twilio functions "event" context
from: '+1444555666',
body: `Get the App: ${smsBody}`
}).then(msg => {
callback(null, msg.sid);
}).catch(err => callback(err));
}