Testing Webhooks using the Nylas CLI and Ruby

Webhooks are the best way to get notifications without having to constantly ask the server. We’re using Ruby to demonstrate how this works.

Testing Webhooks using the Nylas CLI and Ruby

The Nylas CLI is an amazing tool that can be used for a lot of different things, but did you know we can even test Webhooks without the need of an external web server? Before we get started, let’s review what webhooks are, and why they are important.

What are webhooks?

Webhooks are notifications triggered by certain events like receiving an email, opening a link inside an email, when an event has been created or deleted and more. Webhooks are important because they are submitted to our applications when something important happens, without the need of our application having the need of pulling information every “x” amount of time.

Why are webhooks important?

A webhook is triggered by the server and it’s delivered to your application without your application having to request it. Instead of having to make multiple requests, your application can wait until it receives a webhook. This makes applications more efficient and most important, faster.

When sending an email or creating an event, it’s important to know if the message was opened, a click inside the messages was clicked or if an event was modified or deleted. Having all this information will help us to make better decisions.

Using the Nylas CLI

Before getting started with the Nylas CLI, we need to install it.

For details on how to install and use the CLI, we can read the blog post Working with the Nylas CLI.

Setting up a tunnel

In order to test Webhooks on the CLI, we need to create a Webhook Tunnel. This will register a temporary Webhook for us and will keep it open for around 2 hours. It will also provide us with a Web Server and will manage all the Webhook details so we don’t have to worry about anything.

We need to type (The print flag is just so that we can see the output on the CLI)

$ nylas webhook tunnel --print
Webhook tunnel using Nylas CLI

And just like that, our webhook is created, our web server is ready and we only need to trigger some events and see what happens. We can just send an email to ourselves.

Draft email

We can confirm that the email was received by us

Inbox

And check the CLI for the webhook notification

Reading webhook on Nylas CLI

Awesome, huh? We got one notification because we got a new email, and another one because we opened the email. What will happen if we delete the email? Yes, we will get another notification.

But, the information is a little bit hard to read, especially if we get a lot of notifications. What we can do is get some help from a Ruby web application.

Using the Nylas CLI and Ruby

Chances are that we already have Ruby installed on our machines. We can check our version by doing the following (Note that we support Ruby 2.3 and onwards).

$ ruby -v

Ruby 2.6.8p205 (2021–07-07 revision 67951) [Universal.arm64-darwin21]

If we don’t happen to have Ruby installed, we can read the blog post How to Send Emails with the Nylas Ruby SDK where everything is nicely explained.

Installing the Sinatra package

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 two additional gems which are webrick (To handle web server) and json (To handle json).

$ gem install sinatra
$ gem install webrick
$ gem install json

Once installed, we’re ready to go.

Creating our Sinatra web application

We will call this file WebHooksTest.rb

# Import your dependencies
require 'sinatra'
require 'json'

# Create a home page and print out the information
post '/' do
	data = JSON.parse(request.body.read)
# Print on the console
	data.each { |deltas|
		puts JSON.pretty_generate(deltas)
	}
# Send information back to the caller
	"#{JSON.pretty_generate(data)}"
end

When we run this script, it will receive input from the Nylas CLI and print it in a more readable way.

We can run this script from the terminal by using:

$ ruby WebHooksTest.rb
Testing Webhooks on Ruby

Our Web Server is alive on http://127.0.0.1:4567/ but it doesn’t have anything to show and it might give us an error if we open it on the browser as we build it in order to work with the CLI.

Calling our Sinatra app from the Nylas CLI

Now, we have everything ready and we can call our Sinatra Web Application from the Nylas CLI.

We can type

$ nylas webhook tunnel -f http://127.0.0.1:4567/
Opening Ruby app with Nylas CLI

The Webhook tunnel will now forward the results to our Sinatra Web Application console.

If we send ourselves a new message, then we will see this on the console.

Reading Webhook on Ruby app

And it will also look nicer on the CLI.

Reading Webhook on Nylas CLI with Ruby app

And while this might not look like much, it’s simply the foundation of what’s coming next.

Using Ruby

We may be wondering, “Haven’t we used Ruby already?” and the answer would be “Yes, we did. But we did it locally”. Now it’s time to move to the outside world.

What we are going to do is create a Sinatra Web Application and upload it to Heroku, so it’s freely accessible. This web application is going to use SQLite to store the Webhook information and display it on the browser. While using SQLite on Heroku is not recommended because it will reset itself after a day or every time there’s a new deployment, it’s the easiest way to do some testing and it can be migrated later on to Postgresql if needed.

Setting up a Heroku account and CLI

If we don’t have a Heroku account, we can sign up for one here. Once that’s done, we will need to install the Heroku CLI. I found that the best option is to use Brew.

$ brew tap heroku/brew && brew install heroku

Once that’s done, simply log into the Heroku CLI.

$ heroku login

Creating the Sinatra web application

