Appointment Reminder

| Python | Node | Ruby |


Python

15 minutes build time || Difficulty Level: Intermediate || Github Repo

Tutorial Pre-reqs

Configuration

Firstly, create a config_file.cfg file in your project directory. Flask will load this at startup.

Copy
Copied
API_KEY='YOUR_API_KEY'
FROM_NUMBER='YOUR_TELNYX_NUMBER'

Note: This file contains a secret key, it should not be committed to source control.

We’ll also place Flask in debug mode and assume all numbers are in the U.S.

Copy
Copied
DEBUG=True
COUNTRY_CODE='+1'

Note: After pasting the above content, Kindly check and remove any new line added

Install the necessary packages for the application

Copy
Copied
$ pip install telnyx
$ pip install flask
$ pip install celery
$ pip install redis

Note: After pasting the above content, Kindly check and remove any new line added

Server Initialization

Create a file schedule_meeting_server.py and install the basic requirements

The first piece of our application sets up the Telnyx library, Flask, and Celery.

Copy
Copied
import uuid
from datetime import datetime, timedelta

import telnyx
from celery import Celery
from flask import Flask, request, render_template, flash

app = Flask(__name__)
app.secret_key = uuid.uuid4()
app.config.from_pyfile('config_file.cfg')
celery = Celery('schedule_meeting_server', broker='redis://localhost:6379')

telnyx.api_key = app.config['API_KEY']

Note: After pasting the above content, Kindly check and remove any new line added

Collect User Input

We'll create a simple HTML form which collects the meeting date, time, customer name, and phone number.

The Flask route: @app.route('/', methods=['GET', 'POST']) will serve the files created in the folder "templates"

Copy
Copied
$ mkdir templates
$ touch templates/index.html
$ touch templates/success.html

Note: After pasting the above content, Kindly check and remove any new line added

index.html

Inside the newly created templates/index.html

Copy
Copied
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html> <head>
<meta charset="utf-8">
<title>Telnyx Meeting Scheduler</title>
</head>

<body>
<!--If form submission results in error, flash error message -->
{% with messages = get_flashed_messages() %}
  {% if messages %}
    <ul class=flashes>
    {% for message in messages %}
      <li>{{ message }}</li>
    {% endfor %}
    </ul>
  {% endif %}
{% endwith %}

<div>
  <h1>Telnyx Meeting Scheduling</h1>
  <form action="{{ url_for('schedule_meeting') }}" method="post">
  <div>
    <label for="name">Your name: </label>
    <input type="text" name="customer_name" required>
  </div>
  <div>
    <label for="treatment">Meeting name: </label>
    <input type="text" name="meeting_name" required>
  </div>
  <div>
    <label for="meeting-date">Appointment date: </label>
    <input type="date" id="meeting_date" name="meeting_date" required>
  </div>
  <div>
    <label for="meeting_time">Meeting time: </label>
    <input type="time" id="meeting_time" name="meeting_time" required>
  </div>
  <div>
    <label for="phone">Your mobile number: </label>
    <input type="tel" id="phone" name="phone" required>
  </div>
  <div>
    <input type="submit" value="Schedule now!">
  </div>
  </form>

</div>

</body> </html>

Note: After pasting the above content, Kindly check and remove any new line added

success.html

Inside templates/success.html

Copy
Copied
<html>
  <head>
    <title>Telnyx Meeting Scheduler</title>
  </head>
<body>
    <p>{{name}}, thanks for booking a meeting for <em>{{meeting_name}}</em>!</p>
    <p>You are scheduled for {{meetingDT}} and will receive a courtesy reminder at {{phone}} three hours before your meeting.</p>
  </body>
</html>

Note: After pasting the above content, Kindly check and remove any new line added

Implement the SMS Notification

Create a simple function that sends an SMS message parameterized on the destination number and message. The decorator @celery.task allows us to schedule this function to run in the future.

Copy
Copied
@celery.task
def send_reminder(to, message):
    telnyx.Message.create(
        to=to,
        from_=app.config['FROM_NUMBER'],
        text=message
    )

Note: After pasting the above content, Kindly check and remove any new line added

Note: from is a reserved word in Python. The Telnyx Python Library adds an underscore character to any parameter that would conflict with a reserved keyword.

Parse User Input and Schedule the Message

Setup our route which will handle both GET and POST requests.

Copy
Copied
@app.route('/', methods=['GET', 'POST'])
def schedule_meeting():
    if request.method == "POST":
        # ...
    return render_template('index.html')

Note: After pasting the above content, Kindly check and remove any new line added

Now, within the conditional, first parse the user date/time input.

Copy
Copied
meeting_date = datetime.strptime(request.form['meeting_date'], '%Y-%m-%d')
meeting_time = datetime.strptime(request.form['meeting_time'], '%H:%M').time()
meeting_datetime = datetime.combine(meeting_date, meeting_time)

Note: After pasting the above content, Kindly check and remove any new line added

Next, only allow meetings to be scheduled that are three hours and five minutes in the future or later.

Copy
Copied
now = datetime.now()
if meeting_datetime - timedelta(hours=3, minutes=5) < now:
    flash('Appointment time must be at least 3:05 hours from now')
    return render_template('index.html')

Note: After pasting the above content, Kindly check and remove any new line added

Then, compute the reminder time and message, and schedule the reminder.

Remind the User

Remind the user 3 hours before the meeting.

Copy
Copied
reminder_datetime = meeting_datetime - timedelta(hours=3)

message = "{customer_name}, you have a meeting scheduled for {meeting_time}".format(customer_name=request.form['customer_name'], meeting_time=str(meeting_datetime))
to = "{country_code}{phone}".format(country_code=app.config['COUNTRY_CODE'], phone=request.form['phone'])

send_reminder.apply_async([to, message], eta=reminder_datetime)

Note: After pasting the above content, Kindly check and remove any new line added

Finally, render the success template.

Copy
Copied
return render_template('success.html',
                       name=request.form['customer_name'],
                       meeting_name=request.form['meeting_name'],
                       phone=request.form['phone'],
                       meeting_datetime=str(meeting_datetime))

Note: After pasting the above content, Kindly check and remove any new line added

And at the end of the file, start the server.

Copy
Copied
if __name__ == '__main__':
    app.run(port=5010)

Note: After pasting the above content, Kindly check and remove any new line added

Final schedulemeetingserver.py

All together your schedule_meeting_server.py file should look something like:

Copy
Copied
import uuid
from datetime import datetime, timedelta

import telnyx
from celery import Celery
from flask import Flask, request, render_template, flash

app = Flask(__name__)
app.secret_key = uuid.uuid4()
app.config.from_pyfile('config_file.cfg')
celery = Celery('schedule_meeting_server', broker='redis://localhost:6379')

telnyx.api_key = app.config['API_KEY']

@celery.task
def send_reminder(to, message):
    telnyx.Message.create(
        to=to,
        from_=app.config['FROM_NUMBER'],
        text=message
    )

@app.route('/', methods=['GET', 'POST'])
def schedule_meeting():
    if request.method == "POST":
        meeting_date = datetime.strptime(request.form['meeting_date'], '%Y-%m-%d')
        meeting_time = datetime.strptime(request.form['meeting_time'], '%H:%M').time()
        meeting_datetime = datetime.combine(meeting_date, meeting_time)

        now = datetime.now()

        if meeting_datetime - timedelta(hours=3, minutes=5) < now:
            flash('Appointmenttest time must be at least 3:05 hours from now')
            return render_template('index.html')

        reminder_datetime = meeting_datetime - timedelta(hours=3)

        message = "{customer_name}, you have a meeting scheduled for {meeting_time}".format(customer_name=request.form['customer_name'], meeting_time=str(meeting_datetime))
        to = "{country_code}{phone}".format(country_code=app.config['COUNTRY_CODE'], phone=request.form['phone'])

        send_reminder.apply_async([to, message], eta=reminder_datetime)

        return render_template('success.html', name=request.form['customer_name'], meeting_name=request.form['meeting_name'],
                               phone=request.form['phone'], meeting_datetime=str(meeting_datetime))

    return render_template('index.html')

if __name__ == '__main__':
    app.run(port=5010)

Note: After pasting the above content, Kindly check and remove any new line added

