- Products
- Solutions Use casesBy industry
- Developers
- Resources Connect
- Pricing
On a day like today, 13 years ago, _why (also called why the lucky stiff) disappeared from the internet and why would that matter?
Simply, because _why was a visionary, an artist, and an awesome developer.
I started my Ruby journey around 1999 after I read the book Why’s (poignant) Guide to Ruby and hence became a Ruby aficionado and also a _why fanboy.
But obviously, he created more than that…just to name a few:
So, in order to celebrate _why day, we’re going to build a very simple mailbox application using Shoes and of course, our Nylas APIs.
This mailbox application will allow us to read the first five emails of our email account, read the messages, reply to them, delete them or compose new messages.
The original Shoes stopped being maintained some time ago and now it is called Shoes 4, but it runs on JRuby instead of regular Ruby.
As you may wonder, JRuby is a Ruby implementation running on the JVM (Java Virtual Machine).
If you have read the blog post How to Send Emails with Ruby Nylas SDK, then you most likely have rbenv installed already. Otherwise, please go ahead and read it.
We need to use a specific version of the JVM, which is 9.0.4.0.11, so we can use the following code to see our installed version:
$ /usr/libexec/java_home -V
And then this code to choose the one we need:
$ export JAVA_HOME=`/usr/libexec/java_home -v 9.0`
This will change the version just for our current session.
Perform the following to get a list of all available versions:
$ rbenv install -l
And choose the one for JRuby which is, at the time of writing, is this:
jruby-9.3.6.0
After the installation, we just need to make it the default in our system by typing on the terminal:
$ rbenv global jruby-9.3.6.0
Sadly, as we’re using JRuby, we’re not going to be able to install the Nylas gem…but, that doesn’t mean we can’t call the Nylas APIs from our application. So, everything is good, but we’re going to need a couple of extra gems to help us:
# To read .env files $ gem install dotenv # Makes http fun again $ gem install httparty # Shoes 4 GUI Toolkit $ gem install shoes --pre
Let’s check some screenshots, so we know in advance exactly what we are going to build.
First, we will be greeted by the application and then, the first 5 emails will be displayed. We can either Refresh or Compose a new email.
When we open the application, it will present us with the first five emails. For that, we’re using Nylas Messages Endpoint.
Let’s send an email to ourselves. Here we can only go Back or Send the email.
Once the email has been sent, we will be redirected to the main screen. In order to send the emails, we use the Nylas Send Endpoint.
Now, let’s reply to this message, and see how it looks on our email client.
Here we will need to press the Update button as we haven’t implemented any kind of auto-refresh logic. The Update button will simply fetch the first five emails again.
We received a new email and it’s getting displayed in a different color. We can open this email by clicking on its subject.
Here, we’re using a chained Regex. It will help us clean the content of the email, by getting rid of the HTML or CSS that might not be rendered correctly.
Now, we have three options. We can go back, reply to the message or delete it.
Let’s reply first.
To reply to a message, we use the Nylas Send Endpoint, just like with composing a new message but passing the reply_to_message_id field in the body.
Let’s make sure that it is successfully sent again.
Once we click refresh, we will see our new email as unread, and the one we answered as read.
For this, we call the Nylas Messages Endpoint and update the unread field in the body.
Now, the only thing left for us to do, it’s to delete our message. We need to read it first, and then click on the delete button.
Now, the email is gone.
For that we call the Nylas Messages Endpoint and update the label_ids field in the body.
Now that we have seen how it works, we can go ahead and look at the source code.
We’re going to call this file Shoes_Mail_Client.rb
# Import dependecies require 'dotenv/load' require 'httparty' require 'date' # This class will hold the header needed to authenticate us class Headers def get_headers() return @headers = { "Content-Type" => "application/json", "Authorization" => "Bearer " + ENV["ACCESS_TOKEN"], } end end # This class will get our name from our Nylas account class Account def initialize() @headers = Headers.new() # We're calling the Account Endpoint @account = HTTParty.get('https://api.nylas.com/account', :headers => @headers.get_headers()) end def get_name() return @account["name"] end end # This class will get a list of Labels so that we can send email to the trash class Label def initialize() @labelsDict = Hash.new @headers = Headers.new() # We're calling the Labels Endpoint @labels = HTTParty.get('https://api.nylas.com/labels', :headers => @headers.get_headers()) for @label in @labels @labelsDict[@label["name"]] = @label["id"] end end def get_trash() return @labelsDict["trash"] end end # This class will make sure our emails look nice and tidy class Clean_Email def initialize(message) @body = messagegsub(/n/," ").gsub(/<style>.+</style>/," "). gsub(/<("[^"]*"|'[^']*'|[^'">])*>/," "). gsub(/.email-content{.+}/," "). gsub(/ /, " "). gsub(/.s/, " "). gsub(/^s+|s+$/g/, "") end def get_clean() return @body end end # This class will update our email status, either "Read" or "Delete" class Update_Email def initialize(message, type, label="") @headers = Headers.new() if type == "read" @body = { "unread" => false, } else @body = { "label_ids" => [label], } end # We're calling the Messages Endpoint @updated_email = HTTParty.put('https://api.nylas.com/messages/' + message, :headers => @headers.get_headers(), :body => @body.to_json) end end # This class will send our emails or reply them class Send_Email def initialize(recipient_name, recipient_email, subject, body, reply_id=nil) @headers = Headers.new() @body = { "subject" => subject, "body" => body, "to" => [{ "name" => recipient_name, "email" => recipient_email }], "reply_to_message_id" => reply_id } end def send() # We're calling the Send Endpoint @email = HTTParty.post('https://api.nylas.com/send', :headers => @headers.get_headers(), :body => @body.to_json) end end # This class will read our inbox and return the 5 most recent ones class Emails def initialize() @headers = Headers.new() end def get_emails() # We're calling the Messages Endpoint @emails = HTTParty.get('https://api.nylas.com/messages?in=inbox&limit=5', :headers => @headers.get_headers()) end def return_emails() return @emails end end # This class will control the flow of our application class Pages < Shoes url '/', :index url '/read/(d+)', :read url '/compose', :compose url '/reply/(d+)', :reply # Class variables that we can access from anywhere on the application @@email_detail = Net::HTTP::Get @@trash_label = Net::HTTP::Get @@counter = 0 # Our main view...this is our inbox def index background lightblue @@counter = 0 @name = Account.new() if !@account_name @account_name = @name.get_name.split(" ").first end # Greet the user title "Welcome to your Inbox, " + @account_name + "!", align: "center" @emails = Emails.new() @labels = Label.new() # Create the Refresh and Compose buttons stack do background red flow do button "Refresh" do visit "/" end button "Compose" do visit "/compose" end end end stack do para "" end @@trash_label = @labels.get_trash() @emails.get_emails() @@email_detail = @emails.return_emails() for @email in @@email_detail stack do @datetime = Time.at(@email["date"]).to_datetime @date = @datetime.to_s.scan(/d{4}-d{2}-d{2}/) @time = @datetime.to_s.scan(/d{2}:d{2}:d{2}/) # Display emails: Subject, Sender, Date and Time. # Also if unread, display it using a different color if @email["unread"] == true para " ", link(@email["subject"], :click=>"/read/#{@@counter}"), " | " , @email["from"][0]["name"] , " | ", @date , " - ", @time, :size => 20, :stroke => darkblue else para " ", link(@email["subject"], :click=>"/read/#{@@counter}"), " | " , @email["from"][0]["name"] , " | ", @date , " - ", @time, :size => 20 end para "" end # Global counter to make sure we're opening the right email @@counter += 1 end end # View to read an email def read(index) background lightblue # Update email state so it goes from "unread" to "read" @update_email = Update_Email.new(@@email_detail[index.to_i]["id"], "read","") stack do background red flow do button "Back" do visit "/" end button "Reply" do visit "/reply/#{index}" end # When deleting an email, pass the "trash" label button "Delete" do @email = Update_Email.new(@@email_detail[index.to_i]["id"], "delete",@@trash_label) visit "/" end end end stack do background lavender flow do para "Date: ", :size => 25 @datetime = Time.at(@@email_detail[index.to_i]["date"]).to_datetime @date = @datetime.to_s.scan(/d{4}-d{2}-d{2}/) @time = @datetime.to_s.scan(/d{2}:d{2}:d{2}/) para @date[0] + " - " + @time[0], :size => 20 end end stack do background lightcyan flow do para "Sender: ", :size => 25 para @@email_detail[index.to_i]["from"][0]["name"] + " / " + @@email_detail[index.to_i]["from"][0]["email"], :size => 20 end end stack do background lightskyblue flow do para "Subject: ", :size => 25 para @@email_detail[index.to_i]["subject"], :size => 20 end end stack do # Call the Clean_Email in order to get rid of extra HTML @clean_email = Clean_Email.new(@@email_detail[index.to_i]["body"]) @conversation = @clean_email.get_clean() para "Body: ", :size => 25 para "" para @conversation, :size => 20 end end # View to compose an email def compose() @recipient_name = "" @recipient_email = "" @subject = "" @body = "" background lightblue stack do background red flow do button "Back" do visit "/" end button "Send" do # Call the Messages Endpoint to send the email @email = Send_Email.new(@recipient_name.text, @recipient_email.text, @subject.text,@body.text) @email.send() visit "/" end end end stack do background lightcyan flow do para "Recipient's Name: ", :size => 25 @recipient_name = edit_line :width => 400 end end stack do background lightcyan flow do para "Recipient's Email: ", :size => 25 @recipient_email = edit_line :width => 400 end end stack do background lightskyblue flow do para "Subject: ", :size => 25 @subject = edit_line :width => 600 end end stack do flow do para "Body: ", :size => 25 @body = edit_box :width => 800, :height => 240 end end end # View to reply an email def reply(index) @recipient_name = "" @recipient_email = "" @subject = "" @body = "" background lightblue stack do background red flow do button "Back" do visit "/" end button "Send" do # Call the Messages Endpoint to send the email # and using the reply_to_message_id @email = Send_Email.new(@recipient_name,@recipient_email, @subject.text,@body.text, @@email_detail[index.to_i]["id"]) @email.send() visit "/" end end end stack do background lightcyan flow do para "Recipient's Name: ", :size => 25 para @@email_detail[index.to_i]["from"][0]["name"], :size => 20 @recipient_name = @@email_detail[index.to_i]["from"][0]["name"] end end stack do background lightcyan flow do para "Recipient's Email: ", :size => 25 para @@email_detail[index.to_i]["from"][0]["email"], :size => 20 @recipient_email = @@email_detail[index.to_i]["from"][0]["email"] end end stack do background lightskyblue flow do para "Subject: ", :size => 25 @subject = edit_line :width => 600 @subject.text = "Re: " + @@email_detail[index.to_i]["subject"] end end stack do flow do # Body of reply message. @clean_email = Clean_Email.new(@@email_detail[index.to_i]["body"]) @conversation = @clean_email.get_clean() @datetime = Time.at(@@email_detail[index.to_i]["date"]).to_datetime @date = @datetime.to_s.scan(/d{4}-d{2}-d{2}/) @time = @datetime.to_s.scan(/d{2}:d{2}:d{2}/) para "Body: ", :size => 25 @body = edit_box :width => 800, :height => 240 @body.text = "nnnOn " + @date[0] + " at " + @time[0] + " " + @@email_detail[index.to_i]["from"][0]["email"] + " wrote: nnn" + @conversation[0]["conversation"] end end end end # We call our Shoes application, specifying the name, width, height and if it's resizable or not Shoes.app title: "Shoes Email Client", width: 1000, height: 400, resizable: false
A little long, but it’s a complex application. And, I know what you’re thinking…Regex to sanitize HTML? Why don’t you use something like Nokogiri? Well, Shoes comes bundled with version 1.6.4.1 of Nokogiri, which happens to be incompatible with the JVM as it produces an Illegal reflective access operation.
Now that we have our application ready, it’s time to run it. And we can do that from the terminal window:
$ shoes Shoes_Mail_Client.rb
And it will display our inbox.
If you want to check the source code, you can find it on our Github Samples as Shoes-Mail-Client.
If you want to learn more about Shoes, read the book Nobody Knows Shoes and if you want to learn more about the Nylas APIs read our documentation.
Blag aka Alvaro Tejada Galindo is a Senior Developer Advocate at Nylas. He loves learning about programming and sharing knowledge with the community. When he’s not coding, he’s spending time with his wife, daughter and son. He loves Punk Music and reading all sorts of books.