Create a Daily Inspirational Quote App Using Laravel, Twilio, and OpenAI

July 02, 2024
Written by
Anumadu Udodiri Moses
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by

Create a Daily Inspirational Quote App Using Laravel, Twilio, and OpenAI

In our fast-paced lives, staying motivated and inspired can sometimes be challenging. Yet, it's essential for our mental health and for maintaining our overall productivity. Whether you're a budding entrepreneur, developer, or simply interested in personal development, the app we'll develop in this tutorial aims to bring a splash of inspiration to your daily routine.

The idea behind receiving a daily inspirational quote is simple, yet profound. These small nuggets of wisdom can set a positive tone for your day, boost your motivation, and help you navigate through the challenges with a better mindset.

To bring this idea to life, the following technologies will be used in this tutorial.

Prerequisites

The following prerequisites are required to follow along with the tutorial:

The application will have two major features. The first will enable users to add their phone numbers to the list of numbers to receive daily quotes. The second will be a cron job to send out the ChatGPT-generated quotes to users' phone numbers.

To achieve this, we will create a custom Laravel command to get phone numbers, send chat GPT-generated inspirational quotes to them, and create a scheduler task to run this command at regular intervals.

Project setup and configuration

Let's dive right into and build this application. To get started, we need to create a fresh Laravel application and configure the database. Create a new Laravel project and change into the new project directory using the commands below.

composer create-project laravel/laravel twilio_ai_quote_app
cd twilio_ai_quote_app

Database setup and configuration

We next need to set up and configure our database. First, create a new database on your MySQL server. Then, modify the default database configuration, which can be found in the .env file, to match corresponding values of your MySQL server.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=<<DB_NAME>>
DB_USERNAME=<<DB_USER_NAME>>
DB_PASSWORD=<<DB_PASSWORD>>

Assuming the example configuration above, make sure you replace <<DB_NAME>>, <<DB_USER_NAME>>, <<DB_PASSWORD>> with.

Add phone numbers to the list

Let's proceed to create the first section of the application that allows users to register their phone numbers, and receive our AI-generated daily inspirational quotes. To get started, let's create a controller, model, and migration using the command below.

php artisan make:model Subscribe -cm

The command above will create a Subscribe.php file in app/Models directory, SubscribeController.php file in app/Http/Controllers and _create_subscribes_table.php in database/migrations. Let's proceed to update the contents of these files.

Open the newly created migration file above and replace the contents with the following.

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('subscribes', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('phone')->unique();
            $table->string('preference')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('subscribes');
    }
};

The next thing to do is to open app/Http/Controllers/SubscribeController.php and replace the content with the following.

<?php

namespace App\Http\Controllers;

use App\Models\Subscribe;
use Illuminate\Http\Request;

class SubscribeController extends Controller
{
    public function index()
    {
        return view('subscribe');
    }

    public function save(Request $request)
    {
        $request->validate([
            'name' => 'required | string | max:255',
            'phone' => ['required', 'string', 'max:255', 'unique:' . Subscribe::class],
        ]);
        Subscribe::create([
            'name' => $request->name,
            'phone' => $request->phone,
        ]);
        return redirect()->route('list')->with('success', 'Phone number added to list!');
    }
}

In the controller class above, we created two methods: index() and save(). The index() method returns a Blade template, subscribe.blade.php, (which will be created shortly) that contains the HTML for the form users need to register new phone numbers.

The save() method validates the request, stores the request data in the database, and redirects the user back to the page with a success message or error in the case of a validation error.

There's one more step before the request data can be stored in the database. We need to add the name and phone values to the protected $fillable = [''] property. In app/Models/Subscribe.php, replace the contents with the code below.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Subscribe extends Model
{
    use HasFactory;
    protected $fillable = ['name', 'phone'];
}

We also need to run our migration to ensure the DB schema is created. Run migration using the command below

php artisan migrate

Let's create a Blade template to display the form for adding new phone numbers to the recipient list. To keep things a bit organized, let's create a layout file. This will be a Blade component. with the command below.

php artisan make:component layout

The command above will create two files: app/View/Components/layout.php and resources/views/components/layout.blade.php. app/View/Components/layout.php contains a render() function that returns the view to the resources/views/components/layout.blade.php.

Replace the contents of resources/views/components/layout.blade.php with the code below.

<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Laravel</title>
        <link rel="preconnect" href="https://fonts.bunny.net">
        <link href="https://fonts.bunny.net/css?family=figtree:400,600&display=swap" rel="stylesheet" />
        <script src="https://cdn.tailwindcss.com"></script>
    </head>
   <body class="bg-slate-100">
    <div class="max-w-3xl mx-auto pt-6">
        {{ $slot }}
    </div>
   </body>
</html>

In the code above, we added the HTML elements needed for every page. We also imported the Tailwind CSS library using the Tailwind CSS CDN. This way, Tailwind CSS becomes available on every page. To use the layout file, we need to add it as a Blade component to the rest of the pages.