Running the Project

Make sure redis is running in the background, and then start the Celery task and Python server. Assuming your code is in schedule_meeting_server.py.

In one terminal, launch the Celery worker (be sure to activate your virtual environment)

Copy
Copied
$ celery -A schedule_meeting_server.celery worker

Note: After pasting the above content, Kindly check and remove any new line added

Then launch the python flask server

Copy
Copied
$ python schedule_meeting_server.py

Note: After pasting the above content, Kindly check and remove any new line added

Node

15 minutes build time || Difficulty Level: Intermediate || Github Repo

Pre-reqs

Configuration

  1. Run the npm init command accepting the defaults
  2. npm install the necessary packages to run the application.
  3. Create a config.json file in your project directory
  4. Create a templates folder for our template views
  5. Create the index.js file to host the express application
  6. Create the index.html and success.html in the templates folder
Copy
Copied
$ npm init -y
$ npm i express
$ npm i express-nunjucks
$ npm i moment
$ npm i nunjucks
$ npm i telnyx
$ mkdir templates
$ touch config.json index.js templates/index.html templates/success.html

Note: After pasting the above content, Kindly check and remove any new line added

First, use this guide to provision an SMS number and messaging profile, and create an API key. Then add those to the config file, along with your country code.

Copy
Copied
{
    "API_KEY": "YOUR_API_KEY",
    "COUNTRY_CODE": "+1",
    "FROM_NUMBER": "YOUR_TELNYX_NUMBER"
}

Note: After pasting the above content, Kindly check and remove any new line added

Note: This file contains a secret key, it should not be committed to source control.

Server Initialization

First, import the config.json file and initialize the Telnyx library.

Copy
Copied
const config = require('./config.json');
const telnyx = require('telnyx')(config.API_KEY);

Note: After pasting the above content, Kindly check and remove any new line added

Then create an express app that watches the templates directory with Nunjucks, a simple templating language, and parses form data.

Copy
Copied
const moment = require('moment');
const express = require('express');
const expressNunjucks = require('express-nunjucks');
const app = express();
app.use(express.urlencoded());
app.set('views', `${__dirname}/templates`);

expressNunjucks(app, {
    watch: true,
    noCache: true,
});

Note: After pasting the above content, Kindly check and remove any new line added

Collect User Input

Fill out the index.html in the templates folder with a simple HTML form, which collects the meeting date, time, customer name, and phone number.

Copy
Copied
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
    <head>
        <meta charset="utf-8">
        <title>Telnyx Meeting Scheduler</title>
    </head>
    <body>
        <!--If form submission results in error, flash error message -->
        {% if message %}
            <h2>{{ message }}</h2>
        {% endif %}
        <div>
        <h1>Telnyx Meeting Scheduling</h1>
        <form action="/" method="post">
        <div>
            <label for="name">Your name: </label>
            <input type="text" name="customer_name" >
        </div>
        <div>
            <label for="treatment">Meeting name: </label>
            <input type="text" name="meeting_name" >
        </div>
        <div>
            <label for="meeting-date">Appointment date: </label>
            <input type="date" id="meeting_date" name="meeting_date" >
        </div>
        <div>
            <label for="meeting_time">Meeting time: </label>
            <input type="time" id="meeting_time" name="meeting_time" >
        </div>
        <div>
            <label for="phone">Your mobile number: </label>
            <input type="tel" id="phone" name="phone" >
        </div>
        <div>
            <input type="submit" value="Schedule now!">
        </div>
        </form>

        </div>
    </body>
</html>

Note: After pasting the above content, Kindly check and remove any new line added

Then back in index.js serve the index file with a new express route.

Copy
Copied
app.get('/', (req, res) => {
    res.render('index');
});

Note: After pasting the above content, Kindly check and remove any new line added

Implement the SMS Notification

Create a function that sends an SMS message parameterized on the destination number and text.

Copy
Copied
function sendReminder(to, message) {
    telnyx.messages.create({
        to: `${config.COUNTRY_CODE}${to}`,
        from: config.FROM_NUMBER,
        text: message
    });
}

Note: After pasting the above content, Kindly check and remove any new line added

