Let’s imagine that we own a gym equipment rental business. People can reserve our gym equipment for an hour, and we need a scheduling system to handle these reservations. Using Nylas and the Nylas Ruby SDK, we can create a Ruby and Sinatra scheduling system where people can log in using their Google account, select a time slot, and receive a confirmation email with an event attached.
In this blog post, we will explore how we can easily support creating new calendars and events using the Nylas Calendar API.
If you prefer Python, then go here How to build a scheduling application using Python and Flask.
If you already have the Nylas Ruby SDK installed and your Ruby environment configured, continue reading this blog.
Otherwise, I recommend reading the post How to Send Emails with the Nylas Ruby SDK to understand the setup basics.
When we launch our webpage, we need to have a way for people to log into their Google accounts:

When pressing the button, they will be redirected to this login screen:

If you want to use a different provider, you must configure an app for each of them. Here’s how to do it for Microsoft Outlook, How to Create an Azure App.

We choose the account we want to use and enter our password:

We must continue, as our application is still in the testing phase:

We’ll need to access the calendar, so we need to click continue, although we can confirm that we’re just being asked for 4 services related to the calendar and nothing else.
Once logged in, we will be greeted and presented with the available spots for that particular day:

A new event will be scheduled once we click on one:

We will receive a confirmation email and an event on our calendar:

Now, we can see that the selected time slot is no longer available:

We can only reserve one spot per day, so trying to reserve another will result in an error message:

While we can use our default calendar, it would be better to have one specially made for this project. And even better, we can use a virtual calendar. A virtual calendar allows us to assign a calendar to an object or a person without an existing calendar account.
Read how to do it in the blog post A Small Guide to Virtual Calendars.
As we have created a virtual calendar, we need to add extra information to our .env file:
export CLIENT_ID=<CLIENT_ID> export CLIENT_SECRET=<CLIENT_SECRET> export ACCESS_TOKEN=<ACCESS_TOKEN> export ACCOUNT_ID=<ACCOUNT_ID> export CALENDAR_ID=<VIRTUAL_CALENDAR_ID> export VIRTUAL_TOKEN=<VIRTUAL_ACCOUNT_ACCESS_TOKEN>
If you haven’t created a Nylas app yet, follow these steps:



Enter your email address and press Connect:

You’re going to get this:

