Private Inbox System in Rails with Mailboxer

Private Inbox System in Rails with Mailboxer

___

Here I show how to implement a private messaging system with mailboxer gem.



Introduction

It's been quite a while since my last tutorial and since then I've recieved alot of requests by email to implement a private messaging system in Rails. In this tutorial I will guide you on how to implement such a system using the Mailboxer gem. The Gem's documentation is not that sufficient so i'll try to do be as detailed as possible.

You can take a look at the Demo App on heroku, the source code for this tutorial can also be found on my Github Repo. Feel free to clone it!

Getting Started

We want to create a simple application that lets logged in users send and receive messages privately between themselves. Our application should keep a list of messages organized into folders: sentbox, inbox and trash and also ability to send notifications via mail.

Lets start out by creating a new rails app, lets name it messenger

rails new messenger

I'll be using bootstrap css under the hood for some quick styling. Next up, we will need to set up and authentication mechanism, we'll use Devise for that. Let's also throw in our Mailboxer gem at this point and leave it to simmer for a while while we work on authentication.

Loading Gist

We then install our gems using the bundle command

$ bundle install

Authentication

We now need to run Devise's generator to install an initializer that will describe all options available to configure Devise.

$ rails generate devise:install

Completing the additional steps as described on the post-install messages that appear after running the above generator, lets create a controller and name it welcome with one action.

$ rails g controller welcome index

Lets make the root path of our application point to the index action of our newly created welcome controller

routes.rb

Loading Gist

We then need to tweak the application.html layout file a bit to add the flash messages

application.html.erb

Loading Gist

You'll notice the use of the flash_class function that takes the key as an argument, that's actually a helper method I define in the application_helper.rb in the helpers folder. Used to return the appropriate bootstrap classes based on the flash key.

application_helper.rb

Loading Gist

I've also created a _nav partial that holds our applications navigation in the layouts folder.

_nav.html.erb (views/layouts/_nav.html.erb)

Loading Gist

It's also a good idea to copy over the Devise's views to our project so that we can tweak them

$ rails generate devise:views

Next up, lets create a User model configured with Devise modules, Devise has an awesome generator for this:

$ rails generate devise User
$ rake db:migrate

The generator also configures your config/routes.rb file to point to the Devise controller. You can check the User model for any additional configuration options you might want to add but for our case we will leave it at its defaults. At this point we can update the login and sign up links in our _nav partial to point to the appropriate routes.

_nav.html.erb (views/layouts/_nav.html.erb)

Loading Gist

For our application, we want users to provide their names during registration, lets create a migration to add a name attribute to our users.

$ rails g migration AddNameToUsers name:string
$ rake db:migrate

We then add the name input field to Devise's views

new.html.erb (app/views/devise/registrations/new.html.erb)

Loading Gist

edit.html.erb (app/views/devise/registrations/edit.html.erb)

Loading Gist

Since we are running this app on Rails 4, we will need to whitelist the name parameter. In our application controller file:

application_controller.rb

Loading Gist

Integrating Mailboxer

We can now at this juncture tackle the elephant in the room. We already added the Mailboxer gem already in our Gemfile and we can proceed to install it and update our database.

$ rails g mailboxer:install
$ rake db:migrate

The generator creates an initializer file mailboxer.rb which you should use this to edit the default mailboxer settings. The options are quite straight forward, tweak them to your liking. We will then change the default name_method since we already have the name attribute in our user model to prevent conflict.

mailboxer.rb (config/initializers/mailboxer.rb)

Loading Gist

Preparing our model

For us to equip our User model with Mailboxer functionalities we have to add acts_as_messageable. This will enable us send user-user messages. We also need to add an identity defined by a name and an email in our User model as required by Mailboxer.

Our model must therefore have this specific methods. These methods are: mailboxer_email and mailboxer_name as stated in our mailboxer initializer file. You should make sure to change this when you edit their names in the initializer.

user.rb (app/models/user.rb)

Loading Gist

Preparing Controllers

The way I would like to configure this is having two controllers

  1. MailboxController - This will hold our top-level folders (inbox, sentbox and trash) and will be responsible for displaying the messages in each of this folders.
  2. ConversationsController - This will handle various actions including creating conversations, replies and deletion of messages etc.

Let's start by creating the mailbox controller

$ rails g controller mailbox

We will then add some custom routes for inbox, sentbox and trash in our routes.rb file

routes.rb (config/routes.rb)

Loading Gist

Lets define this methods in our mailbox controller

mailbox_controller.rb (app/controllers/mailbox_controller.rb)

Loading Gist

