This year I’ve been asked by my mother-in-law to help her cook Christmas dinner for the family. I’m really looking forward to it, but with a new puppy in the house and a young nephew I can see myself getting distracted and forgetting to put the potatoes in the oven at the right time!
To help with this, I decided to write a small Laravel application that maintains a collection of recipes. You send it a message with the name of a recipe via WhatsApp, Facebook or Viber. It retrieves the list of steps each recipe requires and sends you the next step when it's time to do it. You can relax, safe in the knowledge that everything in the kitchen is under control and when it's time for human intervention, you'll be notified!
Here's the application in action:
In this post I’ll be working with Facebook, but it’s easy to extend to WhatsApp and Viber too as we’ll be using the Vonage laravel-notification package.
Laravel and Vonage Project Setup
There’s quite a lot of setup required for this project, so to avoid taking you until New Year to read this I've jumped straight to the Vonage-specific code in this post. If you are interested, here’s the process I went through to set up the application (each item links to a commit that contains a longer description of the work done):
It’s a pretty simple application that has user management and the ability to show a recipe and it’s associated timings. It’s a standalone application with no dependency on Vonage at the moment. However, we want to be able to receive messages to our Facebook page, so I need to do a little more configuration. To make the application work, I need to link my Facebook page to a Vonage account, create and configuring an application so that Vonage knows where to send the webhook requests to and expose my application to the internet using ngrok so that Vonage can reach it.
If you’d like to try building this yourself, join the Vonage Community Slack workspace and we can work through the required steps together
In the rest of this post, we’re going to be adding the ability for users to send a message to us with a recipe name and have the application respond with the actions that need performing at the correct time.
Handling Inbound Facebook Messages
When I created my Vonage application, I had to provide two URLs; one that will be called when I receive a message from a user and another which receives status updates from Vonage. I’ve chosen /webhooks/inbound-message
for receiving messages and /webhooks/message-status
for the status updates. As Vonage will send a request from outside the application I’ve had to disable CSRF checking in app/Http/Middleware/VerifyCsrfToken.php
:
protected $except = [
'/webhooks/*'
];
Now that Vonage can access my webhook, it’s time to handle those inbound requests. I used make:controller
to generate a new WebhooksController
and updated routes/web.php
to point the above URLs to this controller:
Route::post('/webhooks/inbound-message', 'WebhooksController@inboundMessage')->name('webhooks.inbound');
Route::post('/webhooks/message-status', 'WebhooksController@messageStatus')->name('webhooks.status');
The final thing to do is implement the WebhooksController
. For now I’m logging the inbound request so that I can see the format of the request that Vonage sends:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class WebhooksController extends Controller
{
public function inboundMessage(Request $request) {
\Log::debug('Inbound Message', $request->all());
}
public function messageStatus(Request $request) {
\Log::debug('Message Status', $request->all());
}
}
After making these changes, I sent a message from my personal account to my Facebook page and the following entry appeared in the Laravel log file:
{
"message_uuid": "f4fcc665-7b71-4291-a079-505154e28c36",
"to": {
"id": "987654210987654",
"type": "messenger"
},
"from": {
"id": "123456789012345",
"type": "messenger"
},
"timestamp": "2018-12-12T11:36:44.663Z",
"direction": "inbound",
"message": {
"content": {
"type": "text",
"text": "Christmas Dinner"
}
}
}
Excellent! The user sent me a message and my application received it as intended. Now that we can receive messages it’s time to start sending back responses.
The changes made in this section are shown in this commit
Creating a Laravel Notification
To send updates back to the user at the correct time we’re going to be using the Laravel queue system’s delay
functionality. Before we can do that, we need to enable the queue
functionality in Laravel. We’re going to use the database
driver as it won’t be particularly high throughput and using the database removes the need for additional dependencies such as Redis. We can configure this setting in the .env
file:
QUEUE_CONNECTION=database
Once that’s done, I created the table to store the jobs by running php artisan queue:table && php artisan migrate
.
With all of the admin out of the way, it’s time to start building our notifications. I’ll need to send a simple sentence whenever a condition is triggered. I could have created one notification per message, but in the interest of speed I created a single notification that accepts a string at app/Notifications/FreeText.php
with the following contents:
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
class FreeText extends Notification implements ShouldQueue
{
use Queueable, ShouldQueue;
protected $text;
protected $channel;
public function __construct($text, $channel)
{
$this->text = $text;
$this->channel = $channel;
}
public function via($notifiable)
{
return [];
}
}
This defines a notification, but our application doesn’t know how to send it yet. We need to populate the via
method and implement any to*
methods on the notification. To send these notifications we’re going to be using the nexmo/laravel-notification package which allows us to implement the following methods on our notification:
toNexmoWhatsApp
toNexmoFacebook
toNexmoViberServiceMessage
toNexmoSms
We’re going to implement and toNexmoFacebook
by adding the following to the notification:
public function toNexmoFacebook($notifiable)
{
return (new \Nexmo\Notifications\Message\Text)
->content($this->text);
}
In addition to implementing these methods, we need to tell Laravel how to route the message. Usually you’d use the $notifiable
entity passed to the method to determine how the user wants to be contacted. In this case, we’re going to reply on the channel that we received the message on. This channel is passed in to the notification’s constructor. This is what my via
method looks like with those changes:
public function via($notifiable)
{
return [$this->channel];
}
There is one last thing to do before this will all start working, and that’s to provide some Vonage authentication credentials and configuration for sending via Messenger. If you want to do the same, create an application on the Vonage dashboard and add the following to your .env
file:
NEXMO_APPLICATION_ID="YOUR_APPLICATION_ID"
NEXMO_PRIVATE_KEY=./private.key
NEXMO_FROM_MESSENGER="FACEBOOK_PAGE_ID"
The changes made in this section are shown in this commit
Sending Updates to Facebook
Now that we’ve done all of the hard work, the last thing to do is send the actions that need to be carried out to cook the recipe.
Upon receiving a message from a user we need to extract the channel the message was sent on, and the ID of the user that sent us the message. Once we have that, we can create a new On-Demand Notification using this information:
// The incoming message contains the platform + contact details that
// we need to reply with, so configure a notification route with those
// details
$from = $request->input('from');
// The Vonage Messages API returns messenger, but our channel names are all prefixed with nexmo-
$channel = 'nexmo-' . $from['type'];
$sender = Notification::route($channel, $from['id']);
At this point we can send any text we like to the user. The first thing we need to check is if the message they sent us contains a recipe name. If does not, we send them a message saying that we couldn’t find that recipe.
// Try and find the recipe name that was sent to us
$recipeName = $request->input('message.content.text');
$recipe = \App\Recipe::where('name', $recipeName)->first();
if (!$recipe) {
$sender->notify(new FreeText(
"I couldn't find that recipe",
$channel
));
return;
}
If we get past this block of code then we have a valid recipe, and it’s time to schedule some notifications! Each set of timings on a recipe has an action
and a start_time
in seconds, starting at zero. Fortunately Laravel allows us to delay a notification by a number of seconds from now, making it the perfect fit for our use case.
The final part of our inboundMessage
method needs to iterate over every timing
and schedule a new notification:
foreach ($recipe->timings()->get() as $t) {
$sender->notify((new FreeText(
$t->action,
$channel
))->delay($t->start_time));
}
Putting it all together, our inboundMessage
method looks like the following:
public function inboundMessage(Request $request) {
\Log::debug('Inbound Message', $request->all());
$from = $request->input('from');
// The Vonage API returns messenger, but our channel names are all prefixed with nexmo-
$channel = 'nexmo-' . $from['type'];
$sender = Notification::route($channel, $from['id']);
// Try and find the recipe name that was sent to us
$recipeName = $request->input('message.content.text');
$recipe = \App\Recipe::where('name', $recipeName)->first();
if (!$recipe) {
$sender->notify(new FreeText(
"I couldn't find that recipe",
$channel
));
return;
}
// If we get this far, we have a recipe! Time to schedule some notifications
foreach ($recipe->timings()->get() as $t) {
$sender->notify((new FreeText(
$t->action,
$channel
))->delay($t->start_time));
}
}
The changes made in this section are shown in this commit
Running the Application
Now that everything’s been built it’s time to run the final application! Here’s a quick checklist of everything I needed to do to get things working:
Run
php artisan serve
Make sure
ngrok http 8000
is running so that Vonage can make calls to my applicationRun
php artisan queue:work
to watch for jobs being inserted in to the databaseVisit my Facebook page and send it a recipe (in this case,
Christmas Dinner
!)Sit back and relax, knowing that I’ll get a message when there’s something I need to do
If you’d like to see the complete project for this post you can find it on Github. If you want to run it yourself, you’ll need to:
Link a Facebook page to Vonage
Create a new Vonage application and associate your page with that application
Configure your webhooks
Clone the repo
Update
.env
with your Vonage credentialsRun
composer install
Run
php artisan migrate && php artisan db:seed
Run
php artisan serve
andphp artisan queue:work
in separate terminalsSend your Facebook page a message
Where Next?
Well, that was fun! Not only did I get to try out the Vonage Messages API but I learned a lot about Laravel notifications (including how to build new channels). As an added bonus, I’ll even have a little helper reminding me when things need to happen on Christmas Day!
Michael is a polyglot software engineer, committed to reducing complexity in systems and making them more predictable. Working with a variety of languages and tools, he shares his technical expertise to audiences all around the world at user groups and conferences. Day to day, Michael is a former developer advocate at Vonage, where he spent his time learning, teaching and writing about all kinds of technology.