But no worries, it’s not your fault or ours. It’s actually Google’s increased security. Simply, let’s move to the next step.
To save you some time and to make this post shorter, we already have all the instructions you need to follow in this blog post How to Setup Your First Google API Project.
As we want to create a Ruby web application, our best option is to use Sinatra, one of the most popular Micro Frameworks in the Ruby world. We might need to install some additional gems:
$ gem install sinatra $ gem install puma $ gem install ricecream $ gem install dotenv
Once installed, we’re ready to go.
Now, we’re going to create our scheduling application with Ruby and Sinatra. We need to create a folder called GymEquipment with two folders called views and public. Inside the GymEquipment folder, we’re going to create a file called GymSchedule.rb with the following code:
# frozen_string_literal: true
# Import your dependencies
require 'sinatra'
require 'dotenv/load'
require 'nylas'
require 'ricecream'
# Use sessions
enable :sessions
# Initialize your Nylas API client
nylas = Nylas::API.new(
app_id: ENV['CLIENT_ID'],
app_secret: ENV['CLIENT_SECRET'],
access_token: ENV['ACCESS_TOKEN']
)
# Initialize your Nylas API client
api = Nylas::API.new(
app_id: ENV['CLIENT_ID'],
app_secret: ENV['CLIENT_SECRET']
)
virtual = Nylas::API.new(
access_token: ENV['VIRTUAL_TOKEN']
)
# Call the authorization page
get '/login/nylas/authorized' do
if session[:email_address] == nil
account = api.exchange_code_for_token(params[:code], return_full_response: true)
api_as_user = api.as(account[:access_token])
session[:email_address] = account[:email_address]
session[:participant] = api_as_user.current_account.name
session[:access_token] = account[:access_token]
redirect to("/login")
end
end
# Main page
get '/' do
erb :welcome, layout: :layout
end
# Main page with form
get '/login' do
if session[:email_address] == nil
url = api.authentication_url(redirect_uri: "http://localhost:4567/login/nylas/authorized",
scopes: ["calendar"], response_type: "code", login_hint:
"[email protected]", state: "mycustomstate")
redirect to(url)
end
# Organizers emails
#email = ["[email protected]"]
email = ["nylas_gym"]
calendar = [{account_id: ENV['ACCOUNT_ID'],calendar_ids: [ENV['CALENDAR_ID']]}]
duration = 60
interval = 60
schedules = []
time_slots = []
# Get today's date
today = Date.today
# We rent equipment from 10am to 3pm
start_time = Time.local(today.year, today.month, today.day, 10, 0, 0).strftime("%s").to_i
end_time = Time.local(today.year, today.month, today.day, 15, 0, 0).strftime("%s").to_i
virtual_events = virtual.events.where(calendar_id: ENV['CALENDAR_ID'])
virtual_events.each{ |event|
time_slots.append({"start_time": event.when.start_time.to_i,
"end_time": event.when.end_time.to_i,
"object": "time_slot",
"status": "busy"})
}
free_busy = {email: "nylas_gym", object: "free/busy", time_slots: time_slots}
# Determine availability of organizers
availability = nylas.calendars.availability(duration_minutes: duration,
interval_minutes: interval,
start_time: start_time, end_time: end_time,
emails: email, free_busy: [free_busy])
# Find available times
availability[:time_slots].each do |available|
ts = Time.at(available[:start_time]).to_datetime.strftime('%H:%M:%S')
te = Time.at(available[:end_time]).to_datetime.strftime('%H:%M:%S')
schedules.append([ts, te])
end
# Call page with available schedules
erb :main, layout: :layout, locals: {schedules: schedules, participant: session[:participant]}
end
# Remove auth access
get '/remove' do
api.revoke(session[:access_token])
session[:email_address] = nil
session[:participant] = nil
session[:access_token] = nil
redirect to("/")
end
# Create the event
get '/schedule' do
link = ""
registered = false
# Check if participant has been already registered
today = Date.today()
# We get the details from the chosen time and create the start and end times
start_time = Time.local(today.year, today.month, today.day, 10, 0, 0).strftime("%s").to_i
end_time = Time.local(today.year, today.month, today.day, 15, 0, 0).strftime("%s").to_i
# Check if participant has been already registered for the day
events = virtual.events.where(calendar_id: ENV['CALENDAR_ID'], starts_after: start_time, ends_before: end_time)
events.each{ |event|
event.participants.each{ |participant|
if participant.email == session[:email_address]
registered = true
end
}
}
if registered == false
# Get time parameters
ts = params[:start_time]
te = params[:end_time]
link = ts + " to " + te
today = Date.today()
# We get the details from the chosen time and create the start and end times
start_time = Time.local(today.year, today.month, today.day, ts[0, 2], ts[3, 2],
ts[6, 2]).strftime("%s").to_i
end_time = Time.local(today.year, today.month, today.day, te[0, 2], te[3, 2],
te[6, 2]).strftime("%s").to_i
# We create the event details
event = virtual.events.create(title: "Nylas' Gym Equipment Reservation", location: "Nylas'
Gym", when: {start_time: start_time, end_time: end_time},
participants: [{"name": session[:participant], 'email':
session[:email_address]}], description: "You have reserve Gym
Equipment from #{ts} to #{te}",
calendar_id: ENV['CALENDAR_ID'])
# Generate the Internet Calendar Scheduling
ics = event.generate_ics
# Write the file
File.open("invite.ics", "w") { |f| f.write ics }
# Create the file as an attachment
file = nylas.files.create(file: File.open(File.expand_path('invite.ics'), 'r')).to_h
# Send email with attachment included
nylas.send!(
to: [{ email: session[:email_address], name: session[:participant] }],
subject: 'Nylas'' Gym Equipment Reservation',
body: "You have reserve Gym Equipment from #{ts} to #{te}",
file_ids: [file[:id]]
).to_h
end
# Call page to confirm reservation
erb :scheduled, layout: :layout, locals: {link: link, registered: registered}
end
On the views folder, create four files, we’re going to start with layout.erb:
<html> <head> <script src="https://cdn.tailwindcss.com"></script> <title>Nylas' Gym Equipment</title> </head> <body> <%= yield %> </body> </html>
Then the file welcome.erb:
<div class="bg-[#315acb] border-green-600 border-b p-4 m-4 rounded grid place-items-center"> <h1 class="text-4xl text-white">Welcome to Nylas' Gym Equipment</h1><br><br> <img src="Nyla_Space.png"><br><br><br> <a href="/login"><img src="Google.png" alt="Sign in"></a> </div>
Now main.erb:
<div class="bg-[#315acb] border-green-600 border-b p-4 m-4 rounded grid place-items-center"> <h1 class="text-4xl text-white">Nylas' Gym Equipment</h1> <br> <p class="text-3xl text-center text-white">Welcome <%= participant %></p><br> <p class="text-3xl text-center text-white">Choose a time that's convenient for you, so that we can reserve your gym equipment.</p><br><br> <% schedules.each do |schedule| %> <% link = schedule[0] + " to " + schedule[1] %> <a href="schedule?start_time=<%= schedule[0] %>&end_time=<%= schedule[1] %>" class="text-blue-200"><b><%= link.to_s %></b></a><br> <% end %> <a href="/remove" class="text-red-200"><b>Log Out</b></a> </div>
And finally scheduled.erb:
<div class="bg-[#315acb] border-green-600 border-b p-4 m-4 rounded grid place-items-center"> <% if registered == false %> <h1 class="text-4xl text-white">You have schedule the usage of Nylas' Gym Equipment</h1><br> <p class="text-3xl text-center text-white">You will receive a confirmation for your <%= link %> reservation.</p><br> <% else %> <h1 class="text-4xl text-white">You have already scheduled the usage of Nylas' Gym Equipment. Only one time per customer.</h1><br> <h1 class="text-4xl text-white">If you want to cancel or update your reservation, contact <b>Customer Care</b>.</h1><br> <% end %> <p class="text-3xl text-center text-red">With love, Nylas.</p><br><br> <a href="/login" class="text-red-200"><b>Go back</b></a> </div>
On the public folder, we need to images:


