diff --git a/.pnp.cjs b/.pnp.cjs
index c1a5a3a5e..cd7f836b1 100644
--- a/.pnp.cjs
+++ b/.pnp.cjs
@@ -47,6 +47,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
             ["@percy/cypress", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:3.1.2"],\
             ["@popperjs/core", "npm:2.11.6"],\
             ["@rollup/pluginutils", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.0.2"],\
+            ["@twuni/emojify", "npm:1.0.2"],\
             ["@vitejs/plugin-vue", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:3.1.2"],\
             ["@vue/test-utils", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.1.0"],\
             ["bootstrap", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.2.2"],\
@@ -2226,6 +2227,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
           "linkType": "HARD"\
         }]\
       ]],\
+      ["@twuni/emojify", [\
+        ["npm:1.0.2", {\
+          "packageLocation": "./.yarn/cache/@twuni-emojify-npm-1.0.2-a45d6eb0a7-0044c83b05.zip/node_modules/@twuni/emojify/",\
+          "packageDependencies": [\
+            ["@twuni/emojify", "npm:1.0.2"]\
+          ],\
+          "linkType": "HARD"\
+        }]\
+      ]],\
       ["@types/estree", [\
         ["npm:1.0.0", {\
           "packageLocation": "./.yarn/cache/@types-estree-npm-1.0.0-eddde5b631-910d97fb70.zip/node_modules/@types/estree/",\
@@ -8580,6 +8590,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
             ["@percy/cypress", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:3.1.2"],\
             ["@popperjs/core", "npm:2.11.6"],\
             ["@rollup/pluginutils", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.0.2"],\
+            ["@twuni/emojify", "npm:1.0.2"],\
             ["@vitejs/plugin-vue", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:3.1.2"],\
             ["@vue/test-utils", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:2.1.0"],\
             ["bootstrap", "virtual:dc3fc578bfa5e06182a4d2be39ede0bc5b74940b1ffe0d70c26892ab140a4699787750fba175dc306292e80b4aa2c8c5f68c2a821e69b2c37e360c0dff36ff66#npm:5.2.2"],\
diff --git a/.yarn/cache/@twuni-emojify-npm-1.0.2-a45d6eb0a7-0044c83b05.zip b/.yarn/cache/@twuni-emojify-npm-1.0.2-a45d6eb0a7-0044c83b05.zip
new file mode 100644
index 000000000..4bcf04a90
Binary files /dev/null and b/.yarn/cache/@twuni-emojify-npm-1.0.2-a45d6eb0a7-0044c83b05.zip differ
diff --git a/client/components/ChatLog.vue b/client/components/ChatLog.vue
index 779734f91..d393b1866 100644
--- a/client/components/ChatLog.vue
+++ b/client/components/ChatLog.vue
@@ -22,6 +22,8 @@
 <script setup>
 import { onMounted, reactive } from 'vue'
 import { DateTime } from 'luxon'
+import { emojify } from '@twuni/emojify'
+import uniq from 'lodash-es/uniq'
 import {
   NTimeline,
   NTimelineItem
@@ -61,9 +63,12 @@ const colors = [
 
 onMounted(() => {
   const authorColors = {}
+
   // Get chat log data from embedded json tag
   const chatLog = JSON.parse(document.getElementById(`${props.componentId}-data`).textContent || '[]')
   if (chatLog.length > 0) {
+    const authorNames = uniq(chatLog.map(l => l.author))
+
     let idx = 1
     let colorIdx = 0
     for (const logItem of chatLog) {
@@ -75,12 +80,22 @@ onMounted(() => {
           colorIdx = 0
         }
       }
+
+      // -> Format text
+      let txt = emojify(logItem.text)
+      if (txt.indexOf('@') >= 0) {
+        for (const authorName of authorNames) {
+          txt = txt.replaceAll(`@${authorName}`, `<span class="user-mention">${authorName}</span>`)
+        }
+      }
+      txt = txt.replaceAll('href="/user_uploads/', 'href="https://zulip.ietf.org/user_uploads/')
+
       // -> Generate log item
       state.items.push({
         id: `logitem-${idx}`,
         color: authorColors[logItem.author],
         author: logItem.author,
-        text: logItem.text,
+        text: txt,
         time: DateTime.fromISO(logItem.time).toFormat('dd LLLL yyyy \'at\' HH:mm:ss a ZZZZ')
       })
       idx++
@@ -90,9 +105,58 @@ onMounted(() => {
 </script>
 
 <style lang="scss">
+@import '../shared/colors.scss';
+
 .chatlog {
-  .n-timeline-item-content__content > div > p {
-    margin-bottom: 0;
+  .n-timeline-item-content__content {
+    > div > p:last-child {
+      margin-bottom: 0;
+    }
+
+    blockquote {
+      background-color: $gray-100;
+      border-radius: 5px;
+      padding: 8px;
+      margin-top: -8px;
+
+      > p:last-child {
+        margin-bottom: 0;
+      }
+    }
+
+    .message_inline_image {
+      display: none;
+    }
+
+    // Manual user mention
+    .user-mention {
+      display: inline-block;
+      padding: 1px 5px;
+      background-color: rgba($purple, .05);
+      color: $purple;
+      font-weight: 500;
+      border-radius: 4px;
+
+      > .user-mention {
+        padding: 0;
+
+        &::before {
+          display: none;
+        }
+      }
+
+      &::before {
+        content: '@';
+      }
+    }
+
+    // User reply mention
+    .user-mention + a {
+      text-decoration: none;
+      color: $purple;
+      font-style: italic;
+      cursor: default;
+    }
   }
 }
 </style>
diff --git a/client/shared/colors.scss b/client/shared/colors.scss
new file mode 100644
index 000000000..e34c459e9
--- /dev/null
+++ b/client/shared/colors.scss
@@ -0,0 +1,139 @@
+// Bootstrap 5 Color Variables
+// Extracted from https://github.com/twbs/bootstrap/blob/main/scss/_variables.scss
+// Copyright (c) 2011-2022 Twitter, Inc.
+// Copyright (c) 2011-2022 The Bootstrap Authors
+
+// Tint a color: mix a color with white
+@function tint-color($color, $weight) {
+  @return mix(white, $color, $weight);
+}
+
+// Shade a color: mix a color with black
+@function shade-color($color, $weight) {
+  @return mix(black, $color, $weight);
+}
+
+// Color system
+
+$white:    #fff !default;
+$gray-100: #f8f9fa !default;
+$gray-200: #e9ecef !default;
+$gray-300: #dee2e6 !default;
+$gray-400: #ced4da !default;
+$gray-500: #adb5bd !default;
+$gray-600: #6c757d !default;
+$gray-700: #495057 !default;
+$gray-800: #343a40 !default;
+$gray-900: #212529 !default;
+$black:    #000 !default;
+
+$blue:    #0d6efd !default;
+$indigo:  #6610f2 !default;
+$purple:  #6f42c1 !default;
+$pink:    #d63384 !default;
+$red:     #dc3545 !default;
+$orange:  #fd7e14 !default;
+$yellow:  #ffc107 !default;
+$green:   #198754 !default;
+$teal:    #20c997 !default;
+$cyan:    #0dcaf0 !default;
+
+$blue-100: tint-color($blue, 80%) !default;
+$blue-200: tint-color($blue, 60%) !default;
+$blue-300: tint-color($blue, 40%) !default;
+$blue-400: tint-color($blue, 20%) !default;
+$blue-500: $blue !default;
+$blue-600: shade-color($blue, 20%) !default;
+$blue-700: shade-color($blue, 40%) !default;
+$blue-800: shade-color($blue, 60%) !default;
+$blue-900: shade-color($blue, 80%) !default;
+
+$indigo-100: tint-color($indigo, 80%) !default;
+$indigo-200: tint-color($indigo, 60%) !default;
+$indigo-300: tint-color($indigo, 40%) !default;
+$indigo-400: tint-color($indigo, 20%) !default;
+$indigo-500: $indigo !default;
+$indigo-600: shade-color($indigo, 20%) !default;
+$indigo-700: shade-color($indigo, 40%) !default;
+$indigo-800: shade-color($indigo, 60%) !default;
+$indigo-900: shade-color($indigo, 80%) !default;
+
+$purple-100: tint-color($purple, 80%) !default;
+$purple-200: tint-color($purple, 60%) !default;
+$purple-300: tint-color($purple, 40%) !default;
+$purple-400: tint-color($purple, 20%) !default;
+$purple-500: $purple !default;
+$purple-600: shade-color($purple, 20%) !default;
+$purple-700: shade-color($purple, 40%) !default;
+$purple-800: shade-color($purple, 60%) !default;
+$purple-900: shade-color($purple, 80%) !default;
+
+$pink-100: tint-color($pink, 80%) !default;
+$pink-200: tint-color($pink, 60%) !default;
+$pink-300: tint-color($pink, 40%) !default;
+$pink-400: tint-color($pink, 20%) !default;
+$pink-500: $pink !default;
+$pink-600: shade-color($pink, 20%) !default;
+$pink-700: shade-color($pink, 40%) !default;
+$pink-800: shade-color($pink, 60%) !default;
+$pink-900: shade-color($pink, 80%) !default;
+
+$red-100: tint-color($red, 80%) !default;
+$red-200: tint-color($red, 60%) !default;
+$red-300: tint-color($red, 40%) !default;
+$red-400: tint-color($red, 20%) !default;
+$red-500: $red !default;
+$red-600: shade-color($red, 20%) !default;
+$red-700: shade-color($red, 40%) !default;
+$red-800: shade-color($red, 60%) !default;
+$red-900: shade-color($red, 80%) !default;
+
+$orange-100: tint-color($orange, 80%) !default;
+$orange-200: tint-color($orange, 60%) !default;
+$orange-300: tint-color($orange, 40%) !default;
+$orange-400: tint-color($orange, 20%) !default;
+$orange-500: $orange !default;
+$orange-600: shade-color($orange, 20%) !default;
+$orange-700: shade-color($orange, 40%) !default;
+$orange-800: shade-color($orange, 60%) !default;
+$orange-900: shade-color($orange, 80%) !default;
+
+$yellow-100: tint-color($yellow, 80%) !default;
+$yellow-200: tint-color($yellow, 60%) !default;
+$yellow-300: tint-color($yellow, 40%) !default;
+$yellow-400: tint-color($yellow, 20%) !default;
+$yellow-500: $yellow !default;
+$yellow-600: shade-color($yellow, 20%) !default;
+$yellow-700: shade-color($yellow, 40%) !default;
+$yellow-800: shade-color($yellow, 60%) !default;
+$yellow-900: shade-color($yellow, 80%) !default;
+
+$green-100: tint-color($green, 80%) !default;
+$green-200: tint-color($green, 60%) !default;
+$green-300: tint-color($green, 40%) !default;
+$green-400: tint-color($green, 20%) !default;
+$green-500: $green !default;
+$green-600: shade-color($green, 20%) !default;
+$green-700: shade-color($green, 40%) !default;
+$green-800: shade-color($green, 60%) !default;
+$green-900: shade-color($green, 80%) !default;
+
+$teal-100: tint-color($teal, 80%) !default;
+$teal-200: tint-color($teal, 60%) !default;
+$teal-300: tint-color($teal, 40%) !default;
+$teal-400: tint-color($teal, 20%) !default;
+$teal-500: $teal !default;
+$teal-600: shade-color($teal, 20%) !default;
+$teal-700: shade-color($teal, 40%) !default;
+$teal-800: shade-color($teal, 60%) !default;
+$teal-900: shade-color($teal, 80%) !default;
+
+$cyan-100: tint-color($cyan, 80%) !default;
+$cyan-200: tint-color($cyan, 60%) !default;
+$cyan-300: tint-color($cyan, 40%) !default;
+$cyan-400: tint-color($cyan, 20%) !default;
+$cyan-500: $cyan !default;
+$cyan-600: shade-color($cyan, 20%) !default;
+$cyan-700: shade-color($cyan, 40%) !default;
+$cyan-800: shade-color($cyan, 60%) !default;
+$cyan-900: shade-color($cyan, 80%) !default;
diff --git a/docker/scripts/app-rsync-extras.sh b/docker/scripts/app-rsync-extras.sh
index ef6966224..b99082b53 100755
--- a/docker/scripts/app-rsync-extras.sh
+++ b/docker/scripts/app-rsync-extras.sh
@@ -43,7 +43,6 @@ cat << EOF > "$EXCLUDE"
 *.doc
 *.exe
 *.html
-*.json
 *.mib
 *.new
 *.p7s
diff --git a/package.json b/package.json
index 98fe90225..d96c37b0a 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
     "@fullcalendar/timegrid": "5.11.3",
     "@fullcalendar/vue3": "5.11.2",
     "@popperjs/core": "2.11.6",
+    "@twuni/emojify": "1.0.2",
     "bootstrap": "5.2.2",
     "bootstrap-icons": "1.9.1",
     "browser-fs-access": "0.31.1",
diff --git a/yarn.lock b/yarn.lock
index 66ed75d83..dda4f64ca 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1719,6 +1719,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@twuni/emojify@npm:1.0.2":
+  version: 1.0.2
+  resolution: "@twuni/emojify@npm:1.0.2"
+  checksum: 0044c83b0589767dae1c1bb933cd56f2e5031a438f0fc993413e4cc229080e29c275cdd836be33ee02ddd59a5d1d6223a718685650f11ecfffc69c881c072152
+  languageName: node
+  linkType: hard
+
 "@types/estree@npm:^1.0.0":
   version: 1.0.0
   resolution: "@types/estree@npm:1.0.0"
@@ -7138,6 +7145,7 @@ browserlist@latest:
     "@percy/cypress": 3.1.2
     "@popperjs/core": 2.11.6
     "@rollup/pluginutils": 5.0.2
+    "@twuni/emojify": 1.0.2
     "@vitejs/plugin-vue": 3.1.2
     "@vue/test-utils": 2.1.0
     bootstrap: 5.2.2