Before we can move on, there are some additional gems that we need to install. The first one, OpenSSL, can generate some problems, so here’s how to overcome them.

$ openssl version

OpenSSL 3.0.1 14 Dec 2021 (Library: OpenSSL 3.0.1 14 Dec 2021)

If we have OpenSSL 3.0.1 installed, then we’re not going to be able to install the gem as Ruby OpenSSL doesn’t play well with OpenSSL 3.0.1

What we need to do is install OpenSSL 1.1 like this

$ brew install [email protected]

We might have it installed already. We need to force our system to use that version of OpenSSL

$ brew link [email protected] --force

With that all done, we can install the gem

$ gem install openssl

And we can go back to OpenSSL 3.0.1 if we want

$ brew link openssl --force

There are some extra gems that we need to install, so here they are

$ gem install sqlite3
$ gem install bundler:2.1.2 #We need this specific version
$ gem install sinatra-contrib

Now, we need to create a file called app.rb

# Import your dependencies
require 'sinatra'
require "sinatra/config_file"
require 'nylas'
require 'sqlite3'
require 'openssl'
require 'json'
require 'time'

# Nylas need to check that our app is active
get '/webhook' do
  if params.include? "challenge"
	"#{params['challenge']}"
  end
end

post '/webhook' do
# We need to verify that the signature comes from Nylas
    is_genuine = verify_signature(message = request.body.read, key = ENV['CLIENT_SECRET'], signature = request.env['HTTP_X_NYLAS_SIGNATURE'])
    if !is_genuine
        status 401
        "Signature verification failed!"
    end

	nylas = Nylas::API.new(
		app_id: ENV['CLIENT_ID'],
		app_secret: ENV['CLIENT_SECRET'],
		access_token: ENV['ACCESS_TOKEN']
	)
	
# We read the webhook information and store it on the database
    request.body.rewind
    data = JSON.parse(request.body.read)
    connection = get_db_connection()
    if data["deltas"][0]["type"] == "message.created" or data["deltas"][0]["type"] == "message.update"
		message = nylas.messages.find(data["deltas"][0]["object_data"]["id"])
	elsif data["deltas"][0]["type"] == "message.opened" or data["deltas"][0]["type"] == "message.link_clicked"
		message = nylas.messages.find(data["deltas"][0]["object_data"]["metadata"]["message_id"])
	end
	email_to = ""
	
	message.to.each{ | emails |
		email_to = email_to + ", " + emails.email
	}
	
    connection.execute("INSERT INTO webhooks (id, date, title, from_, to_, type) VALUES (?, ?, ?, ?, ?, ?)", 
	                    [data["deltas"][0]["object_data"]["id"], 
	                    Time.at(data["deltas"][0]["date"]).to_s,
	                    message.subject, 
	                    message.from.first.email,
	                    email_to.delete_prefix(', '),
	                    data["deltas"][0]["type"]])
	connection.close()	                    
	status 200
	"Webhook received"	
end

# We display the database content
get '/' do
	connection = get_db_connection()
	erb :main, :locals => {:webhooks => connection.execute( "select * from webhooks" )}
end

# We connect to the database
def get_db_connection()
	connection = SQLite3::Database.open "database.db"
end

# We generate a signature with our client secret and compare it with the one from Nylas
def verify_signature(message, key, signature)
	digest = OpenSSL::Digest.new('sha256')
	digest = OpenSSL::HMAC.hexdigest(digest, key, message)
	secure_compare(digest, signature)
end

# We compare the keys to see if they are the same
def secure_compare(a, b)
	return false if a.empty? || b.empty? || a.bytesize != b.bytesize
	l = a.unpack "C#{a.bytesize}"

	res = 0
	b.each_byte { |byte| res |= byte ^ l.shift }
	res == 0
end

We will notice right away that the code that we are using is more complex than our initial application, and that’s because when Nylas sent us a Webhook, it expects our application to send a response back in order to validate that the service is working properly. Also, we want to validate the signature to make sure that it’s coming from Nylas and not someone else.

We also need to create a subfolder called views with one file called main.erb

<style>
.webhook {
	padding: 10px;
    margin: 5px;
    background-color: #f3f3f3
}
</style>
<title>Nylas Webhooks</title>
<h1>Webhooks</h1>
<% webhooks.each do |item| %>
  <div class='webhook'>
    <p><b>Id:</b> <%= item[0] %> | Date:</b> <%= item[1] %> | <b>Title:</b> <%= item[2] %> | 
    <b>From:</b> <%= item[3] %> | <b>To</b> <%= item[4] %> | <b>Type</b> <%= item[5] %> </p>

  </div>
<% end %>

As we’re going to use SQLite we need to create a schema and a table.

DROP TABLE IF EXISTS webhooks;
CREATE TABLE webhooks (id TEXT NOT NULL, date TEXT NOT NULL, title TEXT NOT NULL, from_ TEXT NOT NULL, to_ TEXT NOT NULL, type TEXT NOT NULL);

