Let's run our own chat platform

The pandemic has currently gone on for a while and it seems that it'll continue doing so for at least a year or two more. During this time, many industries have realized that they don't really need to be in the same room to do productive work (though there's a lot of nuance to it), however that also makes us re-evaluate how we communicate with one another and how we can best organize our work and information exchange during this time.

Oftentimes, that is achieved with chat platforms:

chat platform example

(example image of a chat UI from the Internet, not to expose any internal data)

Some companies had already adopted an asynchronous model of communications before all of this, while for others something like that was essentially unthinkable. And even when we're not talking about just the cultural aspects, we still need to consider the safety of our data, whether it'll be available later, as well as the tradeoffs of centralized vs decentralized systems. Despite these many factors at play here, we need to find solutions that can best suit our needs and make the transition as easy as possible for those who aren't used to this mode of communication yet.

Thus, i believe that there is some benefit to running your own chat platform. Let me give you an example.

The Why

This example will be of the company that i currently work for. They are amongst those that had instant messaging platforms in use, however there wasn't one platform for the entirety of the company to use. This lead to some teams using Slack, others using Teams, while there were also some that used Skype. On the page of employees and how to contact them, there were sometimes Skype details, however it'd be pretty odd to reach out to someone on Skype and be told to join Slack, but have to wait for approval/invitations, or even worse yet, use both channels for communication inconsistently.

For example, my team uses Slack for most of our communications, except we also use Skype for meetings, whereas our clients want to use Teams, whereas others prefer Zoom for that. Wouldn't it be nice to have a single integrated solution? Yet, the problem here is not just permissions management and the encumbrance of having to switch between so many platforms. It's also the fact that such decentralized usage of the apps not only makes data harder to keep track of and search for, but also that sooner or later silos will develop!

If i want to ask the people in the company whether anyone has heard of a particular weird Java error, there is no actual way for me to do so. Or if i want to ask them whether a particular tool within the company is down or perhaps someone has used some technologies that i might consider for a project, there's also no way for me to do so, without asking all of the individuals about whose opinions i care separately, something that would surely make me miss out on a lot of useful information!

distribution example

While making the above image, i actually realized that i have no idea how to keep track of where everyone is, without checking each made up person individually, so even making an example like that is hard, whereas reality is worse! Speaking of reality, there are products out there, that will cut off the history of your messages if you use their free offerings, like Slack does:

Slack limitations

Shouldn't most enterprises just pay for proper team licenses and not run into these problems? They should, and yet, they don't! A lot of companies out there won't pass the Joel Test, for a variety of reasons, since billing can actually get pretty complicated. What's worse, many don't see anything wrong with their older messages disappearing and not being able to find them, which is about as bad as creating merge requests for your codebases that don't actually attempt to explain the context and reasoning behind your changes, instead just leaving the description field blank, which will puzzle people when they stumble upon that merge request in a few months when something breaks.

But i digress. In my eyes, a self-hosted solution where you have full access to the data is almost always better, especially because of the benefits that it provides in regards to organization and being able to communicate across the boundaries of your teams, thus eliminating these silos and enabling people to converse more easily!

centralization example

We actually had a company forum, but since that form of communication is so detached from the ease of use of IM, it gets almost no activity whatsoever. Thus, an IM solution that you can use for most projects is perhaps one of the better approaches. So, how do we get one working?

The How

The first thing that you'll need will be non-technical: the support of others. You can't attempt to introduce technical solutions that would improve the lives of everyone, if they just don't care about it or will provide outright opposition against it.

For example, in the said company example there was a group of enthusiasts who wanted to introduce such a platform, but before long there were conflicting discussions about whether it'd be better to use Rocket.Chat, Nextcloud Talk, Element or something else, the people who were against a particular tool eventually just leaving the company and only leaving the roadblocks behind them. In the end, the whole initiative was stonewalled by system administrators who didn't want to spare the resources for such a pet project and a corporate policy simply disallowed using certain cloud based tools, something that may or may not have been ignored by some teams.

If you can, you'll want to get a test instance of some sort up and running ASAP. It's better to ask for forgiveness than for permission (in most cases), especially if that means keeping your communication data on company servers within the internal network, as opposed to hoping that "But everyone's Slack/Teams/Skype got hacked" will be a good enough excuse when the time comes to answer for your usage of SaaS, something that most companies simply choose to ignore and pretend isn't a serious risk.

For the purposes of this tutorial, i'll be describing how to get Rocket.Chat running, though others also have had luck with the aforementioned Nextcloud Talk (especially if you already use Nextcloud) or alternative solutions. For my personal needs, Rocket.Chat is wonderfully suitable!

To get this instance running, you'll need:

  • a server, be it a VM or something else, most *nix distros should work
  • a domain name with the appropriate A/AAAA records and an SSL/TLS certificate (or the ability to use Let's Encrypt, possibly with a DNS-01 challenge)
  • an ingress of some sort that can act as a reverse proxy should also allow you to simplify things, for example, Nginx
  • ideally, something like Docker for making your app easier to run and maintain
  • additionally, you'll also want a mail server, or just an SMTP account that will allow you to send e-mails for missed messages etc.

So, with Docker the software configuration aspects can look as simple as the following (Docker Compose format example, for Docker Swarm):

version: '3.4'

services:
  rocketchat_nginx:
    image: nginx:1.21.4
    ports:
      - target: 80
        published: 80
        protocol: tcp
        mode: host
      - target: 443
        published: 443
        protocol: tcp
        mode: host
    volumes:
      - /home/docker/chat/data/rocketchat_nginx/etc/nginx/nginx.conf:/etc/nginx/nginx.conf
      - /home/docker/chat/data/rocketchat_nginx/certs:/certs
    networks:
      - ingress_chat_network
    deploy:
      placement:
        constraints:
          - node.hostname == chat.your-company.com
      resources:
       limits:
         memory: 1024M
         cpus: '1.0'
  rocketchat_app:
    image: rocketchat/rocket.chat:4.1.2
    command: >
      bash -c
        "for i in `seq 1 30`; do
          node main.js &&
          s=$$? && break || s=$$?;
          echo \"Tried $$i times. Waiting 5 secs...\";
          sleep 5;
        done; (exit $$s)"
    volumes:
      - /home/docker/chat/data/rocketchat_app/app/uploads:/app/uploads
    environment:
      - PORT=3000
      - ROOT_URL=https://chat.your-company.com
      - MONGO_URL=mongodb://rocketchat_mongo:27017/rocketchat
      - MONGO_OPLOG_URL=mongodb://rocketchat_mongo:27017/local?replSet=rs01
    depends_on:
      - rocketchat_mongo
    networks:
      ingress_chat_network:
        aliases:
          - "rocketchat-app"
    deploy:
      placement:
        constraints:
          - node.hostname == chat.your-company.com
      resources:
       limits:
         memory: 1024M
         cpus: '1.0'
  rocketchat_mongo:
    image: mongo:4.4.10
    command: mongod --replSet rs0
    volumes:
     - /home/docker/chat/data/rocketchat_mongodb/data/db:/data/db
    networks:
      - ingress_chat_network
    deploy:
      placement:
        constraints:
          - node.hostname == chat.your-company.com
      resources:
       limits:
         memory: 1024M
         cpus: '1.0'
  rocketchat_mongo_init_replica:
    image: mongo:4.4.10
    command: >
      bash -c
        "for i in `seq 1 30`; do
          mongo rocketchat_mongo/rocketchat --eval \"
            rs.initiate({
              _id: 'rs0',
              members: [ { _id: 0, host: 'localhost:27017' } ]})\" &&
          s=$$? && break || s=$$?;
          echo \"Tried $$i times. Waiting 5 secs...\";
          sleep 5;
        done; (exit $$s)"
    depends_on:
      - rocketchat_mongo

networks:
  ingress_chat_network:

Now, the configuration can change a bit from version to version, since some of the previously recommended options (e.g. the storage engine mmapv1 or the --smallfiles flag are no longer supported in MongoDB), however the above example should get you 80% of the way there. Combine that with the following Nginx configuration:

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  /var/log/nginx/access.log  main;
    sendfile        on;
    #tcp_nopush     on;
    keepalive_timeout  65;
    gzip  on;
    #include /etc/nginx/conf.d/*.conf;

    server {
        listen 80;
        server_name chat.your-company.com;
        return 301 https://$host$request_uri;
    }
    server {
        listen 443 ssl http2;
        server_name chat.your-company.com;

        # Configuration for SSL/TLS certificates
        ssl_certificate /certs/chat.your-company.com.crt;
        ssl_certificate_key /certs/chat.your-company.com.key;

        # Disable insecure TLS versions
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_prefer_server_ciphers on;
        ssl_ciphers HIGH:!aNULL:!MD5;

        # Proxy headers
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Proxy to Rocket.Chat
        location / {
            resolver 127.0.0.11 valid=30s; # Docker DNS
            proxy_pass http://rocketchat-app:3000/;
            proxy_redirect default;
        }
    }
}

And you've properly embraced infrastructure as code!

If it all works as it should, you'll see something like the following in the logs:

RocketChat running example

And opening the URL for the first time in the browser should yield the following:

RocketChat installation example

So, all that's left is to configure the instance! When you first set it up, it will probably be usable, if a bit barren. Thankfully, everything that you'll probably want to have a look at will be available in the admin options menu:

Newly set up instance

So, in no particular order, you'll probably want to go through the following points...

Accounts and registration

You'll probably want to set the options pertaining to the usage of real names and usernames, as well as figure out whether your instance should have open registration, or closed one. Of course, you can also hide the registration page behind a secret page, or have the registrations be approved manually, as i did for my instance - anyone can request access, but i choose to whom to grant it.

If you're using AD for managing access to your other resources within an organization or a community, then you'll probably want to check whether it's possible for you to integrate Rocket.Chat with it through LDAP, ideally freeing you from manual account management.

E-Mail

If you're using account vertification functionality or want people to receive reminders about things like missed messages, or allow easy password resets, adding at least the SMTP configuration is a must! Thankfully, it should be relatively simple, especially if you run your own mail server. Otherwise, consider using something like GMail, but be aware of the limitations that you might run into (especially with higher volumes of traffic).

Video calling

Rocket.Chat is working on having their own WebRTC based solution for video calls, but it's still work in progress, so instead i'd suggest that you just integrate with Jitsi, a slightly more privacy conscious solution when compared with Zoom, but one that you can optionally also self-host. Admittedly, for many of the communities out there, self-hosting a video chat platform is way harder due to hardware/bandwidth constraints, so using their free instances is still a viable and functional approach, since the calls show up within the Rocket.Chat UI and the UX is rather good!

Other settings

You'll probably also want to check the file storage configuration (since S3 is supported), rate limiting configuration, as well as a variety of other settings pertaining to the overall user experience and some of the more administratory concerns that you might run into. Personally, i'd also suggest that you consider integrating with Matomo analytics as well, to see how your instance is used, about which i wrote more in another blog article.

Summary

Now, the information above isn't meant to necessarily hold your hand throughout all of the process, because the actual documentation is pretty good, however me setting up such a test instance in the background in less than a day should be a demonstration that it's definitely possible to do so!

And, personally, i think that the end result of all that is rather nice, suddenly you could have everyone in your corporation chatting in the same place, with access rights also being easy to manage:

the end result

Not only that, but you'd no longer need to have Skype, Slack or multiple such apps on your phone, since you could just install the official Rocket.Chat app:

RocketChat app

Both the battery of your phone, as well as your future self will thank you, especially once you'll need to search for your older messages that you won't be able to do because the corporation didn't feel like paying for Slack, even though they have server rooms with resources to spare. Not only that, but Rocket.Chat and many of the other collaboration tools are pretty versatile - even if you just want to chat with a group of friends, have a server for D&D, one for sharing classroom notes, you'll probably find what you're looking for.

The perhaps biggest thing working against you in these circumstances might be the Network effect, which also cannot be dismissed, however you might brush up on your ability to convince others in that case, should you have indeed picked the right use case for which the tools are well suited. It has worked out for me really well, though, since i offer my own Rocket.Chat instance as one of the ways to contact me - most people still prefer just writing me an e-mail, but for those who like IM more, this is a feasible alternative.