When you observe that a user visits your website but does not return after registration, you can reach out to them through various channels, including sending follow-up messages via SMS, email, or phone calls. Informing them about what is new on your website may help bring them back and increase your website traffic.
In this article, you will learn how to send periodic messages to users who are inactive in a Symfony application using Twilio Programmable Voice.
Prerequisites
To complete this tutorial, you will need the following:
PHP 8.1 or higher (ideally PHP 8.3)
Composer installed globally
The Symfony CLI
A Twilio account. You can create a Twilio free trial account if you don't already have one.
A MySQL database
Create a new Symfony project
To get started with the tutorial, you need to create a new Symfony project by running the command below in your terminal.
```bash
Symfony new User_follow_up_app --version="7.0.*" --webapp
```
After running the command above, you need to navigate to the application folder and start the application development server using the commands below:
```bash
cd User_follow_up_app
symfony server:start
```
Next, open localhost:8000 in your browser. You should see the application's default welcome page as shown in the image below.
Database configuration
To connect the application to the MySQL database, open the project in your code editor and then open the .env file from the project's root directory.
Inside the .env file, comment out DATABASE_URL
for Postgresql. Then uncomment the DATABASE_URL
for MySQL
and update the database username and password with your actual MySQL
database values.
```bash
DATABASE_URL="mysql://<database_user>:<database_password>@127.0.0.1:3306/users"
```
Next, open another terminal tab and run the command below to complete the database configuration.
```bash
php bin/console doctrine:database:create
```
Create a member entity
Let’s create the database table schema and a property called entity that will hold information about the registered users. You can create the userInfo
entity by running the command below:
```bash
php bin/console make:entity
```
This command will prompt you to enter the entity name and the database table properties. Set them as shown in the screenshot below.
Next, run the migration commands below to connect the application entity to the MySQL database server.
```bash
php bin/console make:migration
php bin/console doctrine:migrations:migrate
```
The migration command above will prompt you to confirm whether you want to continue with the migration or not. Answer 'yes' to complete the migration.
Create the application form type
You need to create a form type to handle form input in a Symfony application. This form type is used to define the input fields, their types, validation rules, and other form attributes.
Let’s create register and login form types. To create the register form type, open the project in a code editor, navigate to the src folder, and create a new folder named Form. Then, inside the folder, create a RegisterType.php file and add the code below.
```php
<?php
namespace App\Form;
use App\Entity\UserInfo;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
class RegisterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('fullname')
->add('email')
->add('phone')
->add('password', PasswordType::class)
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Userinfo::class,
]);
}
}
```
Next, to create the login form type, inside the src/Form folder, create a LoginType.php file and add the code below.
```php
<?php
namespace App\Form;
use App\Entity\UserInfo;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
class LoginType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('email')
->add('password', PasswordType::class)
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Userinfo::class,
]);
}
}
```
Retrieve Twilio API token
To retrieve your Twilio API access token, log in to your Twilio Console dashboard. You will find the API token under the account info section, as shown in the screenshot below.
Copy the access token somewhere safe; you will use it in the next section.
Securing the Twilio access token in the .env file
To ensure that the access token is secure, let’s store it in the .env file. To do that, from the application's root directory, open the .env file and add the following environment variable.
```bash
TWILIO_ACCOUNT_SID=<twilio_account_sid>
TWILIO_AUTH_TOKEN=<twilio_auth_token>
TWILIO_PHONE_NUMBER=<twilio_phone_number>
```
In the code above, replace the placeholders <twilio_account_sid>
, <twilio_auth_token>
, and <twilio_phone_number>
with your actual Twilio access token.
Installing Twilio SDK
Next, let’s install the Twilio PHP helper library, which will be used to interact with Twilio API endpoints, including programmable voice. To install the SDK, run the command below:
```bash
composer require twilio/sdk
```
Creating the application controller
Now, let’s create a controller that will handle the application login. You can create the controller file by running the command below.
```bash
php bin/console make:controller UserFollowUP
```
Navigate to the src/controller directory. You will see the generated controller file named UserFollowUPController.php. Open the file and replace its code with the following.
```php
<?php
namespace App\Controller;
use App\Entity\UserInfo;
use App\Form\LoginType;
use App\Form\RegisterType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Twilio\Rest\Client;
class UserFollowUPController extends AbstractController
{
private $entityManager;
private $passwordEncoder;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
#[Route('/register', name: 'app_user_follow_up')]
public function register(Request $request,SessionInterface $session): Response
{
$user = new UserInfo();
$form = $this->createForm(RegisterType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$currentDate = (new \DateTime())->format('Y-m-d H:i:s');
$user->setLastSeen($currentDate);
$this->entityManager->persist($user);
$this->entityManager->flush();
$session->set('user', $user);
return $this->redirectToRoute('dashboard');
}
return $this->render('user_follow_up/register.html.twig', [
'form' => $form->createView(),
]);
}
#[Route('/login', name: 'app_login')]
public function login(Request $request, SessionInterface $session): Response
{
$form = $this->createForm(LoginType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$email = $password = $form->get('email')->getData();
$password = $password = $form->get('password')->getData();
$UserInfo = new UserInfo();
$repository = $this->entityManager->getRepository(UserInfo::class);
$user = $repository->findOneBy([
'email' => $email,
'password' => $password,
]);
if ($user) {
$currentDate = (new \DateTime())->format('Y-m-d H:i:s');
$user->setLastSeen($currentDate);
$this->entityManager->persist($user);
$this->entityManager->flush();
return $this->redirectToRoute('dashboard');
} else {
return $this->redirectToRoute('app_login');
}
}
return $this->render('user_follow_up/login.html.twig', [
'form' => $form->createView(),
]);
}
#[Route('/dashboard', name: 'dashboard')]
public function dashboard(SessionInterface $session): Response
{
if (null !== $session->get('user')) {
$user = $session->get('user');
return $this->render('user_follow_up/dashboard.html.twig', [
'user' => $user,
]);
}else{
return $this->redirectToRoute('app_login');
}
}
#[Route('/follow-up', name: 'app_follow_up')]
public function followUp(): Response
{
$twilioSid = $_ENV['TWILIO_ACCOUNT_SID'];
$twilioToken = $_ENV['TWILIO_AUTH_TOKEN'];
$twilioNumber = $_ENV['TWILIO_PHONE_NUMBER'];
$twilioClient = new Client($twilioSid, $twilioToken);
$userRepository = $this->entityManager->getRepository(UserInfo::class);
$inactiveUsers = $userRepository->createQueryBuilder('u')
->where('u.lastSeen <= :oneMonthAgo')
->setParameter('oneMonthAgo', new \DateTime('-1 month'))
->getQuery()
->getResult();
foreach ($inactiveUsers as $user) {
$phoneNo = $user->getPhone();
$fullname = $user->getFullname();
$call = $twilioClient->calls->create(
$phoneNo,
$twilioNumber,
[
'twiml' => '<Response><Say>Hi '.$fullname.', We noticed that you have not logged in to our platform for a while. Please log in to your dashboard to explore new features.</Say></Response>'
]
);
}
return new Response('Follow-up completed.');
}
}
```
In the code above:
The
register()
method handles user registration details and stores them in the database.The
login()
method handles the user login process and updates the "last seen" timestamp of the user to the current login time.The
dashboard()
method is used to display the user details on their dashboard page.While the
followUp()
method is used to create a cron job endpoint that calls all users who are not active within a specified period.
Create the application templates
Now, we need to create template files that will render register, login and dashboard . To do that, navigate to the templates/user_follow_up directory and create the following files.
register.html.twig
login.html.twig
dashboard.html.twig
Add the code below to register.html.twig
```html
{# templates/user_follow_up/register.html.twig #}
{% extends 'base.html.twig' %}
{% block title %}User Registration{% endblock %}
{% block body %}
<div class="registration-form">
<h1>User Registration</h1>
{{ form_start(form, {'attr': {'class': 'form'}}) }}
<div class="form-group">
{{ form_row(form.fullname, {'attr': {'class': 'form-control', 'placeholder': 'Full Name'}}) }}
</div>
<div class="form-group">
{{ form_row(form.phone, {'attr': {'class': 'form-control', 'placeholder': 'Phone'}}) }}
</div>
<div class="form-group">
{{ form_row(form.email, {'attr': {'class': 'form-control', 'placeholder': 'Email'}}) }}
</div>
<div class="form-group">
{{ form_row(form.password, {'attr': {'class': 'form-control', 'placeholder': 'Password'}}) }}
</div>
<button type="submit" class="btn btn-primary">Register</button>
<p>Already a member? <a href="{{ path('app_login') }}">Login</a></p>
{{ form_end(form) }}
</div>
{% endblock %}
```
Add the code below to login.html.twig
```html
{# templates/user_follow_up/login.html.twig #}
{% extends 'base.html.twig' %}
{% block title %}Login{% endblock %}
{% block body %}
<div class="login-form">
<h1>Login</h1>
{{ form_start(form, {'attr': {'class': 'form'}}) }}
<div class="form-group">
{{ form_row(form.email, {'attr': {'class': 'form-control', 'placeholder': 'Email'}}) }}
</div>
<div class="form-group">
{{ form_row(form.password, {'attr': {'class': 'form-control', 'placeholder': 'Password'}}) }}
</div>
<button type="submit" class="btn btn-primary">Login</button>
<p>Don't have an account? <a href="{{ path('app_user_follow_up') }}">Register</a></p>
{{ form_end(form) }}
</div>
{% endblock %}
```
Add the code below to dashboard.html.twig
```html
{# templates/dashboard/index.html.twig #}
{% extends 'base.html.twig' %}
{% block title %}Dashboard{% endblock %}
{% block body %}
<div class="container">
<div class="jumbotron">
<h1 class="display-4">Welcome to Your Dashboard</h1>
<p class="lead">Email: {{ user.fullname }}</p>
<p class="lead">Email: {{ user.email }}</p>
<p class="lead">Phone Number: {{ user.phone }}</p>
<hr class="my-4">
<p>This is where you can manage your account and perform various actions.</p>
</div>
</div>
{% endblock %}
To add style to the application, within the templates/user_follow_up directory, open base.html.twig and replace its existing code with the following.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="stackpath.bootstrapcdn.com/bootstrap/4.5.2/..">
</head>
<body>
<div class="container">
{% block body %}{% endblock %}
</div>
<script src="code.jquery.com/jquery-3.5.1.slim.min.js" </script>
<script src="cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/d.." ></script>
<script src="stackpath.bootstrapcdn.com/bootstrap/4.5.2/.." ></script>
</body>
</html>
```
Tunnel the application
Let's make the application accessible over the internet using Ngrok. To do that run the command below.
```bash
ngrok http localhost:8000
```
Running the above command will generate a Forwarding URL
as shown in the image below.
Copy the Forwarding URL
somewhere, you will need it in the next section.
Schedule the follow-up call
Finally, let’s set up a cron job that runs at specific intervals using Cron-job. Cron-job is an online tool that allows developers to run cron jobs freely without installing any packages. To set up the cron job, log in to your Cron-job dashboard and click on the CREATE CRONJOB
button located at the top right of the dashboard page.
Next, on the Create cronjob
page, configure the cron job as follows:
Title: Follow-up call
URL: Paste the generated forward URL and add
follow-up
at the end.Execution schedule: select every 10 minutes
Then, click on the CREATE
button to save the configuration and start running the cron.
Test the application
To test the application, open http://localhost:8000/register on your browser and create a new account as shown in the GIF below.
After logging in to your dashboard, close the application tab. If you remain inactive for about 10 minutes, you will receive a follow-up call asking you to log in to your account to explore newly added features.
That is how to Send a follow-up call to users in the Symfony app using Twilio
Conclusion
In this article, you have learned how to send follow-up call messages to users who are not active in a Symfony application using Twilio Programmable Voice. This enhances the user experience and provides a more personal touch to your application. Remember, effective communication is key to user retention and engagement.
BioData
I am David Adewale, a software developer and and a technical writer passionate about extending my knowledge to the developers community through technical writing. Here is my LinkedIn profile.