The Pain of Keeping Everyone in the Loop!
Communication while travelling with a bunch of people can be very problematic. Numerous times in the past I have traveled to Montreal with 13 other (aging) hockey players to play in a hockey tournament and basic communication though not impossible is not as easy as it should be.
Not everyone wants to do or go to the same places but everyone wants to stay connected, or reconnect throughout the day. It’s nice to send a broadcast “Where is everyone” message and then based on the replies decide where to go.
Note: This year we brought home the trophy which was a nice change to previous years, and I’d like to think that team communication played a role in our success! 😎
Some Thoughts on the Approach
When I started thinking about this project I felt that there had to be a better way to push messages to those in the group.
In previous years we would use SMS messages, but unless you knew that you absolutely wanted to talk to one specific individual the messages often had to be posted numerous times.
An SMS group was also a possible option but most providers push SMS group messages as MMS (multimedia) which forces users to use a cellular data connection.
Email was not considered an option because not everyone is notified when new email messages are received and Internet data use could become an issue. Plus dealing with long email chains is so 2010…
Project Requirements
So the first step was to define the requirements:
- offer an ability to push a message to the entire group
- tag messages with the senders name or some other identifier
- secure the group so that it is not public
- allow image passing without MMS
- allow users to stop and restart reception of messages
This solution was designed to take advantage of free SMS messaging, which has become pretty standard with most providers.
Twilio API for Messaging
Based on past experience with Twilio I selected it as the platform for SMS message distribution. Twilio is very straightforward to use and I have used it successfully in other projects.
With Twilio, they provide phone numbers that support both SMS and MMS but as noted only an SMS number was considered. Twilio allows messages to be sent to a registered number and can pass the information to a linked web application.
The cost for using Twilio is less than 1 cent per message. In fact sending one message to a group of 10 people only costs 7.5 cents. So 10 messages to the same 10 people results in a cost of only 75 cents.
The web application development was done with PHP and by using the Google App Engine for the message endpoint there was no cost and virtually no work to setup hosting and related services. Google App Engine has a free plan that was more than enough to cover the bandwidth requirements for such a basic application.
Share Images without MMS
To offer users the ability to share photos without MMS it was necessary to support receiving MMS data and then converting it.
I wasn’t concerned about users sending MMS messages since I assumed that if a user chose to send an image in a message they were aware of the fact that data would be used to push the message. On the flip side, I did not want to force a user to use data to see an image.
By converting the image into a web URL that could be added to a textual message the problem could be solved. The user could then select to view the image or not and therefore make the decision to use data or not. This way they would see the text that was included with the message and have the option to view the image by following the link.
This did create another issue though, which was the conversion of an image into a URL. As it turns out this was easily resolved since Twilio hosts the received image data on their servers for a short period of time. In fact images sent last year were still on the Twilio servers, so even a “short time” would be plenty of time.
Using a URL Shortener
The received message which is sent to Twilio for processing includes the Twilio image URL so that just needed to be attached to the message that was being sent to the group. I decided to use a URL shortener since the Twilio URL’s were quite long and sometimes with an additional textual message would run over the 160 character limit resulting in 2 or more messages being sent.
Last year I used the Google URL shortener API, but this year I had to convert that to use the Firebase Dynamic Links due to the Google API preparing to be deprecated.
Software and Tools Used
So all of this boiled down to only requiring a couple of different systems. Each of these systems performs a specific function in the overall project.
- Twilio for SMS messaging
- Google App Engine for application hosting
- Firebase Dynamic Links for URL shortener
Of course to replicate my setup and use these items accounts would need to be created.
The Software Solution
The following represents some elements which were necessary to build the application.
// composer.json libraries "require": { "twilio/sdk": "^4.8", "php": "7.2.*" }
The Twilio API passes in several values when a new message is received. This are the values that we will need to manipulate.
The key elements are the body of the messages and the media URL if one is provided.
// Standard message body is 'Body' but 'body' has been received in the past // this normalizes the data to a standard of 'Body' if (isset($_REQUEST['body']) && !empty($_REQUEST['body'])) { $_REQUEST['Body'] = $_REQUEST['body']; } // Copy the message to a new variable $receivedMessage = isset($_REQUEST['Body']) ? $_REQUEST['Body'] : ''; $picCount = 0; $media = ""; // Media URL's are named 'MediaUrl0' 'MediaUrl1'... $mediaUrl = $_REQUEST['MediaUrl' . $picCount ]; while( $mediaUrl != "" ) { // shorten the Twilio URL's using a function $media .= shortenURL($mediaUrl); $picCount++; $mediaUrl = $_REQUEST['MediaUrl' . $picCount ]; } // at this point the variable $media will contain the shortened URLs of any media // If we don't have a message to work with, let's stop here. if (empty($receivedMessage) && empty($media)) { $response = new Services_Twilio_Twiml; print $response; exit(); }
Notice here that if we don’t have a message we print the response from the Service_Twilio_Twiml object. The Twilio API calls our handler and whatever we print out is returned back for processing. In this particular case we are returning a standard Twilio no operation type message by printing out the result of the empty object.
Security is Important!
One thing that we need to do is to determine whether the sender is allowed to user our system. This can be done by maintaining a list of those that are allowed to send messages using the system. Comparing the phone number of the sender against this list will allow us to implement very basic security.
The user’s phone number is sent through the Twilio API as ‘From’.
Below is a sample of the authorized array. We can use the ‘From’ value for a basic comparison against this array of user details.
$authorizedUsers = [ ['name' => 'Robin', 'mobile' => '+16135554012'], ['name' => 'John', 'mobile' => '+16135552350'], ['name' => 'James', 'mobile' => '+16135556078'], // : etc. ];
The code used for user validation simply loops over this array to validate the message sender. Once a user is validate, a flag is set to denote that the message can be sent and the user object is stored to a temporary variable.
By tracking the sender details, we have access to the values defined in the above array. For now, the user name is all we want and allows outgoing messages to be tagged with a user name. In the future, other elements might be added to the array.
foreach ($authorizedUsers as $user) { if ($user['mobile'] == $_REQUEST['From']) { $authorized = 1; $receivedFrom = $user; } }
Sending a Message
Once all the details are collected, we want the system to send a message to all of the authorized users.
To accommodate that we need to formulate the final message and then send that message to all of the users. The sender will receive a copy of their own message to show them that the user message was delivered successfully.
if ($authorized == 1) { $response = new Services_Twilio_Twiml; // build the final message which will be sent to all users $message = $receivedMessage . ' -' . $receivedFrom['name']; if( $media != "" ) { $message .= $media; } // loop over the users in the array and send the message foreach ($authorizedUsers as $user) { // this builds the response which will contain the user details // the message is concatenated to all others for efficiency $response->message( $message, [ 'to' => $user['mobile'], 'from' => $twilioSettings['myNumber'] // comes from a Twilio settings array ]); } print $response; exit(); } else { // recall that an empty Services_Twilio_Twiml object doesn't really do anything $response = new Services_Twilio_Twiml; print $response; exit(); }
Note that the $twilioSettings array is referenced but was not included above. The array contains the accountSid, authToken, and phone number being used for sending an receiving SMS messages. These items can be obtained through the Twilio web site once an account is setup and enabled.
Raw Twilio Message
Here is a sample outgoing message built with the Services_Twilio_Twiml object. The message is being sent out to all of the users in the group, or all of the users defined in the $authorizedUsers array.
When You Need Some Alone Time
Another benefit of the Twilio system is that it supports STOP and START messages. This means that a user can toggle their involvement in message delivery by sending one of these keywords.
If the chat gets too noisy, a user can send STOP to stop any new messages from being received. Once they are ready to get back into the chat they can send the word START.
Full source code is on GitHub https://github.com/gizmobin/team-pager