Parse User Input and Schedule the Message

Within the POST handler, parse the meeting time and compute how far into the future it is. We're using the moment Javascript library for this. Note that moment will use the current timezone for both datetime instances.

Copy
Copied
app.post('/', (req, res) => {
    const meetingDatetime = moment(
        `${req.body.meeting_date} ${req.body.meeting_time}`,
        'YYYY-MM-DD hh:mm');
    const now = moment();

    const delta = moment.duration(meetingDatetime.diff(now));
    // ...
});

Note: After pasting the above content, Kindly check and remove any new line added

If the meeting is sooner than 3 hours, 5 minutes from now, return an error.

Copy
Copied
if (delta < moment.duration({hours: 3, minutes: 5})) {
    res.render('index', {message: 'Can only schedule meetings at least 3 hours, 5 minutes in the future'});
} else {
    // ...
}

Note: After pasting the above content, Kindly check and remove any new line added

Remind the User

If the time is valid, compute when to send the reminder and schedule the function call. Note that we're using setTimeout here, but a production solution should make use of an asynchronous processing toolkit.

Copy
Copied
const reminderDatetime = meetingDatetime.subtract(3, 'hours');
const message = `${req.body.customer_name}, you have a meeting scheduled for ${meetingDatetime}`;

setTimeout(() => {
    sendReminder(req.body.phone, message);
}, reminderDatetime.diff(now));

Note: After pasting the above content, Kindly check and remove any new line added

Finally, fill out the success template success.html in the templates folder

Copy
Copied
<html>
  <head>
    <title>Telnyx Meeting Scheduler</title>
  </head>
<body>
    <p>{{name}}, thanks for booking a meeting for <em>{{meetingName}}</em>!</p>
    <p>You are scheduled for {{meetingDT}} and will receive a courtesy reminder at {{phone}} three hours before your meeting.</p>
  </body>
</html>

Note: After pasting the above content, Kindly check and remove any new line added

Then render the template after the setTimeout statement.

Copy
Copied
res.render('success', {
    name: req.body.customer_name,
    meetingName: req.body.meeting_name,
    meetingDT: meetingDatetime,
    phone: req.body.phone
});

Note: After pasting the above content, Kindly check and remove any new line added

Start the Server

Start the Express app.

Copy
Copied
const port = 3000;
app.listen(port, () => console.log(`App running on port ${port}`));

Note: After pasting the above content, Kindly check and remove any new line added

Final index.js

All together your index.js file should look something like:

Copy
Copied
const config = require('./config.json');
const telnyx = require('telnyx')(config.API_KEY);
const moment = require('moment');
const express = require('express');
const expressNunjucks = require('express-nunjucks');

const app = express();
app.use(express.urlencoded());
app.set('views', `${__dirname}/templates`);

expressNunjucks(app, {
    watch: true,
    noCache: true,
});

app.get('/', (req, res) => {
    res.render('index');
});

function sendReminder(to, message) {
    telnyx.messages.create({
        to: `${config.COUNTRY_CODE}${to}`,
        from: config.FROM_NUMBER,
        text: message
    });
}

app.post('/', (req, res) => {
    const meetingDatetime = moment(
        `${req.body.meeting_date} ${req.body.meeting_time}`,
        'YYYY-MM-DD hh:mm');
    const now = moment();

    const delta = moment.duration(meetingDatetime.diff(now));
    if (delta < moment.duration({hours: 3, minutes: 5})) {
        res.render('index', {message: 'Can only schedule meetings at least 3 hours, 5 minutes in the future'});
    } else {
        const reminderDatetime = meetingDatetime.subtract(3, 'hours');
        const message = `${req.body.customer_name}, you have a meeting scheduled for ${meetingDatetime}`;

        setTimeout(() => {
            sendReminder(req.body.phone, message);
        }, reminderDatetime.diff(now));

        res.render('success', {
            name: req.body.customer_name,
            meetingName: req.body.meeting_name,
            meetingDT: meetingDatetime,
            phone: req.body.phone
        });
    }
});

const port = 3000;
app.listen(port, () => console.log(`App running on port ${port}`));

Note: After pasting the above content, Kindly check and remove any new line added

