Skip to main content

Appointment Reminder

| Python | Node | Ruby |


Python for Appointment Reminders

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.

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.

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

$ 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.

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"

$ 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

<!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

<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.

@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.

@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.

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.

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.

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.

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.

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

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

Final schedule_meeting_server.py

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

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)

$ 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

$ python schedule_meeting_server.py

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

Node for Appointment Reminders

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
$ 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.

{
"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.

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.

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.

<!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.

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.

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.

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.

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.

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

<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.

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.

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:

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.

$ node index.js
App running on port 3000

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

Ruby for Appointment Reminders

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

Pre-reqs

Configuration

Create a config.yml file in your project directory.

$ 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.

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.

$ 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.

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.

$ 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

<!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.

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.

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.

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.

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.

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.

$ 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:

<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`

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:

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.

$  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

On this page