Let's proceed to build the form. Create a new file named subscribe.blade.php in the resource/views directory. Then, replace the content of the newly created file with the code below.

<x-layout>
    <div class="rounded shadow-xl p-6 bg-white text-grey-50 mt-[20%]">
        <div class="my-2">
            <h2 class="text-3xl font-bold text-gray-500">Add a phone number to list</h2>
        </div>
        @if(session('success'))
            <div class="bg-green-500 text-white p-4 mb-2">
                {{ session('success') }}
            </div>
        @endif
        @if ($errors->any())
            <div class="bg-red-500 text-white p-4 mb-2">
                <ul>
                    @foreach ($errors->all() as $error)
                        <li>{{ $error }}</li>
                    @endforeach
                </ul>
            </div>
        @endif
        <form action="{{ route('list.join') }}" method="POST">
            @csrf
            <div class="mb-4">
                <label class="block text-gray-700 text-sm font-bold mb-2" for="name">
                  Name
                </label>
                <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
                 name="name" 
                 type="text" 
                 placeholder="Name">
            </div>
            <div class="mb-4">
                <label class="block text-gray-700 text-sm font-bold mb-2" for="phone_number">
                  Phone Number
                  <div class="font-thin">Enter phone number with country code without the (+) sight</div>
                  <div class="font-thin">Eg: 123XXXXXXX not +123XXXXXXX</div>
                </label>
                <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
                 name="phone" 
                 type="number" 
                 placeholder="Phone number">
              </div>
              <div class="mb-4">
                <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" type="submit">
                    Join List
                  </button>
              </div>
        </form>
    </div>
</x-layout>

We now need two routes; one to display the form, and the other to handle the submit request. To define them, in routes/web.php add the following.

Route::get('/', [SubscribeController::class, 'index'])->name('list');
Route::post('/join', [SubscribeController::class, 'save'])->name('list.join');

Then, add the following use statement at the top of the file:

use App\Http\Controllers\SubscribeController;

At this point, we can serve our application and make sure the form submission works before proceeding, by using the command below.

php artisan serve

In a different terminal run these commands also.

npm install
npm run dev

If you have done everything right, if you open http://localhost:8000, it should look just like the image below.

Integrate with Twilio for SMS functionality

Now, we can add users to receive daily inspirational quotes in our application. With that out of the way, let's proceed to implement the SMS sending feature using Twilio Programmable Messaging.

First, we need to install the Twilio PHP Helper Library. Do that with the Composer command below.

composer require twilio/sdk

Let's create a service class for our Twilio implementation. Create a new Services folder in the app directory. Then, create a new file named TwilioSms.php in the Services folder. After that, copy and paste the code below into app/Services/TwilioSms.php.

<?php
namespace App\Services;

use App\Models\Subscribe;
use Twilio\Rest\Client;

class TwilioSms
{
    protected $twilioSid;
    protected $twilioAuthToken;
    protected $twilioPhoneNumber;
    protected $twilio;

    public function __construct()
    {
        $this->twilioSid = env("TWILIO_ACCOUNT_SID");
        $this->twilioAuthToken = env("TWILIO_AUTH_TOKEN");
        $this->twilioPhoneNumber = env("TWILIO_PHONE_NUMBER");
        $this->twilio =  new Client($this->twilioSid, $this->twilioAuthToken);
    }

    public function sendSms($aiGeneratedQuote)
    {
        try {
            $users = Subscribe::all();
            foreach ($users as $user) {
                $this->twilio->messages
                    ->create(
                        '+' . $user->phone, // to
                        [
                            "body" => $aiGeneratedQuote,
                            "from" => $this->twilioPhoneNumber //twilio num
                        ]
                    );
            }
            echo $aiGeneratedQuote." Quotes sent successfully";
        } catch (\Exception $e) {
            return $e->getMessage();
        }
    }
}

The class' constructor uses your Twilio Account SID and Auth Token to create an authenticated connection to Twilio. The sendSms() method accepts the AI-generated inspirational quote as an argument, fetches all the phone numbers from the database, and sends the quote to them one after the other using a for-each loop.

The code above pulls in our Twilio credentials from the .env file. Let's add these details to thefile by adding the following lines of code, then replacing the three placeholders with our actual Twilio credentials.

TWILIO_ACCOUNT_SID="<<TWILIO ACCOUNT SID>>"
TWILIO_AUTH_TOKEN="<<TWILIO ACCOUNT AUTH TOKEN>>"
TWILIO_PHONE_NUMBER="<<TWILIO PHONE NUMBER>>"

You can find these details on your Twilio Console dashboard, as shown in the image below.

Integrate OpenAI for content generation

Just like the Twilio service class above, let's create one for the OpenAI API call. In the app/Services directory create a new file named AiQuote.php and add the code below to the class.

<?php

namespace App\Services;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;

class AiQuote
{
    protected $client;
    protected $apiKey;
    protected $url;
    Protected $gptModel;