Ngrok is a globally distributed reverse proxy that allows our local applications to be exposed on the internet. We need to create a user and then install the client.
We can install it by using brew:
$ brew install ngrok/ngrok/ngrok
Then, follow the instructions on the webpage.
The first option is the installation:

Once everything is ready, we can run it by typing the following on the terminal window:
$ ./ngrok http 4567

We need to copy the Forwarding address, which will change every time we run ngrok.
To run our Ruby and Sinatra scheduling application, we need to update our Nylas application. Head to the application settings page, choose Authentication and then type the following on the Add you callback input box:
http://localhost:4567/login/nylas/authorized
And also the address that you copied from ngrok:
https://xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx.ngrok.io

we just need to type the following on the terminal window:
$ ruby GymSchedule.rb

Our application will be running on port 4567 of localhost, so we just need to open our favourite browser and go to the following address:
http://localhost:4567
Everything should work as expected.
Our application is done and working correctly, but, how do we check for existing events? As the virtual calendar doesn’t live on our calendar, this might be tricky, but luckily it is not.
If you have the Nylas CLI installed (if you don’t, here’s how Working with the Nylas CLI) then just run this:
$ nylas api events get --access_token <VIRTUAL_ACCESS_TOKEN>
Or you can run this Ruby script called ReadVirtual.rb:
require 'nylas'
require 'dotenv/load'
virtual = Nylas::API.new(
access_token: ENV['VIRTUAL_TOKEN']
)
events = virtual.events.where(calendar_id: ENV['CALENDAR_ID'])
events.each{ |event|
print(event.title)
print("\nStart time: ", Time.at(event.when.start_time).to_datetime.strftime('%H:%M:%S'))
print("\nEnd time: ", Time.at(event.when.end_time).to_datetime.strftime('%H:%M:%S'))
print("\n", event.participants[0].email)
print("\n\n")
}
Now that we have created a Ruby and Sinatra Scheduling Application, why don’t we explore and create more.
If you want to learn more about the Nylas Email API, please visit our documentation Nylas Calendar Overview.
You can sign up Nylas for free and start building!
Don’t miss the action, watch our livestream Coding with Nylas: