diff --git a/bigbluebutton-html5/imports/api/1.1/chat/server/methods.js b/bigbluebutton-html5/imports/api/1.1/chat/server/methods.js
index 940de17c0cbb3626ba986cad9b6066b6987d45c0..6ce8893022abca29a65e3c1d79edb2c6aaac9f6b 100644
--- a/bigbluebutton-html5/imports/api/1.1/chat/server/methods.js
+++ b/bigbluebutton-html5/imports/api/1.1/chat/server/methods.js
@@ -1,7 +1,3 @@
-import mapToAcl from '/imports/startup/mapToAcl';
 import { Meteor } from 'meteor/meteor';
-import sendChat from './methods/sendChat';
 
-Meteor.methods(mapToAcl(['methods.sendChat'], {
-  sendChat,
-}));
+Meteor.methods({});
diff --git a/bigbluebutton-html5/imports/api/2.0/chat/index.js b/bigbluebutton-html5/imports/api/2.0/chat/index.js
new file mode 100755
index 0000000000000000000000000000000000000000..19531e9c47309b698db8be80cbc09714157f3ccd
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/2.0/chat/index.js
@@ -0,0 +1 @@
+export default new Mongo.Collection('chat2x');
diff --git a/bigbluebutton-html5/imports/api/2.0/chat/server/eventHandlers.js b/bigbluebutton-html5/imports/api/2.0/chat/server/eventHandlers.js
new file mode 100644
index 0000000000000000000000000000000000000000..4e66deea9e0177ca2457a6ba90cef6a6d0f9a3e9
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/2.0/chat/server/eventHandlers.js
@@ -0,0 +1,7 @@
+import RedisPubSub from '/imports/startup/server/redis2x';
+import handleChatMessage from './handlers/chatMessage';
+import handleChatHistory from './handlers/chatHistory';
+
+RedisPubSub.on('GetChatHistoryRespMsg', handleChatHistory);
+RedisPubSub.on('SendPublicMessageEvtMsg', handleChatMessage);
+RedisPubSub.on('SendPrivateMessageEvtMsg', handleChatMessage);
diff --git a/bigbluebutton-html5/imports/api/2.0/chat/server/handlers/chatHistory.js b/bigbluebutton-html5/imports/api/2.0/chat/server/handlers/chatHistory.js
new file mode 100644
index 0000000000000000000000000000000000000000..40293145a550463438d868f4b49047f9a551934e
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/2.0/chat/server/handlers/chatHistory.js
@@ -0,0 +1,17 @@
+import { check } from 'meteor/check';
+import addChat from '../modifiers/addChat';
+
+export default function handleChatHistory({ body }, meetingId) {
+  const { history } = body;
+
+  check(meetingId, String);
+  check(history, Array);
+
+  const chatsAdded = [];
+
+  history.forEach((message) => {
+    chatsAdded.push(addChat(meetingId, message));
+  });
+
+  return chatsAdded;
+}
diff --git a/bigbluebutton-html5/imports/api/2.0/chat/server/handlers/chatMessage.js b/bigbluebutton-html5/imports/api/2.0/chat/server/handlers/chatMessage.js
new file mode 100644
index 0000000000000000000000000000000000000000..4ee789871cce0ef06cb75b9e0bf8fd4e9f00db14
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/2.0/chat/server/handlers/chatMessage.js
@@ -0,0 +1,11 @@
+import { check } from 'meteor/check';
+import addChat from '../modifiers/addChat';
+
+export default function handleChatMessage({ body }, meetingId) {
+  const { message } = body;
+
+  check(meetingId, String);
+  check(message, Object);
+
+  return addChat(meetingId, message);
+}
diff --git a/bigbluebutton-html5/imports/api/2.0/chat/server/index.js b/bigbluebutton-html5/imports/api/2.0/chat/server/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..92451ac76bf27410726e8f3cd2eebac46cd7b83e
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/2.0/chat/server/index.js
@@ -0,0 +1,3 @@
+import './eventHandlers';
+import './methods';
+import './publishers';
diff --git a/bigbluebutton-html5/imports/api/2.0/chat/server/methods.js b/bigbluebutton-html5/imports/api/2.0/chat/server/methods.js
new file mode 100644
index 0000000000000000000000000000000000000000..940de17c0cbb3626ba986cad9b6066b6987d45c0
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/2.0/chat/server/methods.js
@@ -0,0 +1,7 @@
+import mapToAcl from '/imports/startup/mapToAcl';
+import { Meteor } from 'meteor/meteor';
+import sendChat from './methods/sendChat';
+
+Meteor.methods(mapToAcl(['methods.sendChat'], {
+  sendChat,
+}));
diff --git a/bigbluebutton-html5/imports/api/2.0/chat/server/methods/sendChat.js b/bigbluebutton-html5/imports/api/2.0/chat/server/methods/sendChat.js
new file mode 100755
index 0000000000000000000000000000000000000000..f1afbf068a0f343d827c11d610251dfb945e6911
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/2.0/chat/server/methods/sendChat.js
@@ -0,0 +1,59 @@
+import { Meteor } from 'meteor/meteor';
+import { check } from 'meteor/check';
+import RedisPubSub from '/imports/startup/server/redis';
+import RegexWebUrl from '/imports/utils/regex-weburl';
+
+const HTML_SAFE_MAP = {
+  '<': '&lt;',
+  '>': '&gt;',
+  '"': '&quot;',
+  "'": '&#39;',
+};
+
+const parseMessage = (message) => {
+  let parsedMessage = message || '';
+  parsedMessage = parsedMessage.trim();
+
+  // Replace <br/> with \n\r
+  parsedMessage = parsedMessage.replace(/<br\s*[\\/]?>/gi, '\n\r');
+
+  // Sanitize. See: http://shebang.brandonmintern.com/foolproof-html-escaping-in-javascript/
+  parsedMessage = parsedMessage.replace(/[<>'"]/g, c => HTML_SAFE_MAP[c]);
+
+  // Replace flash links to flash valid ones
+  parsedMessage = parsedMessage.replace(RegexWebUrl, "<a href='event:$&'><u>$&</u></a>");
+
+  return parsedMessage;
+};
+
+export default function sendChat(credentials, message) {
+  const REDIS_CONFIG = Meteor.settings.redis;
+  const CHANNEL = REDIS_CONFIG.channels.toBBBApps.chat;
+
+  const CHAT_CONFIG = Meteor.settings.public.chat;
+  const PUBLIC_CHAT_TYPE = CHAT_CONFIG.type_public;
+
+  const { meetingId, requesterUserId, requesterToken } = credentials;
+
+  check(meetingId, String);
+  check(requesterUserId, String);
+  check(requesterToken, String);
+  check(message, Object);
+
+  let eventName = 'SendPrivateMessagePubMsg';
+  const parsedMessage = message;
+
+  parsedMessage.message = parseMessage(message.message);
+
+  if (message.chat_type === PUBLIC_CHAT_TYPE) {
+    eventName = 'SendPublicMessagePubMsg';
+  }
+
+  const payload = {
+    parsedMessage,
+    meetingId,
+    requesterId: message.fromUserid,
+  };
+
+  return RedisPubSub.publish(CHANNEL, eventName, payload);
+}
diff --git a/bigbluebutton-html5/imports/api/2.0/chat/server/modifiers/addChat.js b/bigbluebutton-html5/imports/api/2.0/chat/server/modifiers/addChat.js
new file mode 100755
index 0000000000000000000000000000000000000000..38b4aa3d9679f69607763906ab89b73b7592845f
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/2.0/chat/server/modifiers/addChat.js
@@ -0,0 +1,62 @@
+import Chat from '/imports/api/2.0/chat';
+import Logger from '/imports/startup/server/logger';
+import { check } from 'meteor/check';
+import { BREAK_LINE } from '/imports/utils/lineEndings';
+
+const parseMessage = (message) => {
+  let parsedMessage = message || '';
+
+  // Replace \r and \n to <br/>
+  parsedMessage = parsedMessage.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, `$1${BREAK_LINE}$2`);
+
+  // Replace flash links to html valid ones
+  parsedMessage = parsedMessage.split('<a href=\'event:').join('<a target="_blank" href=\'');
+  parsedMessage = parsedMessage.split('<a href="event:').join('<a target="_blank" href="');
+
+  return parsedMessage;
+};
+
+export default function addChat(meetingId, message) {
+  const parsedMessage = parseMessage(message.message);
+
+  const fromUserId = message.fromUserId;
+  const toUserId = message.toUserId;
+
+  check(fromUserId, String);
+  check(toUserId, String);
+
+  const selector = {
+    meetingId,
+    'message.fromTime': message.fromTime,
+    'message.fromUserId': message.fromUserId,
+    'message.toUserId': message.toUserId,
+  };
+
+  const modifier = {
+    $set: {
+      meetingId,
+      message: {
+        message: parsedMessage,
+        toUsername: message.toUsername,
+        fromTimezoneOffset: message.fromTimezoneOffset,
+        fromColor: message.fromColor,
+        toUserId: message.toUserId,
+        fromUserId: message.fromUserId,
+        fromTime: message.fromTime,
+        fromUsername: message.fromUsername,
+      },
+    },
+  };
+
+  const cb = (err, numChanged) => {
+    if (err) {
+      return Logger.error(`Adding chat to collection: ${err}`);
+    }
+
+    const { insertedId } = numChanged;
+    const to = message.toUsername || 'PUBLIC';
+    return Logger.info(`Added chat id=${insertedId} from=${message.fromUsername} to=${to}`);
+  };
+
+  return Chat.upsert(selector, modifier, cb);
+}
diff --git a/bigbluebutton-html5/imports/api/2.0/chat/server/modifiers/clearChats.js b/bigbluebutton-html5/imports/api/2.0/chat/server/modifiers/clearChats.js
new file mode 100755
index 0000000000000000000000000000000000000000..b2ecab2d591acbf6cf2e7f494d2e0126a6959402
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/2.0/chat/server/modifiers/clearChats.js
@@ -0,0 +1,10 @@
+import Chat from '/imports/api/2.0/chat';
+import Logger from '/imports/startup/server/logger';
+
+export default function clearChats(meetingId) {
+  if (meetingId) {
+    return Chat.remove({ meetingId }, Logger.info(`Cleared Chats (${meetingId})`));
+  }
+
+  return Chat.remove({}, Logger.info('Cleared Chats (all)'));
+}
diff --git a/bigbluebutton-html5/imports/api/2.0/chat/server/modifiers/clearUserSystemMessages.js b/bigbluebutton-html5/imports/api/2.0/chat/server/modifiers/clearUserSystemMessages.js
new file mode 100644
index 0000000000000000000000000000000000000000..32053f04bda6f419731de405b93770ff90f62c63
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/2.0/chat/server/modifiers/clearUserSystemMessages.js
@@ -0,0 +1,24 @@
+import Chat from '/imports/api/2.0/chat';
+import Logger from '/imports/startup/server/logger';
+import { check } from 'meteor/check';
+
+/**
+ * Remove any system message from the user with userId.
+ *
+ * @param {string} meetingId
+ * @param {string} userId
+ */
+export default function clearUserSystemMessages(meetingId, userId) {
+  check(meetingId, String);
+  check(userId, String);
+
+  const CHAT_CONFIG = Meteor.settings.public.chat;
+
+  const selector = {
+    meetingId,
+    'message.fromUserid': CHAT_CONFIG.type_system,
+    'message.toUserid': userId,
+  };
+
+  return Chat.remove(selector, Logger.info(`Removing system messages from: (${userId})`));
+}
diff --git a/bigbluebutton-html5/imports/api/2.0/chat/server/publishers.js b/bigbluebutton-html5/imports/api/2.0/chat/server/publishers.js
new file mode 100644
index 0000000000000000000000000000000000000000..b005e9e845726df4ccbe224074340e714b8652aa
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/2.0/chat/server/publishers.js
@@ -0,0 +1,41 @@
+import Chat from '/imports/api/2.0/chat';
+import { Meteor } from 'meteor/meteor';
+import { check } from 'meteor/check';
+import Logger from '/imports/startup/server/logger';
+
+import mapToAcl from '/imports/startup/mapToAcl';
+
+function chat(credentials) {
+  const CHAT_CONFIG = Meteor.settings.public.chat;
+  const PUBLIC_CHAT_TYPE = CHAT_CONFIG.type_public;
+
+  const { meetingId, requesterUserId, requesterToken } = credentials;
+
+  check(meetingId, String);
+  check(requesterUserId, String);
+  check(requesterToken, String);
+
+  Logger.info(`Publishing chat for ${meetingId} ${requesterUserId} ${requesterToken}`);
+
+  return Chat.find({
+    $or: [
+      {
+        'message.chatType': PUBLIC_CHAT_TYPE,
+        meetingId,
+      }, {
+        'message.fromUserid': requesterUserId,
+        meetingId,
+      }, {
+        'message.toUserid': requesterUserId,
+        meetingId,
+      },
+    ],
+  });
+}
+
+function publish(...args) {
+  const boundChat = chat.bind(this);
+  return mapToAcl('subscriptions.chat', boundChat)(args);
+}
+
+Meteor.publish('chat2x', publish);
diff --git a/bigbluebutton-html5/imports/startup/client/base.jsx b/bigbluebutton-html5/imports/startup/client/base.jsx
index 8fe54898f4bcebf4040ade8b219490e12920262c..12a4d3060b4f572815e0dd753845488292dfa5d8 100644
--- a/bigbluebutton-html5/imports/startup/client/base.jsx
+++ b/bigbluebutton-html5/imports/startup/client/base.jsx
@@ -84,7 +84,7 @@ Base.propTypes = propTypes;
 Base.defaultProps = defaultProps;
 
 const SUBSCRIPTIONS_NAME = [
-  'users2x', 'users', 'chat', 'cursor', 'cursor2x', 'deskshare', 'meetings', 'meetings2x',
+  'users2x', 'users', 'chat', 'chat2x', 'cursor', 'cursor2x', 'deskshare', 'meetings', 'meetings2x',
   'polls', 'presentations', 'presentations2x', 'shapes', 'shapes2x', 'slides', 'slides2x', 'captions', 'breakouts',
 ];
 
diff --git a/bigbluebutton-html5/imports/startup/server/redis2x.js b/bigbluebutton-html5/imports/startup/server/redis2x.js
index a3b38b95e7ff25904fa9322dce63d9b8e7b3639d..bbb64c04434147a991a2e059ca938318c5065aca 100644
--- a/bigbluebutton-html5/imports/startup/server/redis2x.js
+++ b/bigbluebutton-html5/imports/startup/server/redis2x.js
@@ -105,7 +105,7 @@ class RedisPubSub2x {
     Logger.info(`2.0 QUEUE | PROGRESS ${this.queue.progress()}% | LENGTH ${this.queue.length()}} ${eventName} | CHANNEL ${channel}`);
 
     // We should only handle messages from this two channels, else, we simple ignore them.
-    if (channel !== fromAkkaApps || channel !== toHTML5) {
+    if (channel !== fromAkkaApps && channel !== toHTML5) {
       Logger.warn(`The following message was ignored: CHANNEL ${channel} MESSAGE ${message}`);
       return;
     }
diff --git a/bigbluebutton-html5/server/main.js b/bigbluebutton-html5/server/main.js
index 30fa511fcf34757bcd3b0f9c191957aaf2779605..98f14fdb955f3ca7d3b9c782933db11a3b34102b 100755
--- a/bigbluebutton-html5/server/main.js
+++ b/bigbluebutton-html5/server/main.js
@@ -18,6 +18,7 @@ import '/imports/api/2.0/shapes/server';
 import '/imports/api/2.0/cursor/server';
 import '/imports/api/2.0/presentations/server';
 import '/imports/api/2.0/slides/server';
+import '/imports/api/2.0/chat/server';
 
 // Commons
 import '/imports/api/log-client/server';