diff --git a/Ring/Ring/Extensions/UIColor+Ring.swift b/Ring/Ring/Extensions/UIColor+Ring.swift
index ae88929776ac3e689bb11ffc5933efad3e10fe61..db4ec9c3e3bc075587dbb35637c593e6adfdd315 100644
--- a/Ring/Ring/Extensions/UIColor+Ring.swift
+++ b/Ring/Ring/Extensions/UIColor+Ring.swift
@@ -53,4 +53,9 @@ extension UIColor {
                                          blue: 48.0/255.0,
                                          alpha: 1.0)
 
+    static let ringMsgCellTimeText = UIColor(colorLiteralRed: 128.0/255.0,
+                                                 green: 128.0/255.0,
+                                                 blue: 128.0/255.0,
+                                                 alpha: 1.0)
+
 }
diff --git a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift
index fa63c030205aee2ec2ea106338db4e94c9bf6595..8eea0f533594c28b4ad8ae5454e95e12bc5d0021 100644
--- a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift
+++ b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift
@@ -30,4 +30,7 @@ class MessageCell: UITableViewCell, NibReusable {
     @IBOutlet weak var messageLabel: UILabel!
     @IBOutlet weak var bottomCorner: UIView!
     @IBOutlet weak var topCorner: UIView!
+    @IBOutlet weak var timeLabel: UILabel!
+    @IBOutlet weak var leftDivider: UIView!
+    @IBOutlet weak var rightDivider: UIView!
 }
diff --git a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellReceived.xib b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellReceived.xib
index b381a8be6edd0eb6f85ef866c665e5b23b13e42e..63530ba25131ad89c38cb6aa02939d0a9258a9f2 100644
--- a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellReceived.xib
+++ b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellReceived.xib
@@ -6,6 +6,7 @@
     <dependencies>
         <deployment identifier="iOS"/>
         <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
+        <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     <objects>
@@ -59,6 +60,27 @@
                             </userDefinedRuntimeAttribute>
                         </userDefinedRuntimeAttributes>
                     </view>
+                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="zuX-zz-1Qq" userLabel="Left Divider">
+                        <rect key="frame" x="24" y="16" width="138" height="1"/>
+                        <color key="backgroundColor" red="0.94117647058823528" green="0.94117647058823528" blue="0.94117647058823528" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                        <constraints>
+                            <constraint firstAttribute="height" constant="1" id="OBa-HX-Vts"/>
+                        </constraints>
+                    </view>
+                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="eza-Ni-w3g" userLabel="Right Divider">
+                        <rect key="frame" x="348.5" y="16" width="137.5" height="1"/>
+                        <color key="backgroundColor" red="0.94117647058823528" green="0.94117647058823528" blue="0.94117647058823528" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                        <constraints>
+                            <constraint firstAttribute="height" constant="1" id="7z5-Mi-Abp"/>
+                        </constraints>
+                    </view>
+                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="11/14/2016 12:34PM" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mhg-uK-iD9" userLabel="Message Time">
+                        <rect key="frame" x="178" y="6" width="154.5" height="21"/>
+                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
+                        <fontDescription key="fontDescription" type="system" pointSize="17"/>
+                        <nil key="textColor"/>
+                        <nil key="highlightedColor"/>
+                    </label>
                 </subviews>
                 <constraints>
                     <constraint firstAttribute="bottom" secondItem="kZJ-Ay-LTR" secondAttribute="bottom" constant="8" id="1QQ-bu-6Bl"/>
@@ -67,10 +89,18 @@
                     <constraint firstAttribute="trailing" secondItem="kZJ-Ay-LTR" secondAttribute="trailing" priority="1" constant="16" id="99Y-bR-Ioq"/>
                     <constraint firstItem="kZJ-Ay-LTR" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="H2p-sc-9uM" secondAttribute="leading" priority="1" constant="64" id="Eso-cy-OYs"/>
                     <constraint firstItem="XcL-CH-BiH" firstAttribute="leading" secondItem="kZJ-Ay-LTR" secondAttribute="leading" id="GaI-yj-QFt"/>