We will use the @active variable to highlight the currently selected folder in our view. We can now define the mailbox method as a helper method in our application_controller.rb file.

application_controller.rb (app/controllers/application_controller.rb)

Loading Gist

Let's now create a view file for each of the actions in the mailbox controller. Each of this will share the same partial to navigate between the mailbox folders

inbox.html.erb (app/views/mailbox/inbox.html.erb)

Loading Gist

sent.html.erb (app/views/mailbox/sent.html.erb)

Loading Gist

trash.html.erb (app/views/mailbox/trash.html.erb)

Loading Gist

Lets create the folder_view partial, in our mailbox folder, create a new file name it _folder_view.html.erb and paste the following contents.

_folder_view.html.erb (app/views/mailbox/_folder_view.html.erb)

Loading Gist

Inside here, we also make use of another partial called folders. Lets also, in the same mailbox folder create a new file _folders.html.erb and have the following contents

_folders.html.erb (app/views/mailbox/_folders.html.erb)

Loading Gist

Remember the @active instance variable we instantiated in our mailbox controller? lets make a helper method that makes use of that variable to highlight the current page. In our application_helper.rb file add the following method.

application_helper.rb (app/helpers/application_helper.rb)

Loading Gist

For our inbox, it would be a good idea to display the number of unread messages. We will define a helper method in our mailbox_helper.rb file in the helpers directory.

mailbox_helper.rb (app/helpers/mailbox_helper.rb)

Loading Gist

We can now add a link in our navigation bar to link to our inbox page

_nav.html.erb (app/views/layous/_nav.html.erb)

Loading Gist

Clicking the inbox link should take you to our up and running inbox page with navigation already in place for the inbox, sent and trash folders.

Creating Conversations

With our mailbox ready to show messages from our inbox, sent and trash folders, its now time we created functionality to create and send new messages to other users. Lets go ahead and create our second controller (conversations)

$ rails g controller conversations

Let's not forget to add a resources route for our conversations which will also hold other 3 member routes to handle reply, trash and untrashing of messages.

routes.rb (config/routes.rb)

Loading Gist

With the conversations routes in place, lets edit our compose button link in our folder_view partial to point to the new action of our conversations controller.

_folder_view.html.erb (app/views/mailbox/_folder_view.html.erb)

Loading Gist

We now need to create the new method in our conversations controller and its corresponding view file

conversations_controller.rb (app/controllers/conversations_controller.rb)

Loading Gist

new.html.erb (app/views/conversations/new.html.erb)

Loading Gist

Notice we are still using our folder_view partial which is in our mailbox folder. This partial will be responsible for displaying different contents based on the current view. To start with, we are passing in a locale called is_conversation. When this is set to true, we will render the form to create new conversations and messages, else we will show contents for each mailbox folder.

In our views folder, conversations folder, create a form partial called _form.html.erb, that will hold the form to create new conversations.

_form.html.erb (app/views/conversations/_form.html.erb)

Loading Gist

We then need to update our folder_view partial to render our conversation form if the is_conversation variable is set to true.

_folder_view.html.erb (app/views/mailbox/_folder_view.html.erb)

Loading Gist

Let's not forget to update our mailboxes passing the is_conversation locale as false. Update our mailbox view files (inbox.html.erb,sent.html.erb,trash.html.erb) to read

inbox.html.erb, sent.html.erb, trash.html.erb (app/views/mailbox/)

Loading Gist

With this, clicking on the Compose button on either page should take you to the new view of our conversations controller and you should have a beautiful form ready to send messages

With our form up and running, we need to implement the create action in our conversations controller to save the messages to the database. We call Mailboxer's send_message method on the current user and that takes in an array of recipients, the message body and message subject after which we redirect to the show action of our conversations controller.

conversations_controller.rb (app/controllers/conversations_controller.rb)

Loading Gist

As you can see in the show action, we query all messages in the current conversation for the current user and store that in the @reciepts variable. Also, the conversation is actually a helper method we need to define in our application controller to help us get the current conversation.

application_controller.rb (app/controllers/application_controller.rb)

Loading Gist

Creating the show view of the show action:

show.html.erb (app/views/conversations/show.html.erb)

Loading Gist

Notice we call another partial messages in our new view. Create a new partial _messages in the conversations folder.

_messages.html.erb (app/views/conversations/_messages.html.erb)

Loading Gist

Creating a new conversation should successfully redirect you to that specific conversation and you should have your beautiful conversation show page like the one below

Now that we can successfully send a message, we need to mechanism to reply correct? we will need to add a reply form on this page and a subsequent reply action in our conversations controller.