Running the Project

Launch the index.js file from the command line and open your browser to http://localhost:3000 to set an appointment reminder.

Copy
Copied
$ node index.js
App running on port 3000

Note: After pasting the above content, Kindly check and remove any new line added

Ruby

15 minutes build time || Difficulty Level: Intermediate || Github Repo

Pre-reqs

Configuration

Create a config.yml file in your project directory.

Copy
Copied
$ touch config.yml

Note: After pasting the above content, Kindly check and remove any new line added

Then, use this guide to provision an SMS number and messaging profile, and create an API key. Then add those to the config.yml file, along with your country code.

Copy
Copied
api_key: "YOUR_API_KEY"
country_code: "+1"
from_number: "YOUR_TELNYX_NUMBER"

Note: After pasting the above content, Kindly check and remove any new line added

Server Initialization

Create a scheduler.rb file for the application and install the Gems needed to run the application.

Copy
Copied
$ touch scheduler.rb
$ gem install 'telnyx'
$ gem install 'sinatra'
$ gem install 'rufus-scheduler'
$ gem install 'activesupport'

Note: After pasting the above content, Kindly check and remove any new line added

The first piece of our application loads the config YAML file, configures Telnyx, and starts a Rufus scheduler.

Copy
Copied
require 'sinatra'
require 'date'
require 'active_support/all'
require 'rufus/scheduler'
require 'telnyx'
require 'yaml'

$config = YAML.load(File.open('config.yml').read)
Telnyx.api_key = $config['api_key']
scheduler = Rufus::Scheduler.new

Note: After pasting the above content, Kindly check and remove any new line added

Collect User Input

Create a simple HTML form within a views folder named index.erb which collects the meeting date, time, customer name, and phone number.

Copy
Copied
$ mkdir views
$ touch views/index.erb

Note: After pasting the above content, Kindly check and remove any new line added

The index.erb file should look something like

Copy
Copied
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
    <head>
        <meta charset="utf-8">
        <title>Telnyx Meeting Scheduler</title>
    </head>
    <body>
        <% if defined?(message) %>
            <h2><%= message %></h2>
        <% end %>
        <div>
        <h1>Telnyx Meeting Scheduling</h1>
        <form action="/" method="post">
        <div>
            <label for="name">Your name: </label>
            <input type="text" name="customer_name" >
        </div>
        <div>
            <label for="treatment">Meeting name: </label>
            <input type="text" name="meeting_name" >
        </div>
        <div>
            <label for="meeting-date">Appointment date: </label>
            <input type="date" id="meeting_date" name="meeting_date" >
        </div>
        <div>
            <label for="meeting_time">Meeting time: </label>
            <input type="time" id="meeting_time" name="meeting_time" >
        </div>
        <div>
            <label for="phone">Your mobile number: </label>
            <input type="tel" id="phone" name="phone" >
        </div>
        <div>
            <input type="submit" value="Schedule now!">
        </div>
        </form>

        </div>
    </body>
</html>

Note: After pasting the above content, Kindly check and remove any new line added

We'll serve it with Sinatra.

Copy
Copied
get '/' do
  erb :index
end

Note: After pasting the above content, Kindly check and remove any new line added

Implement the SMS Notification

Create a simple function that sends an SMS message parameterized on the destination number and text.

Copy
Copied
def send_reminder(to, message)
    Telnyx::Message.create(
        from: $config['from_number'],
        to: "#{$config['country_code']}#{to}",
        text: message
    )
end

Note: After pasting the above content, Kindly check and remove any new line added

Parse User Input and Schedule the Message

Within the POST handler, parse the meeting time, and compute how far into the future it is. Note that we are inserting the current timezone into the user submitted data in order to match with the output of DateTime.now.

Copy
Copied
post '/' do
    meeting_datetime = DateTime.strptime(
        "#{params[:meeting_date]} #{params[:meeting_time]} #{DateTime.now.strftime('%Z')}",
        '%Y-%m-%d %H:%M %Z')
    current_datetime = DateTime.now

    delta = meeting_datetime.to_time - current_datetime.to_time
    # ...
end

Note: After pasting the above content, Kindly check and remove any new line added