+                    <constraint firstItem="mhg-uK-iD9" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" constant="-2" id="O3h-2G-pe5"/>
+                    <constraint firstItem="eza-Ni-w3g" firstAttribute="leading" secondItem="mhg-uK-iD9" secondAttribute="trailing" constant="16" id="Pdf-ru-PnO"/>
+                    <constraint firstItem="zuX-zz-1Qq" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" constant="16" id="QoG-Zs-Lv7"/>
                     <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="kZJ-Ay-LTR" secondAttribute="trailing" constant="64" id="TCY-7X-mFs"/>
+                    <constraint firstItem="zuX-zz-1Qq" firstAttribute="trailing" secondItem="mhg-uK-iD9" secondAttribute="leading" constant="-16" id="aUU-d6-Dse"/>
+                    <constraint firstItem="mhg-uK-iD9" firstAttribute="centerX" secondItem="H2p-sc-9uM" secondAttribute="centerX" id="gD0-yo-bga"/>
                     <constraint firstItem="kZJ-Ay-LTR" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="8" id="jhd-A8-c1o"/>
                     <constraint firstItem="kZJ-Ay-LTR" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="16" id="nWe-5k-Qpn"/>
+                    <constraint firstItem="eza-Ni-w3g" firstAttribute="centerY" secondItem="mhg-uK-iD9" secondAttribute="centerY" id="vhB-Uv-04a"/>
+                    <constraint firstItem="zuX-zz-1Qq" firstAttribute="centerY" secondItem="mhg-uK-iD9" secondAttribute="centerY" id="xFW-jt-00h"/>
                     <constraint firstItem="WBd-CS-7Qv" firstAttribute="leading" secondItem="kZJ-Ay-LTR" secondAttribute="leading" id="yBG-sT-w2a"/>
+                    <constraint firstAttribute="trailingMargin" secondItem="eza-Ni-w3g" secondAttribute="trailing" constant="16" id="yMp-aN-6PX"/>
                 </constraints>
             </tableViewCellContentView>
             <connections>
@@ -78,7 +108,10 @@
                 <outlet property="bubble" destination="kZJ-Ay-LTR" id="hdG-fG-L69"/>
                 <outlet property="bubbleBottomConstraint" destination="1QQ-bu-6Bl" id="a4F-pf-cXL"/>
                 <outlet property="bubbleTopConstraint" destination="jhd-A8-c1o" id="40k-2d-6rW"/>
+                <outlet property="leftDivider" destination="zuX-zz-1Qq" id="9Jc-cV-VTA"/>
                 <outlet property="messageLabel" destination="lyR-7c-S2k" id="hd3-pz-Pwh"/>
+                <outlet property="rightDivider" destination="eza-Ni-w3g" id="Jfy-s9-5t7"/>
+                <outlet property="timeLabel" destination="mhg-uK-iD9" id="x8D-vK-F6G"/>
                 <outlet property="topCorner" destination="WBd-CS-7Qv" id="GCm-Hv-5Ei"/>
             </connections>
             <point key="canvasLocation" x="-411" y="-132.5"/>
diff --git a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellSent.xib b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellSent.xib
index 41e65e646798012f611666493a47500e7318eed3..4b7eda38ec91e96d643fa9a6c1c618a4e5048a95 100644
--- a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellSent.xib
+++ b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellSent.xib
@@ -6,6 +6,7 @@
     <dependencies>
         <deployment identifier="iOS"/>
         <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
+        <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     <objects>
@@ -59,16 +60,44 @@
                             </userDefinedRuntimeAttribute>
                         </userDefinedRuntimeAttributes>
                     </view>