show.html.erb (app/views/conversations/show.html.erb)

Loading Gist

Now lets add the reply action to our controller

conversations_controller.rb (app/controllers/conversations_controller.rb)

Loading Gist

Mailboxer's reply_to_conversation method makes replying to conversations a breeze. It takes in a conversation and the message body and optionally a subject as arguments. If you want users to change the subject during reply, then remember to add the subject text field in the reply form and also in the reply_to_conversation method above as the third argument.

Displaying messages in our mailbox folders

Our mailbox controller views are lonely and we need to show contents in each of them from which users can click on a snippet and view the full message. In this folders, we want to show a snippet of the last message in each unique conversation.

Lets create a new partial conversation inside our conversations folder name it _conversation.html.erb

_conversation.html.erb (app/views/conversations/_conversation.html.erb)

Loading Gist

Updating our inbox, sent and trash views

inbox.html.erb

Loading Gist

sent.html.erb

Loading Gist

trash.html.erb

Loading Gist

In all of our three views, we pass in messages as a locale to our folder_view partial which represents messages from either our inbox, sentbox or trash. Finally lets update our folder_view partial to use the messages locale and consequently render the conversation partial we just created.

_folder_view.html.erb (app/views/mailbox/_folder_view.html.erb)

Loading Gist

You should now be able to see your messages, if any, when you click the inbox and sent links in any view. One last part is remaining, as you can guess, that is ability to delete messages and send them to the trash and also ability to untrash messages which we might have deleted by mistake.

Trashing and Untrashing Messages

We will now need to add a "Move to trash" button in each conversation that is not yet deleted. For those already deleted, we need to show an "Untrash" button.

_conversation.html.erb (app/views/conversations/_conversation.html.erb)

Loading Gist

We now add the corresponding trash and untrash methods in our controller

conversations_controller.rb (app/controllers/conversations_controller.rb)

Loading Gist

As you can see Mailboxer provides handful methods move_to_trash to send messages to the current users's trash box and untrash to move back the message back to the users inbox.

Enhancements

As you many have noted, the current method of selecting recipients is not very efficient especially when the number of users increase, finding a user can become way too tedious. We can improve on this by use of a jQuery plugin called Chosen which makes it dead simple to select from a large list and is more user-friendly. Fortunately for us, there is a chosen-rails gem that makes integrating this plugin into Rails app very easy.

To Install the gem in our rails app, add chosen-rails to your Gemfile and run bundle install

Gemfile

Loading Gist

We then need to add Chosen to application.js and application.css.scss files:

application.js

Loading Gist

application.css

Loading Gist

We then add the chosen-select class to our select dropdown in our conversation's form select field

_form.html.erb (app/views/conversations/_form.html.erb)

Loading Gist

Lastly, lets equip our multiple-select dropdown with some Chosen magic. As we will be using plain jquery, rename conversations.js.coffee to conversations.js and paste in the following code

conversations.js (app/assets/conversations.js)

Loading Gist

Reload the page with your form and you should see jQuery Chosen in action.

Conclusion

That wraps it up for this tutorial and I hope I did a good job of trying to explain the nitty-gritty of implementing a private messaging system using the Mailboxer Gem. Its my hope that this post was useful and as always let me know what you think in the comments section below. Happy Coding!!



35 Comments

___

68b44d11b3bd4eb288adac9a235dc1a431ee855e

mrblack

09 Apr 15

It's cool. Very clear explanation.

511f717a3d3fe82dfe2524054c3d8b2a08fad926

Sean Kelley

26 Apr 15

Hi I am working through this and in: app/views/conversations/show.html.erb the line: should be:

511f717a3d3fe82dfe2524054c3d8b2a08fad926

Sean Kelley

26 Apr 15

comments cut off... I changed form_for :message, url: reply_conversation_path(conversation) to form_for :message, url: reply_conversation_path(@conversation)

511f717a3d3fe82dfe2524054c3d8b2a08fad926

Sean Kelley

26 Apr 15

Sorry- I missed adding :conversation to the application_controller as a helper.

Dd4bf77cd0a642f111a26ff16377a6793a3b1e4e

shalafister

27 Apr 15

Hi!, amazing tutorial thank you very much. I have a problem with js code. I can upload photos and delete photos by clicking the remove button. It actually removes from the database however, keeps the pictures on the screen. They are not vanished. Here is my code, I had to change a bit as I have associated models (boat & picture), If you can help I really appreciate, have been trying to solve for a long time;

Dd4bf77cd0a642f111a26ff16377a6793a3b1e4e

shalafister

27 Apr 15

