diff --git a/apimail/api/views.py b/apimail/api/views.py
index 0b35d303e24103d58dd7e3eb120e0e373f3fe9bc..2c628a066d1abb4d9ae7ef3161efc25dc2f2866a 100644
--- a/apimail/api/views.py
+++ b/apimail/api/views.py
@@ -152,6 +152,44 @@ class StoredMessageFilterBackend(filters.BaseFilterBackend):
         queryset = StoredMessage.objects.all()
         queryfilter = Q()
 
+        view_option = request.query_params.get('view', None)
+        if view_option == 'by_thread':
+            queryset = queryset.filter(data__References__isnull=True)
+
+        thread_of_uuid = request.query_params.get('thread_of_uuid', None)
+        if thread_of_uuid:
+            # Identify email thread using data['References'] or data['Message-Id'].
+            # Since Django ORM does not support hyphenated lookups, use raw SQL.
+            # First find the message at the root of the thread. which is the message
+            # with Message-Id first in the list of the message with uuid `thread_of_uuid`
+            reference_message = get_object_or_404(StoredMessage, uuid=thread_of_uuid)
+
+            # First try the RFC 2822 References MIME header:
+            head_id = None
+            try:
+                head_id = reference_message.data['References'].split()[0]
+            except KeyError:
+                # Then try the RFC 2822 In-Reply-To
+                try:
+                    head_id = reference_message.data['In-Reply-To'].split()[0]
+                except KeyError:
+                    # This message is head of the thread as far as can be guessed
+                    head_id = reference_message.data['Message-Id']
+
+            if head_id:
+                thread_query_raw = (
+                    "SELECT apimail_storedmessage.id FROM apimail_storedmessage "
+                    "WHERE UPPER((apimail_storedmessage.data ->> %s)::text) LIKE UPPER(%s) "
+                    "OR UPPER((apimail_storedmessage.data ->> %s)::text) LIKE UPPER(%s) "
+                    "ORDER BY apimail_storedmessage.datetimestamp DESC;")
+                sm_ids = [sm.id for sm in StoredMessage.objects.raw(
+                    thread_query_raw,
+                    ['Message-Id', '%%%s%%' % head_id, 'References', '%%%s%%' % head_id])]
+                queryset = queryset.filter(pk__in=sm_ids)
+            else:
+                queryset = queryset.filter(uuid=thread_of_uuid)
+
+
         flow = request.query_params.get('flow', None)
         if flow == 'in':
             # Restrict to incoming emails
diff --git a/apimail/static/apimail/assets/vue/components/MessagesTable.vue b/apimail/static/apimail/assets/vue/components/MessagesTable.vue
index 1ba4a520bccd03f814667f18981a808bfa433608..3c5e38bd29a4e3517718fd9485e45c0177cfe568 100644
--- a/apimail/static/apimail/assets/vue/components/MessagesTable.vue
+++ b/apimail/static/apimail/assets/vue/components/MessagesTable.vue
@@ -132,7 +132,25 @@
       <h2 class="text-center mb-2">Messages&nbsp;for&emsp;<strong>{{ accountSelected.email }}</strong></h2>
       <hr class="my-2">
       <b-row class="mb-0">
-	<b-col class="col-lg-6">
+	<b-col class="col-lg-4">
+	  <b-form-group
+	    label="View by "
+	    label-cols-sm="6"
+	    label-align-sm="right"
+	    label-size="sm"
+	    >
+	    <b-form-radio-group
+	      v-model="viewFormat"
+	      buttons
+	      button-variant="outline-primary"
+	      size="sm"
+	      :options="viewFormatOptions"
+	      class="float-center"
+	      >
+	    </b-form-radio-group>
+	  </b-form-group>
+	</b-col>
+	<b-col md="auto">
 	  <small class="p-2">Last loaded: {{ lastLoaded }}</small>
 	  <b-badge
 	    class="p-2"
@@ -143,9 +161,9 @@
 	    Refresh now
 	  </b-badge>
 	</b-col>
-	<b-col class="col-lg-6">
+	<b-col>
 	  <b-form-group
-	    label="Auto refresh every: "
+	    label="Refresh interval: "
 	    label-cols-sm="6"
 	    label-align-sm="right"
 	    label-size="sm"
@@ -158,7 +176,7 @@
 	      :options="refreshMinutesOptions"
 	      class="float-center"
 	      >
-	      &nbsp;minutes
+	      &nbsp;mins
 	    </b-form-radio-group>
 	  </b-form-group>
 	</b-col>
@@ -290,6 +308,12 @@
 	</b-col>
       </b-row>
     </b-card>
+    <div v-if="threadOf" class="m-2">
+      <b-button size="sm" variant="info"><strong>Focusing on thread {{ threadOf }}</strong></b-button>
+      <b-button size="sm" variant="warning" @click="threadOf = null">
+	Unfocus this thread
+      </b-button>
+    </div>
     <b-table
       id="my-table"
       class="mb-0"
@@ -379,7 +403,18 @@
 	      <path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>
 	    </svg>
 	  </b-button>
+	  <br>
 	</template>
+	<div v-if="threadOf != message.uuid">
+	  <b-button class="m-2" variant="primary" @click="threadOf = message.uuid">
+	    Focus on this thread
+	  </b-button>
+	</div>
+	<div v-else>
+	  <b-button class="m-2" variant="warning" @click="threadOf = null">
+	    Unfocus this thread
+	  </b-button>
+	</div>
 	<message-content
 	  :message="message"
 	  :tags="tags"
@@ -438,6 +473,12 @@ export default {
 	    ],
 	    filter: null,
 	    filterOn: [],
+	    viewFormat: 'by_message',
+	    viewFormatOptions: [
+		{ text: 'message', value: 'by_message' },
+		{ text: 'thread', value: 'by_thread' },
+	    ],
+	    threadOf: null,
 	    timePeriod: 'any',
 	    timePeriodOptions: [
 		{ text: 'week', value: 'week' },
@@ -524,6 +565,13 @@ export default {
 	    var params = '?account=' + this.accountSelected.email
 	    // Our API uses limit/offset pagination
 	    params += '&limit=' + ctx.perPage + '&offset=' + ctx.perPage * (ctx.currentPage - 1)
+	    // By message or thread view:
+	    if (this.viewFormat == 'by_thread') {
+		params += '&view=by_thread'
+	    }
+	    if (this.threadOf) {
+		params += '&thread_of_uuid=' + this.threadOf
+	    }
 	    // Add flow direction
 	    if (this.flowDirection) {
 		params += '&flow=' + this.flowDirection
@@ -605,6 +653,12 @@ export default {
 	accountSelected: function () {
 	    this.$root.$emit('bv::refresh::table', 'my-table')
 	},
+	viewFormat: function () {
+	    this.$root.$emit('bv::refresh::table', 'my-table')
+	},
+	threadOf: function () {
+	    this.$root.$emit('bv::refresh::table', 'my-table')
+	},
 	timePeriod: function () {
 	    this.$root.$emit('bv::refresh::table', 'my-table')
 	},