+                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="h8N-aw-5lV" userLabel="Right Divider">
+                        <rect key="frame" x="347.5" y="16" width="138.5" height="1"/>
+                        <color key="backgroundColor" red="0.94117647058823528" green="0.94117647058823528" blue="0.94117647058823528" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                        <constraints>
+                            <constraint firstAttribute="height" constant="1" id="3h1-r8-Nis"/>
+                        </constraints>
+                    </view>
+                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="2U4-l3-KET" userLabel="Left Divider">
+                        <rect key="frame" x="24" y="16" width="137" height="1"/>
+                        <color key="backgroundColor" red="0.94117647058823528" green="0.94117647058823528" blue="0.94117647058823528" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                        <constraints>
+                            <constraint firstAttribute="height" constant="1" id="gla-pJ-IsN"/>
+                        </constraints>
+                    </view>
+                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="11/14/2016 12:34PM" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ogn-wv-fZy" userLabel="Message Time">
+                        <rect key="frame" x="177" y="6" width="154.5" height="21"/>
+                        <fontDescription key="fontDescription" type="system" pointSize="17"/>
+                        <nil key="textColor"/>
+                        <nil key="highlightedColor"/>
+                    </label>
                 </subviews>
                 <constraints>
                     <constraint firstAttribute="bottom" secondItem="kZJ-Ay-LTR" secondAttribute="bottom" constant="8" id="1QQ-bu-6Bl" userLabel="Bubble Bottom Constraint"/>
+                    <constraint firstItem="h8N-aw-5lV" firstAttribute="leading" secondItem="ogn-wv-fZy" secondAttribute="trailing" constant="16" id="1jW-JR-t5r"/>
                     <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="kZJ-Ay-LTR" secondAttribute="trailing" priority="1" constant="64" id="99Y-bR-Ioq"/>
                     <constraint firstItem="kZJ-Ay-LTR" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" priority="1" constant="16" id="Eso-cy-OYs"/>
+                    <constraint firstItem="ogn-wv-fZy" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" constant="-2" id="Fxg-Wa-Rb9"/>
+                    <constraint firstItem="2U4-l3-KET" firstAttribute="centerY" secondItem="ogn-wv-fZy" secondAttribute="centerY" id="J6Y-Ti-HDv"/>
                     <constraint firstItem="EMh-bG-ilg" firstAttribute="trailing" secondItem="kZJ-Ay-LTR" secondAttribute="trailing" id="MY3-Aj-94K"/>
+                    <constraint firstItem="ogn-wv-fZy" firstAttribute="centerX" secondItem="H2p-sc-9uM" secondAttribute="centerX" id="RaG-SO-xFo"/>
                     <constraint firstAttribute="trailing" secondItem="kZJ-Ay-LTR" secondAttribute="trailing" constant="16" id="TCY-7X-mFs"/>
+                    <constraint firstItem="h8N-aw-5lV" firstAttribute="centerY" secondItem="ogn-wv-fZy" secondAttribute="centerY" id="Xdu-7c-MbP"/>
+                    <constraint firstItem="2U4-l3-KET" firstAttribute="trailing" secondItem="ogn-wv-fZy" secondAttribute="leading" constant="-16" id="Xw6-H2-byY"/>
+                    <constraint firstAttribute="trailingMargin" secondItem="h8N-aw-5lV" secondAttribute="trailing" constant="16" id="iwC-fc-F9c"/>
                     <constraint firstItem="kZJ-Ay-LTR" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="8" id="jhd-A8-c1o"/>
                     <constraint firstItem="hdz-AQ-xHI" firstAttribute="trailing" secondItem="kZJ-Ay-LTR" secondAttribute="trailing" id="lSl-vu-Wkl"/>
                     <constraint firstItem="kZJ-Ay-LTR" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="64" id="nWe-5k-Qpn"/>
+                    <constraint firstItem="2U4-l3-KET" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" constant="16" id="uoy-US-ksI"/>
                     <constraint firstItem="EMh-bG-ilg" firstAttribute="top" secondItem="kZJ-Ay-LTR" secondAttribute="top" id="zEh-jv-0Ha"/>
                     <constraint firstItem="hdz-AQ-xHI" firstAttribute="bottom" secondItem="kZJ-Ay-LTR" secondAttribute="bottom" id="zWA-Jg-F6Q"/>
                 </constraints>
@@ -78,7 +107,10 @@
                 <outlet property="bubble" destination="kZJ-Ay-LTR" id="hdG-fG-L69"/>
                 <outlet property="bubbleBottomConstraint" destination="1QQ-bu-6Bl" id="woo-UQ-wXK"/>
                 <outlet property="bubbleTopConstraint" destination="jhd-A8-c1o" id="cll-eA-OC5"/>