    public function __construct()
    {
        $this->apiKey = env('OPEN_AI_API_KEY');
        $this->url = env('OPEN_AI_API_URL');
        $this->gptModel = env('YOUR_GPT_MODEL');
        $this->client = new Client();
    }

    public function generateMotivationalQuote()
    {
        $payload = [
            'model' => $this->gptModel,
            'messages' => [
                [
                  "role" => "system",
                  "content" => "<<REPLACE WITH YOUR PROMPT>>",
                ],
                [
                  "role" => "user",
                  "content" => "<<REPLACE WITH YOUR PROMPT>>"                  
                ]
            ]
        ];

        try {
            $response = $this->client->post($this->url.'completions', [
                'headers' => [
                    'Authorization' => 'Bearer ' . $this->apiKey,
                    'Content-Type' => 'application/json',
                ],
                'json' => $payload,
            ]);

            $body = json_decode($response->getBody(), true);
            return $body['choices'][0]['message']['content'] ?? 'No quote could be generated.';
        } catch (GuzzleException $e) {
            return 'Failed to generate quote: ' . $e->getMessage();
        }
    }
}

Then, add the OpenAI credentials, below, to your .env file, and replace `<>` with your OpenAI API key.

OPEN_AI_API_KEY=<<OPEN_AI_API_KEY>>
OPEN_AI_API_URL="https://api.openai.com/v1/chat/"
YOUR_GPT_MODEL=<<YOUR GPT MODEL>>

Then, <<REPLACE WITH YOUR PROMPT>> should be replaced with your prompt.

In the AiQuote.php service, we connected to the OpenAI API from the __controller() method using the API key and URL from the .env file. In the generateMotivationalQuote() method, we made the actual API call with the API key passed along in the header to generate a motivational quote.

Schedule the daily inspirational messages

Now we have our services for generating inspirational quotes and sending SMS to registered phone numbers. The final two steps would be to create an Artisan Console command to fire off the quote generation, SMS sending, and schedule that command to run at a scheduled interval.

Let's start by creating the Artisan command using the command below.

php artisan make:command SendAiQuote

The command will create a new command class name SendAiQuote.php in the app/Console/Commands directory. Open this file; we are mostly concerned about the ​​handle() method. Then, replace the handle() method with the code below.

public function handle(\App\Services\AiQuote $AiQuote, \App\Services\TwilioSms $twilioSms)
{
    $generatedMotivationalQuote = $AiQuote->generateMotivationalQuote();
    $twilioSms->sendSms($generatedMotivationalQuote);
}

We passed the AiQuote and TwilioSms service classes as arguments to the `handle()` method. Then, we generated a quote by calling the generateMotivationalQuote() method on $AiQuote. The return value is passed to the sendSms() method of the $twilioSms service, which loops through the phone numbers and sends the quotes as an SMS.

Now, we need to schedule this command to run daily. To do this, replace the content of app/Console/Kernel.php with the code below.

<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    /**
     * The Artisan commands provided by your application.
     *
     * @var array
     */
    protected $commands = [
        \App\Console\Commands\SendAiQuote::class,
    ];

}

Then, in routes/console.php, add the following to the bottom of the file:

\Illuminate\Support\Facades\Schedule::command('app:send-ai-quote')
	->daily()
	->sendOutputTo('storage/task-output.log');

These changes fire our Artisan command every 24 hours (daily). Each time, the request's result will be stored in a log file named task-output.log located in the storage directory directory.

Before testing, we need to reconfigure the frequency of the scheduled task from daily to everyMinute so that we won't have to wait for a whole day before we can see the result. In routes/console.php, update the call to Schedule::command with this:

\Illuminate\Support\Facades\Schedule::command('app:send-ai-quote')
    ->everyMinute()
    ->sendOutputTo('task-output.log');

Test that the application works as expected

After reconfiguring our cron job to fire every minute, run the following command to run the scheduled code in the foreground until the command is canceled.

php artisan schedule:work

This command works for testing on a local machine. Check the Laravel documentation for a detailed explanation of how to configure this for a production environment.

You should receive the SMS in the test phone number in less than no time.

To test with a Twilio trial account, you need at least one user in your applications database. The user's phone number must be a verified Twilio phone number (Twilio callerID)

That's how to create a daily inspirational quote app using Laravel, Twilio, and OpenAI's API

We've successfully developed a daily inspirational quote app leveraging Laravel, Twilio, and the OpenAI API. This guide walked you through several key steps:

  • Setting up a new Laravel application and configuring its database
  • Implementing a functionality that allows users to register their phone numbers
  • Utilizing OpenAI to generate motivational quotes and dispatching them as SMS messages through Twilio's programmable voice service
  • Crafting an artisan command to automate the task, which is programmed to execute daily

This project not only demonstrates the practical integration of Laravel, Twilio, and OpenAI to create a meaningful application but also lays the groundwork for further innovation. Feel free to delve deeper into the capabilities of these technologies.

Moses Anumadu is a software developer and online educator who loves to write clean, maintainable code. He's creating something awesome for Laravel developers with Laravel.