Running this script using ruby init_db.rb will generate the database.db file we need for our application.

require "sqlite3"

connection = SQLite3::Database.open "database.db"

File.foreach("schema.sql") { |command| 
	connection.execute(command) 
}

connection.close()

Deploying our application to Heroku

The first thing we need to do is create a file called Gemfile, which will hold the information of the gems we need to install

source 'https://rubygems.org'
gem 'sinatra'
gem 'nylas'
gem 'webrick'
gem 'sinatra-contrib'
gem 'sqlite3'
gem 'openssl'
gem 'json'
gem 'time'

Then, on the terminal we need to type

$ bundle _2.1.2_ install

This will generate a file called Gemfile.lock and if we’re using a Mac, we need to add this as well

$ bundle _2.1.2_ lock --add-platform x86_64-linux

As we’re going to use SQLite, we need to create a folder called config with a file called database.yml and enter the following 

development:
  adapter: sqlite3
  database: db/mydata.sqlite3

production:
  adapter: sqlite3
  database: db/mydata.sqlite3

Finally, we can initialize our git repository

$ git init
$ echo .env >> .gitignore
$ git add .
$ git commit -m “Initializing Git repository” 

We must not forget to commit our changes. Otherwise, they will not be taken into account

Finally, we’re going to create our Heroku application.

$ heroku create blagruby-webhooks

Application names in Heroku must be unique, so you can change blag with your own name.

Now…in an ideal world, we could just deploy our application and let the webhooks come in…but…Heroku doesn’t allow us to use SQLite for Ruby applications…which is strange because we can for Python applications…but for Ruby, there are many missing components and the SQLite gem needs to be compiled.

Happily, there’s a way to do it, otherwise this section wouldn’t even make sense.

We need to run this command before pushing our changes

$ heroku config:set BUILDPACK_URL=https://github.com/whynot/heroku-buildpack-ruby-with-sqlite3.git

This will create a custom backpack with all the missing files needed to successfully compile the SQLite gem.

Since we’re going to need to access our Nylas Tokens, we don’t want to use/push the .env file into Heroku, so we’re going to use Heroku Environment Variables instead.

We need to go to our Heroku Dashboard and select our application. Then go to Settings

Heroku Dashboard

And select Reveal Config Vars.

Config Vars

This will open the Config Vars so that we can update them

Reveal Config Vars

This information can be found on our Nylas Dashboard.

With all that ready, we can push our changes and deploy our application.

$ git push heroku master
Upload Ruby app to Heroku

We can now open our web application by typing

$ heroku open
Testing Ruby App on Web

It looks somehow empty, but that’s because we don’t have any webhook notifications yet. For that, we need to create a webhook first.

Creating a webhook

If we go to our Nylas Dashboard and choose Webhooks

Webhooks Dashboard

We can press Create a webhook

Create Webhook

Here is the most important piece, as we need to provide the URL of our Heroku Flask Server and choose the Triggers that we want to use.

Pass URL to Webhook

For this example we’re going to choose the first three Message Triggers.

Choose triggers

Notice that message.link_clicked only works when an email is sent from a paid account, so the free trial will not work.

When we press Create Webhook, we can see our webhook listed.

Created triggers

Now, if we send an email to our connected account, we will see that the event is saved and displayed on our Flask application. Notice that I haven’t implemented auto refresh, so we need to manually refresh the page.

Email to test webhooks

We will see the first webhook reflected on the webpage.

First webhook on Ruby App

Awesome! A new email was received.

So the next step is to send an email from a paid account so that we can track when the email is opened and more important when a link is clicked. We call this file SendEmailTracking.rb

#!/usr/bin/env ruby
require 'dotenv/load'
require 'nylas'

nylas = Nylas::API.new(
    app_id: ENV["CLIENT_ID"],
    app_secret: ENV["CLIENT_SECRET"],
    access_token: ENV["ACCESS_TOKEN"]
)

tracking = {
        "links": true,
        "opens": true,
        "thread_replies": true,
        "payload": "new-payload",
}

nylas.send!(
    to: [{ email: [email protected]', name: "Nylas" }],
    subject: "With Love, from Nylas",
    body: "This email was sent using the Ruby SDK for the Nylas Email API. Visit <a href='https://nylas.com'>Nylas</a> for details",
    tracking: tracking
    ).to_h

Notice that the link needs to be between link tags.

Here’s the email that we received.

Check mailbox

We open it.

Open email with link

And click on the link.

Reading all webhooks on Ruby app

All events are correctly logged and displayed.

If you want to learn more about Webhooks, please go to our documentation.

Tags:

You May Also Like

Why it’s mission-critical to build bi-directional email in your CRM with an email API… now
Why it’s mission-critical to build bi-directional email in your CRM with an email API… now
hero banner
Everything you need to know about Microsoft’s basic authentication deprecation
How we secure APIs at Nylas using JSON Web Tokens