If the meeting is sooner than 3 hours, 5 minutes from now, return an error.

Copy
Copied
if delta < 11100 # 3 hours, 5 minutes in seconds
    return erb :index, :locals => {:message => 'Can only schedule meetings at least 3 hours 5 minutes in advance'}
else
    # ...
end

Note: After pasting the above content, Kindly check and remove any new line added

Remind the User

If the time is valid, compute when to send the reminder and schedule the function call.

Copy
Copied
meeting_dt_formatted = meeting_datetime.strftime('%Y-%m-%d %l:%M %p')
reminder_time = meeting_datetime.to_time - 3.hours

scheduler.at reminder_time do
    message = "#{params[:customer_name]}, you have a meeting scheduled for #{meeting_dt_formatted}"
    send_reminder params[:phone], message
end

Note: After pasting the above content, Kindly check and remove any new line added

We'll use a new template confirm the reminder was set.

Copy
Copied
$ touch views/success.erb

Note: After pasting the above content, Kindly check and remove any new line added

The success.erb file should look something like:

Copy
Copied
<html>
  <head>
    <title>Telnyx Meeting Scheduler</title>
  </head>
<body>
    <p><%= name %>, thanks for booking a meeting for <em><%= meeting_name %></em>!</p>
    <p>You are scheduled for <%= meeting_dt %> and will receive a courtesy reminder at <%= phone %> three hours before your meeting.</p>
  </body>
</html>

Note: After pasting the above content, Kindly check and remove any new line added

Finally, render the success template`

Copy
Copied
erb :success, :locals => {
    :name => params[:customer_name],
    :meeting_name => params[:meeting_name],
    :meeting_dt => meeting_dt_formatted,
    :phone => params[:phone]
}

Note: After pasting the above content, Kindly check and remove any new line added

Final scheduler.rb

All together, your scheduler.rb should look something like:

Copy
Copied
require 'sinatra'
require 'date'
require 'active_support/all'
require 'rufus/scheduler'
require 'telnyx'
require 'yaml'

$config = YAML.safe_load(File.open('config.yml').read)
Telnyx.api_key = $config['api_key']
scheduler = Rufus::Scheduler.new

def send_reminder(to, message)
  Telnyx::Message.create(
    from: $config['from_number'],
    to: "#{$config['country_code']}#{to}",
    text: message
  )
end

get '/' do
  erb :index
end

post '/' do
  meeting_datetime = DateTime.strptime(
    "#{params[:meeting_date]} #{params[:meeting_time]} #{DateTime.now.strftime('%Z')}",
    '%Y-%m-%d %H:%M %Z'
  )
  current_datetime = DateTime.now

  delta = meeting_datetime.to_time - current_datetime.to_time
  if delta < 11_100 # 3 hours, 5 minutes in seconds
    return erb :index, locals: { message: 'Can only schedule meetings at least 3 hours 5 minutes in advance' }
  else
    meeting_dt_formatted = meeting_datetime.strftime('%Y-%m-%d %l:%M %p')
    reminder_time = meeting_datetime.to_time - 3.hours

    scheduler.at reminder_time do
      message = "#{params[:customer_name]}, you have a meeting scheduled for #{meeting_dt_formatted}"
      send_reminder params[:phone], message
    end

    erb :success, locals: {
      name: params[:customer_name],
      meeting_name: params[:meeting_name],
      meeting_dt: meeting_dt_formatted,
      phone: params[:phone]
    }
  end
end

Note: After pasting the above content, Kindly check and remove any new line added

Running the Project

Launch the scheduler.rb file from the command line and open your browser to http://localhost:4567 to set an appointment reminder.

Copy
Copied
$  ruby scheduler.rb
[2020-09-24 13:16:39] INFO  WEBrick 1.6.0
[2020-09-24 13:16:39] INFO  ruby 2.7.1 (2020-03-31) [x86_64-darwin19]
== Sinatra (v2.1.0) has taken the stage on 4567 for development with backup from WEBrick
[2020-09-24 13:16:39] INFO  WEBrick::HTTPServer#start: pid=75889 port=4567

Note: After pasting the above content, Kindly check and remove any new line added