Dd4bf77cd0a642f111a26ff16377a6793a3b1e4e

shalafister

27 Apr 15

//when the remove button is clicked removedfile: function(file){ //location.reload(); //removeFile(file); // grap the id of the uploaded file we set earlier var id = $(file.previewTemplate).find('.dz-remove').attr('id'); var boat_id = $(file.previewTemplate).find('.dz-remove').attr('boat_id'); // // make a DELETE ajax request to delete the file $.ajax({ type: 'DELETE', url: '/boats/' + boat_id + '/pictures/' + id, success: function(file){ console.log(data.message); } }); }

C4b6a5649fe857c30c6b438a0c1486d8940e2f14

mattshanklin

15 May 15

Great tutorial!!!!! I'm a rails newb and wanted to know how you would customize the conversation/new on each users profile. So if I go to the default user page that there is a "message me" button to message the person directly?

D639288e53643148ac3375030474a47f5d7dd9c6

tye

18 Jul 15

Great work. For the "TRASHING AND UNTRASHING MESSAGES" section, the file "show.html.erb" instead of "_conversation.html.erb ". Still great work anyways:)

D639288e53643148ac3375030474a47f5d7dd9c6

tye

19 Jul 15

Just in case anyone experience error with Devise migration, like this "NoMethodError: undefined method `merge!' for # in your Gemfile. Link http://goo.gl/7IR8h7

Ddd1e5a555361b721657a172e0edad8560c6359b

Michael Devenport

17 Aug 15

Hi Joseph, i'm still new to app-development and could do with a point in the right direction. I have implemented user avatars using paperclip which is working, i can call <%= image_tag(current_user.avatar.url(:thumb)) %> within the layouts nav partial, but i would like to replace the 'placeholders/images' with according user avatars. How do i render user avatar images in _conversations.html.erb and _messages.html.erb ? Thanks.

Ddd1e5a555361b721657a172e0edad8560c6359b

Michael Devenport

17 Aug 15

Hi Joseph, Thanks for your response but i'm confused, your answer does not contain any code? I might be new to RoR but even i know ' .. ' will not resolve my issue .. Could you please send the code to my email at croxleyway@gmail.com or repost here or tweet me maybe. Thanks very much ;-)

Ddd1e5a555361b721657a172e0edad8560c6359b

Michael Devenport

17 Aug 15

Got it ! THANK YOU SO MUCH, i love your work my friend these tutorials are awesome . Checkout your gmail/hangouts real time messaging app i deployed with Avatars at http://www.efilecabi.net/ Thank you. Michael

308b0a885faf4c854e4877ee1e98ffe63c2ca6be

Maqnai2234

02 Sep 15

Thank, you, the most tutorial!

B855b1290c2152ecedc5162c2de2c14e8eb5ea83

Manuel

07 Sep 15

The email notifications don't work? Any source where i can readup on it?

B855b1290c2152ecedc5162c2de2c14e8eb5ea83

Manuel

07 Sep 15

Hey Jo, Did that > then i pushed it up to heroku: https://enigmatic-earth-306... Still get an error: https://enigmatic-earth-306...

B855b1290c2152ecedc5162c2de2c14e8eb5ea83

Manuel

08 Sep 15

Set up mail with Mandrill - Now I'm looking to migrate new message fields - I'm thinking rails g migration add_fields_to_message title:string other:text other:text

680e03ffcbf7fac514db701d858234e853bf3eef

Aaronman2

12 Sep 15

Hey Joseph, the tutorial was amazing thanks for that but i have a question, i wanna ask how you added search system to search Recipients because right now i followed your tutorial and got the app working but instead of the search it's giving me select menu with the name of users

Cb38603ba15eb026969dd6e806faae954792ddec

naiyyar

03 Oct 15

Hi Joseph, thank you very much, these tutorials are awesome. i have a question, How can i add file attachment option with paperclip? i tried adding below code in initializers/mailboxer.rb but didn't get success. Notification.class_eval do has_attached_file :attachment end Thanks

12b87a83884651906691a192a0c0ffe419bc8906

hktangudu

14 Oct 15

hi, i am getting an below error Showing C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/mailboxer-0.13.0/app/views/mailboxer/message_mailer/new_message_email.html.erb where line #17 raised: Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true can you help me ?

9bd31c4e2c4d0e3ea84bea6050ebce706c1a5e13

leongg

30 Oct 15

Joseph awesome !! got a question, is there a way to mix you gmail like chat app with this tutorial? Thanks!

585ab7bdf9718277a10035d84256436b0b8ca134

Joseph William Hays

04 Dec 15