+                <outlet property="leftDivider" destination="2U4-l3-KET" id="y4j-CT-gez"/>
                 <outlet property="messageLabel" destination="lyR-7c-S2k" id="hd3-pz-Pwh"/>
+                <outlet property="rightDivider" destination="h8N-aw-5lV" id="9pc-93-BG6"/>
+                <outlet property="timeLabel" destination="ogn-wv-fZy" id="7yt-vi-cSp"/>
                 <outlet property="topCorner" destination="EMh-bG-ilg" id="nHl-hn-BZ1"/>
             </connections>
             <point key="canvasLocation" x="-411" y="-132.5"/>
diff --git a/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift b/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift
index c938de3edb29778ccc04479a5e44dde4f8894cf3..f84ab0b664ad2910d7a94ebf52e9a1305a44209a 100644
--- a/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift
+++ b/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift
@@ -23,14 +23,6 @@ import RxSwift
 import Reusable
 import SwiftyBeaver
 
-enum BubbleChaining {
-    case singleMessage
-    case firstOfSequence
-    case lastOfSequence
-    case middleOfSequence
-    case error
-}
-
 class ConversationViewController: UIViewController, UITextFieldDelegate, StoryboardBased, ViewModelBased {
 
     let log = SwiftyBeaver.self
@@ -136,6 +128,7 @@ class ConversationViewController: UIViewController, UITextFieldDelegate, Storybo
         //Bind the TableView to the ViewModel
         self.viewModel.messages.subscribe(onNext: { [weak self] (messageViewModels) in
             self?.messageViewModels = messageViewModels
+            self?.computeSequencing()
             self?.tableView.reloadData()
         }).disposed(by: self.disposeBag)
 
@@ -203,62 +196,132 @@ class ConversationViewController: UIViewController, UITextFieldDelegate, Storybo
         return textFieldShouldEndEditing
     }
 
