-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for SMS conversations #7
base: master
Are you sure you want to change the base?
Conversation
@adambrenecki : OMG YOU ARE DUCKING AWESOME I LOVE YOU!!! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are fantastic! There are no real blockers here, although as you can see from line comments I have plenty of questions, and what I hope is an easy request regarding the JSON.
Thank you so much for this! You are absolutely fantastic!
from_=request.form['From']), | ||
Body='', | ||
) | ||
return "Hello world!" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could be wrong here, but my understanding is that Twilio requires a valid TwiML response in order to consider a message processed. If that return is going back to Twilio (which I think it is), then this will make Twilio unhappy.
We should be able to get away with just <Response></Response>
as a return, which is a valid return that does nothing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Twilio didn't generate an error message when I tested it, contrary to their docs. (Perhaps because I'm sending a response with whatever Flask's default Content-Type is (text/plain maybe?) rather than the TwiML type, it's not expecting to be able to do anything with it?)
In any case, I agree that what you're suggesting is nicer, whether it's strictly required or not.
}, | ||
"events": [{ | ||
"function": "rickroll.send_sms", | ||
"expression": "rate(4 minutes)" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oooh, this is cool! So every N minutes (n=4 in this case) we'll clear the SMS queue of messages? (So on average, we'd see a reply every two minutes?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep! This seemed like the simplest way to have a delay before replying without actually making the Lambda function spin for a few minutes (and rack up charges in the process).
I thought about randomly skipping 50% of the queue every time send_sms
runs to increase the variability, but I'm not sure whether that would make it more realistic or less.
last_msg = -1 | ||
else: | ||
raise | ||
else: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Python n00b 'woah' moment here... You can have a try/except/else
statement? That's cool!
Am I correct the else
happens if no exception is raised?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep!
You can also attach an else
block to a for
or while
loop, which executes if the loop runs to completion (as opposed to hitting a break
).
bucket.put_object( | ||
Key=state_key, | ||
Body=str(next_msg), | ||
Expires=datetime.now() + timedelta(days=7), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Am I correct this expires our messages after a week, so if the same person were to text again after a week has passed the conversation would start again from the beginning?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. I wasn't sure what the threshold should be, but I felt like there should be some cut-off.
bucket.put_object( | ||
Key='queue/{to}/{from_}'.format(to=request.form['To'], | ||
from_=request.form['From']), | ||
Body='', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this lose the message they sent us? It would be visible in the Twilio logs, but in terms of maximum amusement for line operators, being able to keep the conversation in a more accessible form would be amazing. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or am I correct that these queued messages get deleted later (when we process them), so storing the SMS received in them is not exactly useful. :)
Would it make more sense to have a separate logs/
folder, which just logs incoming and outbound messages and timestamps,. but doesn't play a role in message queuing at all? (Absence of a logs directory isn't a blocker for this change, but is certainly a nice-to-have!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's exactly how it works! (I meant to add a PR comment summarising how everything works, but ran out of time.)
// Put a random S3 bucket name that doesn't already exist here. Zappa | ||
// will create it and put your Lambda code in it when you run | ||
// 'zappa init'. | ||
"s3_bucket": "insert-bucket-name-here", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of my least favourite things about JSON is that it doesn't allow comments... 😭
While Zappa may be relaxed enough to allow them, the QA/Testing part of my soul stays awake at night when I know we're technically breaking a format standard.
I know it's awful, but can I ask for something like:
"s3_bucket": "An S3 bucket that doesn't already exist. Zappa will create it and use it for storage when you run `zappa init`"
Comments are waaay nicer, and if we were using YAML or HJSON I'd be all over them. But every linter (including github's changes view) and my psyche is going to worry every time it sees a //
otherwise. :/
|
||
6. Record the URL it spits out, connect it as the incoming call webhook to your own phone number with Twilio. Set the incoming SMS webhook to the same URL, but with `/sms` on the end. (You can do this for more than one number.) | ||
|
||
Congratulations! You now have your own serverless Rick Astley Hotline! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documentation! You are amazing! ❤️
|
||
# Find out which message we sent last, if any | ||
# Error handling taken from https://stackoverflow.com/a/33843019 | ||
try: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code climate is point out that this function is a bit complex, and this seems perfect for factoring out to a separate function which returns the next message number (or raises on error).
I'm guessing that if we do throw an exception, we should log that but still try to process the other messages in our queue, so a single bad message doesn't knock out the entire SMS queue. (This isn't a blocker for this PR, but indicates future work that may be required.)
I have no idea what the best way is to log an error from Lambda.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, no issues on separating out into a separate function. I'll do both that and what you're suggesting with error catching as well.
For logging errors from Lambda, I'd use Sentry; it's a really well-thought-out tool with a generous free tier, and the whole thing is open source too! (We run the open-source version on-prem at work.)
Closes #4.
This does make the deployment process a little more complicated though, and it also wouldn't be safe to publish the endpoint URL publicly (since someone who knew the incoming SMS endpoint could forge requests from Twilio and trick the Hotline into 'replying' to a third party).
Tell me what you think! :)