An amazing tutorial! However, I keep running into a problem with the @receipts. I'm getting an error from Action Controller: ` NoMethodError in Mailbox#inbox: undefined method `each' for nil:NilClass`. What's going on here?

A2b4c5e13dd82363cb3ee2ac876242c076f680cb

Himal

08 Dec 15

Hey there! This tutorial was a huge help to me, I really appreciate the time you put into this tutorial. I unfornuatly was unable to get the chosen part working. I followed your steps but the form did not change at all. Do you know why this would be? Thanks!

46d36f52527ad81a4d68858b063e16662f3f9fa9

RakeshTejra

15 Dec 15

HI Is this possible to create more than two conversations between two same users? Because I tried this and it said: "Validation failed: Notification base This message must be added to existing conversation (#)" Thanks!

Dd330e1dd0f2a05bdf058e7888e4bee2c7b58bfe

Joseph Ndungu mod

02 Apr 16

The partial should be in your conversations folder. Please check the example app used for this tutorial to spot where you went wrong https://github.com/Joseph-N...

Ff04eb4185f8f84b4741ce0bcc1c083a71e6a984

Baptiste Becmeur

02 Apr 16

Hey Joseph, I don't understand your example can't work. There is a template missing error.

Ed6a21df10c68d316472593da04792383702e6dd

charleswdickstein

30 Jan 16

Hello, Great tutorial. I have an issue when a user clicks Message in a Users#index: The user still has to click "Compose" on the next page then has to find the user he wants to message. I want to be able to click "Message" in the user index and start a new conversation with that user immediately after. I have tried variations of code like but to no avail. Thanks!

Ff04eb4185f8f84b4741ce0bcc1c083a71e6a984

Baptiste Becmeur

02 Apr 16

Hey Joseph, Thank you so much for this Tuto! I have a question I follow step by step what you suggest and I have a little error. The mailbox/sent view doesn't appears. Here is the message error: Missing partial mailbox/_messages, application/_messages with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :jbuilder]}. Maybe I miss a step. Your help is appreciated. Thank you.

104ee16b7b52389e50e58831bca18a56f6d04c81

Qasem Hajizadeh

26 Jun 16

Hi Joseph You can make a tutorial for your commenting system ? Thanks

B5a5cb919da466ff00c30317ce920a8aa481ad12

ajazshaik

29 Jul 16

Hi Joseph can you make a tutorial to send sms to users

B17b0b2b231b1df03a32999d18a38fdea227c6ed

CharlesEricLD

12 Sep 16

Hi Joseph, Thanks for that tutorial !! Do you know how it would be possible to hide the recipient field and add a button on a different show page like "Send a message @User1" ?

29457e9c396ba33889af100c8da8c25c67f5d85e

naveen1336

16 Mar 17

how to add attachment to the mails.

Bb702caa156f1b6afc4b709368243694f4d24ce6

john-lanticse

19 Jul 17

The following is the error i encountered and how i solved it. Error: Please provide the :host parameter, set default_url_options[:host], or set :only_path Solution: Adding this in the routes default_url_options :host => "example.com" Error: Here is the message error: Missing partial mailbox/_messages, application/_messages or ` NoMethodError in Mailbox#inbox: undefined method `each' for nil:NilClass` Solution: the trashing and untrashing views should be in the show.html.erb not in the _conversation.html.erb

Bb4ce44fddf2e8a3053261e956aa345fba6287e8

RailsCod3rFuture

18 Aug 17

Can you post a version this tutorial with multiple model type support. User Admin or Buyer Seller?

Bb4ce44fddf2e8a3053261e956aa345fba6287e8

RailsCod3rFuture

19 Aug 17

Can you post a version this tutorial with multiple model type support. User Admin or Buyer Seller?

Latest Tutorials

___

Private Inbox System in Rails with Mailboxer New

Introduction It's been quite a while since my last tutorial and since then I've recieved alot of requests by email to implement a private messaging system ...

Ajax Sortable Lists Rails 4

With me, is a simple to-do list application where users can create dummy to-do lists and displays them in card-like form just like in Trello. We want to e...

Managing ENV variables in Rails

Often when developing Rails applications, you will find a need to setup a couple of environment variables to store secure information such as passwords, a...

Gmail Like Chat Application in Ruby on Rails

Introduction We are all fond of the Gmail and Facebook inline chat modules. About a week ago, I came across a tutorial on how to replicate hangouts chat...

Fast Autocomplete Search Terms - Rails

Introduction In many cases you find that you need to introduce a global search in your rails application i.e. search multiple models at a go using one form...

Load more scroll top