I'm trying to track the sms delivery status of the messages I send using the bot framework. I'm using Twilio, and sending proactive messages. Right now I'm trying to do so with twilio status callbacks
This is similar to this question, I tried that approach but I couldn't get it to work. I've added my url on the TwiML app and it is not firing. I have double and triple checked, and I suspect this url is somehow ignored or not going through with my current set up. I don't get any callbacks on the proactive message nor on the replies the bot sends to the user. However the flow works fine and I can reply and get proper responses from the bot. Edit: calling this "approach 1"
approach 2: I've also tried this doing some light modifications on Twilio adapter, to be able to add my callback just before create message. (I changed it so it uses a customized client wrapper that adds my callback url when creating the twilio resource) This does work, partially: when I reply a message from my bot, I get the status callbacks. But as the proactive message is sent using the default adapter, I don't get a callback on the initial message.
approach 3: Finally, I also tried using the TwilioAdapter when sending the proactive message but for some reason as soon as I send an activity, the TurnContext is disposed, so I can't save the state or do any subsequent actions. This leads me to believe twilio adapter is not intended to be used this way (can't be used on proactive messages), but I'm willing to explore this path if necessary.
Here is the modified Twilio Adapter:
public class TwilioAdapterWithErrorHandler : TwilioAdapter
{
private const string TwilioNumberKey = "TwilioNumber";
private const string TwilioAccountSidKey = "TwilioAccountSid";
private const string TwilioAuthTokenKey = "TwilioAuthToken";
private const string TwilioValidationUrlKey = "TwilioValidationUrl";
public TwilioAdapterWithErrorHandler(IConfiguration configuration, ILogger<TwilioAdapter> logger, TwilioAdapterOptions adapterOptions = null)
: base(
new TwilioClientWrapperWithCallback(new TwilioClientWrapperOptions(configuration[TwilioNumberKey], configuration[TwilioAccountSidKey], configuration[TwilioAuthTokenKey], new Uri(configuration[TwilioValidationUrlKey]))), adapterOptions, logger)
{
OnTurnError = async (turnContext, exception) =>
{
// Log any leaked exception from the application.
logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");
Task[] tasks = {
// Send a message to the user
turnContext.SendActivityAsync("We're sorry but this bot encountered an error when processing your answer."),
// Send a trace activity, which will be displayed in the Bot Framework Emulator
turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError")
};
Task all = Task.WhenAll(tasks); //task with the long running tasks
await Task.WhenAny(all, Task.Delay(5000)); //wait with a timeout
};
}
}
Modified client Wrapper:
public class TwilioClientWrapperWithCallback : TwilioClientWrapper
{
public TwilioClientWrapperWithCallback(TwilioClientWrapperOptions options) : base(options) { }
public async override Task<string> SendMessageAsync(TwilioMessageOptions messageOptions, CancellationToken cancellationToken)
{
var createMessageOptions = new CreateMessageOptions(messageOptions.To)
{
ApplicationSid = messageOptions.ApplicationSid,
MediaUrl = messageOptions.MediaUrl,
Body = messageOptions.Body,
From = messageOptions.From,
};
createMessageOptions.StatusCallback = new System.Uri("https://myApp.ngrok.io/api/TwilioSms/SmsStatusUpdated");
var messageResource = await MessageResource.CreateAsync(createMessageOptions).ConfigureAwait(false);
return messageResource.Sid;
}
}
Finally, here's my summarized code that sends the proactive message:
[HttpPost("StartConversationWithSuperBill/{superBillId:long}")]
[HttpPost("StartConversationWithSuperBill/{superBillId:long}/Campaign/{campaignId:int}")]
public async Task<IActionResult> StartConversation(long superBillId, int? campaignId)
{
ConversationReference conversationReference = this.GetConversationReference("+17545517768");
//Start a new conversation.
await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, async (turnContext, token) =>
{
await turnContext.SendActivityAsync("proactive message 1");
//this code was edited for brevity. Here I would start a new dialog that would cascade into another, but the end result is the same, as soon as a message is sent, the turn context is disposed.
await turnContext.SendActivityAsync("proactive message 2"); //throws ObjectDisposedException
}, default(CancellationToken));
var result = new { status = "Initialized fine!" };
return new JsonResult(result);
}
private ConversationReference GetConversationReference(string targetNumber)
{
string fromNumber = "+18632704234";
return new ConversationReference
{
User = new ChannelAccount { Id = targetNumber, Role = "user" },
Bot = new ChannelAccount { Id = fromNumber, Role = "bot" },
Conversation = new ConversationAccount { Id = targetNumber },
//ChannelId = "sms",
ChannelId = "twilio-sms", //appparently when using twilio adapter we need to set this. if using TwiML app and not using Twilio Adapter, use the above. Otherwise the frameworks interprets answers from SMS as new conversations instead.
ServiceUrl = "https://sms.botframework.com/",
};
}
(I can see that I could just call create conversation reference and do two callbacks, one for each message, but in my actual code I'm creating a dialog that sends one message and then invokes another dialog that starts another message)
Edit 2:
Some clarifications:
On approach 2, I'm using two adapters, as suggested by code sample and documentation on using twilio adapter. The controller that starts the proactive message uses an instance of a default adapter (similar to this one), and TwilioController (the one that gets the twilio incoming messages) uses TwilioAdapterWithErrorHandler.
On approach 3, I excluded the default adapter, and both controllers use TwilioAdapterWithErrorHandler.
Edit 3:
Here's a small repo with the issue.
I found a fix for this problem, around approach 3, by changing the overload I use for ContinueConversation. Replace this :
//Start a new conversation.
await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, async (turnContext, token) =>
With this:
//Start a new conversation.
var twilioAdapter = (TwilioAdapterWithErrorHandler)_adapter;
await twilioAdapter.ContinueConversationAsync(_appId, conversationReference, async (context, token) =>
This way, the context is not disposed, an I can use the twilio adapter for the proactive message and have status callbacks on all messages.
Sending a message from Twilio is succesful, now i want to be able to enter the TO phone number the message body from a form, but i'm not sure which method is the best. I'm thinking maybe calling a variable from another ViewController but i am not sure if that would work, suggestions?
Here is my twilio controller
public IActionResult Sendsms()
{
var accountSid = "XXXXXXXXXXXXXXXXXXXXXXXXXX";
var authToken = "XXXXXXXXXXXXXXXXXXXXXXXXXXXX";
TwilioClient.Init(accountSid, authToken);
var to = new PhoneNumber("+XXXXXXXXXXXXXXX");
var from = new PhoneNumber("+XXXXXXXXXXXXXXXX");
var message = MessageResource.Create(to: to,
from: from,
body: "this is the ship that made the kessel run in 14 parsecs?");
return Content(message.Sid);
}
I have this javascript code that send data to channels
// Note that the path doesn't matter for routing; any WebSocket
// connection gets bumped over to WebSocket consumers
socket = new WebSocket("ws://" + window.location.host + "/chat/");
socket.onmessage = function(e) {
alert(e.data);
}
socket.onopen = function() {
socket.send({"test":"data"});
}
// Call onopen directly if socket is already open
if (socket.readyState == WebSocket.OPEN) socket.onopen();
I'm curios how from message I can get the json {"test":"data"}
here's the view
# Connected to websocket.connect
#channel_session
def ws_connect(message, key):
# Accept connection
message.reply_channel.send({"accept": True})
You implemented the connection callback, but did not implement what should happen when a message arrives the server endpoint. Add add message receive function:
def on_receive(message):
print('test received: {}'.format(message.content['test']))
Register the function in routing.py:
channel_routing = [
route("websocket.connect", ws_connect),
route("websocket.receive", on_receive),
]
The JSON message you send will be stored in message.content which is basically just a python dict.
i've written an Azure function and connected the output to a notification-hub to send push notifications using APNS. It works fine as long as i send the notification to all registered devices, but i don't know how to use tags in order to address a specific user. If i try to use a tag, i get an error message saying "Exception while executing function: Functions.SendSinglePushNotification. Microsoft.Azure.WebJobs.Host: Error while handling parameter notification after function returned:. Microsoft.Azure.NotificationHubs: notification.Tag property should be null."
Here's my code so far:
#r "Microsoft.Azure.NotificationHubs"
#r "Newtonsoft.Json"
using System;
using Microsoft.Azure.NotificationHubs;
using Newtonsoft.Json;using
Microsoft.Azure.WebJobs.Host.Bindings.Runtime;
public static void Run(HttpRequestMessage req, TraceWriter log,Binder
binder, out AppleNotification notification)
{
string user = "Test";
string tagExpression = "Test";
string userTag = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "userid", true) == 0)
.Value;
string apnsNotificationPayload = "{\"aps\": {\"alert\": \"Test: (" + user + ")\" }}";
notification = new AppleNotification(apnsNotificationPayload);
}
I was trying to use notification = new
AppleNotification(apnsNotificationPayload,tagExpression);
but that does not work. How can i achieve that?
Thanks a lot and best regards
I had similar issue. Eventually, what worked for me was constructing Notification client manually. I am developing functions in Visual Studio, so my code is slightly different from yours.
[FunctionName("MyFunction")]
public static async Task Run([ServiceBusTrigger("queuename", AccessRights.Listen, Connection =
"<connection-settings-name>")] string myQueueItem, TraceWriter log)
{
log.Info($"C# Queue trigger function processed: {myQueueItem}");
var notificationHubSas = "<DefaultFullSharedAccessSignature from Azure portal>";
var notificationHubName = "myhub";
var nhClient = NotificationHubClient.CreateClientFromConnectionString(notificationHubSas, notificationHubName);
var tags = "";
await nhClient.SendTemplateNotificationAsync(<notification payload>, tags);
}
Can I delete older SMS logs from the twilio server via the user console? I only need about 30 days worth of records.
==============================================================
Update: following #philnash's suggestion, I was able to delete all messages older than 30 days with the following C# code:
using Twilio;
void deleteMessages(int num_days)
{
var twilio = new TwilioRestClient(AccountSid, AuthToken);
bool done;
do
{
done = false;
var request = new MessageListRequest();
request.Count = 1000; // this is all you can get per request
request.DateSent = DateTime.Now.AddDays(-num_days);
request.DateSentComparison = ComparisonType.LessThanOrEqualTo;
var messages = twilio.ListMessages(request);
if (messages.Messages.Count < 1000) done = true;
foreach (var message in messages.Messages)
{
twilio.DeleteMessage(message.Sid);
}
} while (!done);
}
Twilio developer evangelist here.
I can't find a way to delete logs from the console, however you can absolutely write some code to do so. Here's an example to delete messages older than 30 days in Ruby:
require "twilio-ruby"
require "date"
client = Twilio::REST::Client.new(ENV["TWILIO_ACCOUNT_SID"], ENV["TWILIO_AUTH_TOKEN"])
messages = client.messages.list("DateSent<" => (Date.today - 30).to_s)
while !messages.empty?
messages.each { |message| message.delete }
messages = messages.next_page
end
Let me know if that sort of thing helps.
You can do by writing a javascript code and run using nodejs
var twilio = require('twilio');
var accountSid = 'your sid'; // Your Account SID from www.twilio.com/console
var authToken = 'your token'; // Your Auth Token from www.twilio.com/console
var twilio = require('twilio');
var client = new twilio(accountSid, authToken);
client.messages.each(messages => messages.remove().then().done());