DCtheTall / twilly

A library which provides an abstract syntax for designing Twilio SMS interactions. Designed for use with Node.js and Express
MIT License
52 stars 3 forks source link

Trigger from outside flow? #3

Open rivertam opened 5 years ago

rivertam commented 5 years ago

So I want to create a flow and then have a website form trigger that flow for a phone number entered in the form. Should I just use twilio's normal API? This seems like an oddly missing part of twilly.

DCtheTall commented 5 years ago

This is a great idea! I admit this is definitely missing key functionality.

I am going to look into adding this (for today I will add it to the "Feature Pipeline" section of the README).

PRs are welcome if you are interested in working on it, just let me know if you plan on working on it!

DCtheTall commented 5 years ago

Leaving some notes here.

Q: The reason all Flows start from a request from Twilio is because Twilly sets an SMS cookie which stores the SMS cookie, which is what is used to store the interaction's state in the graph. How do we get the cookie for the remote trigger?

A: We can make some dummy GET request to Twilio's API and read the cookie from their response. Optionally we can have it automatically overwrite the current state with a fresh one.

Right now, Twilly returns an Express router, the twilly() function's interface can be modified so that you can choose API endpoints which trigger Actions. This leans toward the use case where the server with the Twilly code can be used as an SMS application micro-service.

If you want to trigger a Flow outside of a webhook, and remotely during some asynchronous Node.js procedure, I can have Twilly export a new function which takes a Flow name (string) as a parameter. This will trigger that Flow or throw an exception if the flow does not exist.

The function should also throw an exception if the twilly() function is not called before the new function, for now let's just call it triggerFlow though that may change, to ensure the FlowSchema graph has been traversed.

I will leave it up to the user to handle the case when the SMS Cookie from Twilio has expired in their Flow definitions.

rivertam commented 5 years ago

So I know very little about twilly internals, but I think it would be possible to pick any number available to the service, right? I'll say at least for my usecase, it's actually preferable if the from does not have an associated cookie, because I might prefer to use a number the user has never seen before if I'm the one initiating contact. That way, at least theoretically, the user can have multiple conversations with the service at the same time. I just want to point that out there -- the SMS cookie looks like a nice signal, but I'm not sure you're treating it right ("handle the case" rather than "prefer the case", for example). I don't use the cookie for authentication or anything, just for conversation state/whatever twilly uses it for which I assume is just conversation state.

I won't speak for other users, but just saying if I trigger I definitely don't need you to do anything terribly special in the case that the cookie has expired; that's probably the way I wanted it anyways. That said, I wouldn't necessarily expect twilly to pick a number the user's never seen before. As long as it's documented, any number is fine.

DCtheTall commented 5 years ago

@rivertam I have started implementing this feature and I found that my comment was actually not correct. The SMS cookie is stored in Twilio's server and is sent with requests, but not responses (AFAIK, I could be wrong). So we cannot get the SMS cookie unless a Twilio server makes a request to the Express app. So it will start with a fresh state each time.

My goal is to have a triggerFlow() function instead of creating an instance of a Trigger action since Action objects are actually mostly read-only. The function will take a Flow name and a from number. that takes a Flow as an argument but disallows certain actions like Questions, since it is not possible to set an SMS cookie without an incoming request from Twilio.

Unfortunately I cannot think of a way to make this feature without a minor regression, you won't be able to have multiple Twilly routers in your app without providing extra parameters to the twilly() function call. Since I expect this use case to be rare, I think it is worth it though. This isn't necessary anymore.

I'll update the docs on how to use multiple Twilly routers with different FlowSchema's once this change is merged into master and released.

In terms of persisting state across triggered flows either from a Twilio request or a call to triggerFlow, Twilly unfortunately has to treat these as entirely separate interactions. It will be up to Twilly users to store their state across those features using the getUserContext hook.

DCtheTall commented 5 years ago

Just an update: I am working on this, the implementation is done. I just need to:

1. Test it with my own Twilio account. 2. Test that the changes I made did not cause a regression. 3. Add unit tests for the new functionality.

DCtheTall commented 5 years ago

@rivertam check out this PR if you want to get a sense of what it will look like. Unfortunately there is no way to use Question actions in the Flow, and you cannot trigger other Flows, but it provides basic messaging functionality. Any state you want to persist across triggerFlow and your larger FlowSchema should be accessible through the getUserContext hook.

rivertam commented 5 years ago

Sorry I've been AWOL. I actually realized it might be illegal in some places to message people from a service without prompting. It's obviously useful in some cases, but for my use-case it seems too risky, so I'm changing my application to be web-first rather than a messaging service. Unfortunately, that means I can't really opine on this topic right now.

DCtheTall commented 5 years ago

@rivertam no worries, I have been busy with work/travel recently as well. Once I get around to tests I will merge it in since I still think this use case is worth the change.

DCtheTall commented 3 years ago

@rivertam FWIW if you wanted to implement this without having to wait on me merging #7 (which I will get to someday) you could also use Twilio's Node.js API to send the text, then use your Twilly graph to just handle the user's reply.