-    func isFirstMessage(cellForRowAt indexPath: IndexPath) -> Bool {
-        return indexPath.row == 0
+    func computeSequencing() {
+        var lastShownTime: Date?
+        for (index, messageViewModel) in self.messageViewModels!.enumerated() {
+            // time labels
+            let time = messageViewModel.receivedDate
+            if index == 0 {
+                // always show first message's time
+                messageViewModel.timeStringShown = getTimeLabelString(forTime: time)
+                lastShownTime = time
+            } else {
+                // only show time for new messages if beyond an arbitrary time frame (1 minute)
+                // from the previously shown time
+                let hourComp = Calendar.current.compare(lastShownTime!, to: time, toGranularity: .hour)
+                let minuteComp = Calendar.current.compare(lastShownTime!, to: time, toGranularity: .minute)
+                if hourComp == .orderedSame && minuteComp == .orderedSame {
+                    messageViewModel.timeStringShown = nil
+                } else {
+                    messageViewModel.timeStringShown = getTimeLabelString(forTime: time)
+                    lastShownTime = time
+                }
+            }
+            // sequencing
+            messageViewModel.sequencing = getMessageSequencing(forIndex: index)
+        }
+    }
+
+    func getTimeLabelString(forTime time: Date) -> String {
+        // get the current time
+        let currentDateTime = Date()
+
+        // prepare formatter
+        let dateFormatter = DateFormatter()
+        if Calendar.current.compare(currentDateTime, to: time, toGranularity: .year) == .orderedSame {
+            if Calendar.current.compare(currentDateTime, to: time, toGranularity: .weekOfYear) == .orderedSame {
+                if Calendar.current.compare(currentDateTime, to: time, toGranularity: .day) == .orderedSame {
+                    // age: [0, received the previous day[
+                    dateFormatter.dateFormat = "h:mma"
+                } else {
+                    // age: [received the previous day, received 7 days ago[
+                    dateFormatter.dateFormat = "E h:mma"
+                }
+            } else {
+                // age: [received 7 days ago, received the previous year[
+                let day = Calendar.current.component(.day, from: time)
+                // apply appropriate suffix to day value
+                let suffix = day == 1 ? "st" : (day == 2 ? "nd" : (day == 3 ? "rd" : "th"))
+                dateFormatter.dateFormat = "MMM d'\(suffix),' h:mma"
+            }
+        } else {
+            // age: [received the previous year, inf[
+            dateFormatter.dateFormat = "MMM d'th yyyy,' h:mma"
+        }
+
+        // generate the string containing the message time
+        return dateFormatter.string(from: time).uppercased()
     }
 
-    func isLastMessage(cellForRowAt indexPath: IndexPath) -> Bool {
-        return self.messageViewModels?.count == indexPath.row + 1
+    func formatTimeLabel(forCell cell: MessageCell,
+                         withMessageVM messageVM: MessageViewModel) {
+        // hide for potentially reused cell
+        cell.timeLabel.isHidden = true
+        cell.leftDivider.isHidden = true
+        cell.rightDivider.isHidden = true
+
+        if messageVM.timeStringShown == nil {
+            return
+        }
+
+        // setup the label
+        cell.timeLabel.text = messageVM.timeStringShown
+        cell.timeLabel.textColor = UIColor.ringMsgCellTimeText
+        cell.timeLabel.font = UIFont.boldSystemFont(ofSize: 14.0)
+
+        // show the time
+        cell.timeLabel.isHidden = false
+        cell.leftDivider.isHidden = false
+        cell.rightDivider.isHidden = false
     }
 
-    func getBubbleChaining(cellForRowAt indexPath: IndexPath) -> BubbleChaining {
-        if let msgViewModel = self.messageViewModels?[indexPath.row] {
+    func getMessageSequencing(forIndex index: Int) -> MessageSequencing {
+        if let msgViewModel = self.messageViewModels?[index] {
             let msgOwner = msgViewModel.bubblePosition()
-            if self.messageViewModels?.count == 1 || indexPath.row == 0 {
-                if self.messageViewModels?.count == indexPath.row + 1 {
-                    return BubbleChaining.singleMessage
+            if self.messageViewModels?.count == 1 || index == 0 {
+                if self.messageViewModels?.count == index + 1 {
+                    return MessageSequencing.singleMessage
                 }
-                let nextMsgViewModel = indexPath.row + 1 <= (self.messageViewModels?.count)!
-                    ? self.messageViewModels?[indexPath.row + 1] : nil
+                let nextMsgViewModel = index + 1 <= (self.messageViewModels?.count)!
+                    ? self.messageViewModels?[index + 1] : nil
                 if nextMsgViewModel != nil {
                     return msgOwner != nextMsgViewModel?.bubblePosition()
-                        ? BubbleChaining.singleMessage : BubbleChaining.firstOfSequence
+                        ? MessageSequencing.singleMessage : MessageSequencing.firstOfSequence
                 }
-            } else if self.messageViewModels?.count == indexPath.row + 1 {
-                let lastMsgViewModel = indexPath.row - 1 >= 0 && indexPath.row - 1 < (self.messageViewModels?.count)!
-                    ? self.messageViewModels?[indexPath.row - 1] : nil
+            } else if self.messageViewModels?.count == index + 1 {
+                let lastMsgViewModel = index - 1 >= 0 && index - 1 < (self.messageViewModels?.count)!
+                    ? self.messageViewModels?[index - 1] : nil
                 if lastMsgViewModel != nil {
                     return msgOwner != lastMsgViewModel?.bubblePosition()
-                        ? BubbleChaining.singleMessage : BubbleChaining.lastOfSequence
+                        ? MessageSequencing.singleMessage : MessageSequencing.lastOfSequence
                 }
             }
-            let lastMsgViewModel = indexPath.row - 1 >= 0 && indexPath.row - 1 < (self.messageViewModels?.count)!
-                ? self.messageViewModels?[indexPath.row - 1] : nil
-            let nextMsgViewModel = indexPath.row + 1 <= (self.messageViewModels?.count)!
-                ? self.messageViewModels?[indexPath.row + 1] : nil
-            var chaining = BubbleChaining.singleMessage
+            let lastMsgViewModel = index - 1 >= 0 && index - 1 < (self.messageViewModels?.count)!
+                ? self.messageViewModels?[index - 1] : nil
+            let nextMsgViewModel = index + 1 <= (self.messageViewModels?.count)!
+                ? self.messageViewModels?[index + 1] : nil
+            var sequencing = MessageSequencing.singleMessage
             if (lastMsgViewModel != nil) && (nextMsgViewModel != nil) {
                 if msgOwner != lastMsgViewModel?.bubblePosition() && msgOwner == nextMsgViewModel?.bubblePosition() {
-                    chaining = BubbleChaining.firstOfSequence
+                    sequencing = MessageSequencing.firstOfSequence
                 } else if msgOwner != nextMsgViewModel?.bubblePosition() && msgOwner == lastMsgViewModel?.bubblePosition() {
-                    chaining = BubbleChaining.lastOfSequence
+                    sequencing = MessageSequencing.lastOfSequence
                 } else if msgOwner == nextMsgViewModel?.bubblePosition() && msgOwner == lastMsgViewModel?.bubblePosition() {
-                    chaining = BubbleChaining.middleOfSequence
+                    sequencing = MessageSequencing.middleOfSequence
                 }
             }
-            return chaining
+            return sequencing
         }
-        return BubbleChaining.error
+        return MessageSequencing.unknown
     }
 
     func applyBubbleStyleToCell(toCell cell: MessageCell,
-                                withChaining chaining: BubbleChaining,
-                                withContent content: String,
-                                withType type: BubblePosition) {
-
+                                cellForRowAt indexPath: IndexPath,
+                                withMessageVM messageVM: MessageViewModel) {
+        let type = messageVM.bubblePosition()
         let bubbleColor = type == .received ? UIColor.ringMsgCellReceived : UIColor.ringMsgCellSent
 
-        cell.messageLabel.setTextWithLineSpacing(withText: content, withLineSpacing: 2)
+        cell.messageLabel.setTextWithLineSpacing(withText: messageVM.content, withLineSpacing: 2)
 
         cell.topCorner.isHidden = true
         cell.topCorner.backgroundColor = bubbleColor
@@ -267,20 +330,60 @@ class ConversationViewController: UIViewController, UITextFieldDelegate, Storybo
         cell.bubbleBottomConstraint.constant = 8
         cell.bubbleTopConstraint.constant = 8
 
-        switch chaining {
+        var adjustedSequencing = messageVM.sequencing
+
+        if messageVM.timeStringShown != nil {
+            cell.bubbleTopConstraint.constant = 32
+            adjustedSequencing = indexPath.row == (self.messageViewModels?.count)! - 1 ?
+                .singleMessage : adjustedSequencing != .singleMessage && adjustedSequencing != .lastOfSequence ?
+                    .firstOfSequence : .singleMessage
+        }
+
+        if indexPath.row + 1 < (self.messageViewModels?.count)! {
+            if self.messageViewModels?[indexPath.row + 1].timeStringShown != nil {
+                switch adjustedSequencing {
+                case .firstOfSequence:
+                    adjustedSequencing = .singleMessage
+                case .middleOfSequence:
+                    adjustedSequencing = .lastOfSequence
+                default: break
+                }
+            }
+        }
+
+        switch adjustedSequencing {
         case .middleOfSequence:
             cell.topCorner.isHidden = false
             cell.bottomCorner.isHidden = false
             cell.bubbleBottomConstraint.constant = 1
-            cell.bubbleTopConstraint.constant = 1
+            cell.bubbleTopConstraint.constant = messageVM.timeStringShown != nil ? 32 : 1
         case .firstOfSequence:
             cell.bottomCorner.isHidden = false
             cell.bubbleBottomConstraint.constant = 1
+            cell.bubbleTopConstraint.constant = messageVM.timeStringShown != nil ? 32 : 8
         case .lastOfSequence:
             cell.topCorner.isHidden = false
-            cell.bubbleTopConstraint.constant = 1
+            cell.bubbleTopConstraint.constant = messageVM.timeStringShown != nil ? 32 : 1
         default: break
         }
+
+    }
+
+    func formatCell(withCell cell: MessageCell,
+                    cellForRowAt indexPath: IndexPath,
+                    withMessageVM messageVM: MessageViewModel) {
+        // hide/show time label
+        formatTimeLabel(forCell: cell, withMessageVM: messageVM)
+
+        // bubble grouping for cell
+        applyBubbleStyleToCell(toCell: cell, cellForRowAt: indexPath, withMessageVM: messageVM)
+
+        // special cases where top/bottom margins should be larger
+        if indexPath.row == 0 {
+            cell.bubbleTopConstraint.constant = 32
+        } else if self.messageViewModels?.count == indexPath.row + 1 {
+            cell.bubbleBottomConstraint.constant = 16
+        }
     }
 
 }
@@ -291,38 +394,14 @@ extension ConversationViewController: UITableViewDataSource {
     }
 
     func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-
         if let messageViewModel = self.messageViewModels?[indexPath.row] {
-            let chaining = self.getBubbleChaining(cellForRowAt: indexPath)
             if messageViewModel.bubblePosition() == .received {
-                // left side (incoming)
                 let cell = tableView.dequeueReusableCell(for: indexPath, cellType: MessageCellReceived.self)
-
-                // Format cell
-                applyBubbleStyleToCell(toCell: cell, withChaining: chaining, withContent: messageViewModel.content, withType: .received)
-
-                // Special cases where top/bottom margins should be larger
-                if isFirstMessage(cellForRowAt: indexPath) {
-                    cell.bubbleTopConstraint.constant = 16
-                } else if isLastMessage(cellForRowAt: indexPath) {
-                    cell.bubbleBottomConstraint.constant = 16
-                }
-
+                formatCell(withCell: cell, cellForRowAt: indexPath, withMessageVM: messageViewModel)
                 return cell
             } else {
-                // right side (outgoing)
                 let cell = tableView.dequeueReusableCell(for: indexPath, cellType: MessageCellSent.self)
-
-                // Format cell
-                applyBubbleStyleToCell(toCell: cell, withChaining: chaining, withContent: messageViewModel.content, withType: .sent)
-
-                // Special cases where top/bottom margins should be larger
-                if isFirstMessage(cellForRowAt: indexPath) {
-                    cell.bubbleTopConstraint.constant = 16
-                } else if isLastMessage(cellForRowAt: indexPath) {
-                    cell.bubbleBottomConstraint.constant = 16
-                }
-
+                formatCell(withCell: cell, cellForRowAt: indexPath, withMessageVM: messageViewModel)
                 return cell
             }
         }
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageViewModel.swift b/Ring/Ring/Features/Conversations/Conversation/MessageViewModel.swift
index b766246ef1b25a0e8202fb56764e6135969493a3..63d091df0ed5563e66d5d9e32da820f532978c3e 100644
--- a/Ring/Ring/Features/Conversations/Conversation/MessageViewModel.swift
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageViewModel.swift
@@ -26,6 +26,14 @@ enum BubblePosition {
     case generated
 }
 
+enum MessageSequencing {
+    case singleMessage
+    case firstOfSequence
+    case lastOfSequence
+    case middleOfSequence
+    case unknown
+}
+
 enum GeneratedMessageType: String {
     case sendContactRequest = "The invitation has been sent"
     case receivedContactRequest = "Contact request received"
@@ -37,16 +45,32 @@ class MessageViewModel {
     fileprivate let accountService: AccountsService
     fileprivate var message: MessageModel
 
+    var timeStringShown: String?
+    var sequencing: MessageSequencing = .unknown
+
     init(withInjectionBag injectionBag: InjectionBag,
          withMessage message: MessageModel) {
         self.accountService = injectionBag.accountService
         self.message = message
+        self.timeStringShown = nil
     }
 
     var content: String {
         return self.message.content
     }
 
+    var receivedDate: Date {
+        return self.message.receivedDate
+    }
+
+    var id: Int64 {
+        return self.message.id
+    }
+
+    var status: MessageStatus {
+        return self.message.status
+    }
+
     func bubblePosition() -> BubblePosition {
         if self.message.isGenerated {
             return .generated