diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyDataSource.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyDataSource.kt
index b0762171ee49bf60f17fb5bf893735f39d0e210b..6e1d3fe8cec8162a32db1da771bf5bce3456b996 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyDataSource.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyDataSource.kt
@@ -36,13 +36,22 @@ class StoryGroupReplyDataSource(private val parentStoryId: Long) : PagedDataSour
   }
 
   private fun readRowFromRecord(record: MmsMessageRecord): StoryGroupReplyItemData {
-    return if (MmsSmsColumns.Types.isStoryReaction(record.type)) {
-      readReactionFromRecord(record)
-    } else {
-      readTextFromRecord(record)
+    return when {
+      record.isRemoteDelete -> readRemoteDeleteFromRecord(record)
+      MmsSmsColumns.Types.isStoryReaction(record.type) -> readReactionFromRecord(record)
+      else -> readTextFromRecord(record)
     }
   }
 
+  private fun readRemoteDeleteFromRecord(record: MmsMessageRecord): StoryGroupReplyItemData {
+    return StoryGroupReplyItemData(
+      key = StoryGroupReplyItemData.Key.RemoteDelete(record.id),
+      sender = if (record.isOutgoing) Recipient.self() else record.individualRecipient.resolve(),
+      sentAtMillis = record.dateSent,
+      replyBody = StoryGroupReplyItemData.ReplyBody.RemoteDelete(record)
+    )
+  }
+
   private fun readReactionFromRecord(record: MmsMessageRecord): StoryGroupReplyItemData {
     return StoryGroupReplyItemData(
       key = StoryGroupReplyItemData.Key.Reaction(record.id),
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt
index 5997ccb554b45bc972b258c4326865d1accbbbe5..939fc68f7d47ed4e81a26a4117e22cabfe140be3 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt
@@ -181,9 +181,6 @@ class StoryGroupReplyFragment :
                   requireContext(),
                   it.sender
                 ),
-                onPrivateReplyClick = { model ->
-                  requireListener<Callback>().onStartDirectReply(model.storyGroupReplyItemData.sender.id)
-                },
                 onCopyClick = { model ->
                   val clipData = ClipData.newPlainText(requireContext().getString(R.string.app_name), model.text.message.getDisplayBody(requireContext()))
                   ServiceUtil.getClipboardManager(requireContext()).setPrimaryClip(clipData)
@@ -216,6 +213,25 @@ class StoryGroupReplyFragment :
               )
             )
           }
+          is StoryGroupReplyItemData.ReplyBody.RemoteDelete -> {
+            customPref(
+              StoryGroupReplyItem.RemoteDeleteModel(
+                storyGroupReplyItemData = it,
+                remoteDelete = it.replyBody,
+                nameColor = colorizer.getIncomingGroupSenderColor(
+                  requireContext(),
+                  it.sender
+                ),
+                onDeleteClick = { model ->
+                  lifecycleDisposable += DeleteDialog.show(requireActivity(), setOf(model.remoteDelete.messageRecord)).subscribe { didDeleteThread ->
+                    if (didDeleteThread) {
+                      throw AssertionError("We should never end up deleting a Group Thread like this.")
+                    }
+                  }
+                },
+              )
+            )
+          }
         }
       }
     }
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyItem.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyItem.kt
index 6cfed46956490bfb40de9fa1585ee0d5b4dbd578..04609031043ef6284db1d39c55804d428120ddf1 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyItem.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyItem.kt
@@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.util.DateUtils
 import org.thoughtcrime.securesms.util.ViewUtil
 import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
 import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
+import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
 import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
 import org.thoughtcrime.securesms.util.visible
 import java.util.Locale
@@ -38,13 +39,13 @@ object StoryGroupReplyItem {
   fun register(mappingAdapter: MappingAdapter) {
     mappingAdapter.registerFactory(TextModel::class.java, LayoutFactory(::TextViewHolder, R.layout.stories_group_text_reply_item))
     mappingAdapter.registerFactory(ReactionModel::class.java, LayoutFactory(::ReactionViewHolder, R.layout.stories_group_reaction_reply_item))
+    mappingAdapter.registerFactory(RemoteDeleteModel::class.java, LayoutFactory(::RemoteDeleteViewHolder, R.layout.stories_group_remote_delete_item))
   }
 
   class TextModel(
     val storyGroupReplyItemData: StoryGroupReplyItemData,
     val text: StoryGroupReplyItemData.ReplyBody.Text,
     @ColorInt val nameColor: Int,
-    val onPrivateReplyClick: (TextModel) -> Unit,
     val onCopyClick: (TextModel) -> Unit,
     val onDeleteClick: (TextModel) -> Unit,
     val onMentionClick: (RecipientId) -> Unit
@@ -74,6 +75,35 @@ object StoryGroupReplyItem {
     }
   }
 
+  class RemoteDeleteModel(
+    val storyGroupReplyItemData: StoryGroupReplyItemData,
+    val remoteDelete: StoryGroupReplyItemData.ReplyBody.RemoteDelete,
+    val onDeleteClick: (RemoteDeleteModel) -> Unit,
+    @ColorInt val nameColor: Int
+  ) : MappingModel<RemoteDeleteModel> {
+    override fun areItemsTheSame(newItem: RemoteDeleteModel): Boolean {
+      return storyGroupReplyItemData.sender == newItem.storyGroupReplyItemData.sender &&
+        storyGroupReplyItemData.sentAtMillis == newItem.storyGroupReplyItemData.sentAtMillis
+    }
+
+    override fun areContentsTheSame(newItem: RemoteDeleteModel): Boolean {
+      return storyGroupReplyItemData == newItem.storyGroupReplyItemData &&
+        storyGroupReplyItemData.sender.hasSameContent(newItem.storyGroupReplyItemData.sender) &&
+        nameColor == newItem.nameColor
+    }
+
+    override fun getChangePayload(newItem: RemoteDeleteModel): Any? {
+      return if (nameColor != newItem.nameColor &&
+        storyGroupReplyItemData == newItem.storyGroupReplyItemData &&
+        storyGroupReplyItemData.sender.hasSameContent(newItem.storyGroupReplyItemData.sender)
+      ) {
+        NAME_COLOR_CHANGED
+      } else {
+        null
+      }
+    }
+  }
+
   class ReactionModel(
     val storyGroupReplyItemData: StoryGroupReplyItemData,
     val reaction: StoryGroupReplyItemData.ReplyBody.Reaction,
@@ -104,13 +134,12 @@ object StoryGroupReplyItem {
     }
   }
 
-  private class TextViewHolder(itemView: View) : MappingViewHolder<TextModel>(itemView) {
-
-    private val avatar: AvatarImageView = itemView.findViewById(R.id.avatar)
-    private val name: FromTextView = itemView.findViewById(R.id.name)
-    private val body: EmojiTextView = itemView.findViewById(R.id.body)
-    private val date: TextView = itemView.findViewById(R.id.viewed_at)
-    private val dateBelow: TextView = itemView.findViewById(R.id.viewed_at_below)
+  private abstract class BaseViewHolder<T>(itemView: View) : MappingViewHolder<T>(itemView) {
+    protected val avatar: AvatarImageView = itemView.findViewById(R.id.avatar)
+    protected val name: FromTextView = itemView.findViewById(R.id.name)
+    protected val body: EmojiTextView = itemView.findViewById(R.id.body)
+    protected val date: TextView = itemView.findViewById(R.id.viewed_at)
+    protected val dateBelow: TextView = itemView.findViewById(R.id.viewed_at_below)
 
     init {
       body.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
@@ -123,6 +152,9 @@ object StoryGroupReplyItem {
         }
       }
     }
+  }
+
+  private class TextViewHolder(itemView: View) : BaseViewHolder<TextModel>(itemView) {
 
     override fun bind(model: TextModel) {
       itemView.setOnLongClickListener {
@@ -179,6 +211,39 @@ object StoryGroupReplyItem {
     }
   }
 
+  private class RemoteDeleteViewHolder(itemView: View) : BaseViewHolder<RemoteDeleteModel>(itemView) {
+
+    override fun bind(model: RemoteDeleteModel) {
+      itemView.setOnLongClickListener {
+        displayContextMenu(model)
+        true
+      }
+
+      name.setTextColor(model.nameColor)
+      if (payload.contains(NAME_COLOR_CHANGED)) {
+        return
+      }
+
+      AvatarUtil.loadIconIntoImageView(model.storyGroupReplyItemData.sender, avatar, DimensionUnit.DP.toPixels(28f).toInt())
+      name.text = resolveName(context, model.storyGroupReplyItemData.sender)
+
+      date.text = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), model.storyGroupReplyItemData.sentAtMillis)
+      dateBelow.text = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), model.storyGroupReplyItemData.sentAtMillis)
+    }
+
+    private fun displayContextMenu(model: RemoteDeleteModel) {
+      itemView.isSelected = true
+      SignalContextMenu.Builder(itemView, itemView.rootView as ViewGroup)
+        .preferredHorizontalPosition(SignalContextMenu.HorizontalPosition.START)
+        .onDismiss { itemView.isSelected = false }
+        .show(
+          listOf(
+            ActionItem(R.drawable.ic_trash_24_solid_tinted, context.getString(R.string.StoryGroupReplyItem__delete)) { model.onDeleteClick(model) }
+          )
+        )
+    }
+  }
+
   private class ReactionViewHolder(itemView: View) : MappingViewHolder<ReactionModel>(itemView) {
     private val avatar: AvatarImageView = itemView.findViewById(R.id.avatar)
     private val name: FromTextView = itemView.findViewById(R.id.name)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyItemData.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyItemData.kt
index 5fe19b1326100ac207daca31b7f5279a4251cf98..5acb4b66d02a2b7f8aba8f7dd1175e13976de058 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyItemData.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyItemData.kt
@@ -1,6 +1,7 @@
 package org.thoughtcrime.securesms.stories.viewer.reply.group
 
 import org.thoughtcrime.securesms.conversation.ConversationMessage
+import org.thoughtcrime.securesms.database.model.MessageRecord
 import org.thoughtcrime.securesms.recipients.Recipient
 
 data class StoryGroupReplyItemData(
@@ -12,10 +13,12 @@ data class StoryGroupReplyItemData(
   sealed class ReplyBody {
     data class Text(val message: ConversationMessage) : ReplyBody()
     data class Reaction(val emoji: CharSequence) : ReplyBody()
+    data class RemoteDelete(val messageRecord: MessageRecord) : ReplyBody()
   }
 
   sealed class Key {
     data class Text(val messageId: Long) : Key()
     data class Reaction(val reactionId: Long) : Key()
+    data class RemoteDelete(val messageId: Long) : Key()
   }
 }
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyRepository.kt
index 9cafbbe4d0d6fbc4e9319f53e92e1097ed90774f..85c10249047efd9e8f7cef81c26579269cc953de 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyRepository.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyRepository.kt
@@ -29,6 +29,7 @@ class StoryGroupReplyRepository {
 
       val threadId = SignalDatabase.mms.getThreadIdForMessage(parentStoryId)
 
+      ApplicationDependencies.getDatabaseObserver().registerMessageUpdateObserver(messageObserver)
       ApplicationDependencies.getDatabaseObserver().registerMessageInsertObserver(threadId, messageObserver)
       ApplicationDependencies.getDatabaseObserver().registerConversationObserver(threadId, observer)
 
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplySender.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplySender.kt
index 604f2643a5ee6e85ff47fa50bf642f9e8c371a64..859e86fce87a1ef1f84cc4db8a4f90f064441e03 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplySender.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplySender.kt
@@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.database.model.ParentStoryId
 import org.thoughtcrime.securesms.database.model.StoryType
 import org.thoughtcrime.securesms.mediasend.v2.UntrustedRecords
 import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
+import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage
 import org.thoughtcrime.securesms.sms.MessageSender
 
 /**
@@ -40,24 +41,26 @@ object StoryGroupReplySender {
           Completable.create {
             MessageSender.send(
               context,
-              OutgoingMediaMessage(
-                recipient,
-                body.toString(),
-                emptyList(),
-                System.currentTimeMillis(),
-                0,
-                0L,
-                false,
-                0,
-                StoryType.NONE,
-                ParentStoryId.GroupReply(message.id),
-                isReaction,
-                null,
-                emptyList(),
-                emptyList(),
-                mentions,
-                emptySet(),
-                emptySet()
+              OutgoingSecureMediaMessage(
+                OutgoingMediaMessage(
+                  recipient,
+                  body.toString(),
+                  emptyList(),
+                  System.currentTimeMillis(),
+                  0,
+                  0L,
+                  false,
+                  0,
+                  StoryType.NONE,
+                  ParentStoryId.GroupReply(message.id),
+                  isReaction,
+                  null,
+                  emptyList(),
+                  emptyList(),
+                  mentions,
+                  emptySet(),
+                  emptySet()
+                )
               ),
               message.threadId,
               false,
diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/DeleteDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/util/DeleteDialog.kt
index 423f9a48684e8dea8d7b10873841bfd0f8665ca3..85aa8fd425ac9bde878e906a7f880ceb90e27fc3 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/util/DeleteDialog.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/util/DeleteDialog.kt
@@ -15,6 +15,17 @@ import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask
 
 object DeleteDialog {
 
+  /**
+   * Displays a deletion dialog for the given set of message records.
+   *
+   * @param context           Android Context
+   * @param messageRecords    The message records to delete
+   * @param title             The dialog title
+   * @param message           The dialog message, or null
+   * @param forceRemoteDelete Allow remote deletion, even if it would normally be disallowed
+   *
+   * @return a Single, who's value notes whether or not a thread deletion occurred.
+   */
   fun show(
     context: Context,
     messageRecords: Set<MessageRecord>,
diff --git a/app/src/main/res/layout/stories_group_remote_delete_item.xml b/app/src/main/res/layout/stories_group_remote_delete_item.xml
new file mode 100644
index 0000000000000000000000000000000000000000..48f93fe66e46d3009eca9309d501827305f51d69
--- /dev/null
+++ b/app/src/main/res/layout/stories_group_remote_delete_item.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginHorizontal="8dp"
+    android:background="@drawable/selectable_list_item_background"
+    android:clipToPadding="false"
+    android:paddingHorizontal="8dp"
+    android:paddingTop="6dp"
+    android:paddingBottom="6dp">
+
+    <org.thoughtcrime.securesms.components.AvatarImageView
+        android:id="@+id/avatar"
+        android:layout_width="28dp"
+        android:layout_height="28dp"
+        app:fallbackImageSize="small"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/bubble"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
+        android:background="@drawable/rounded_rectangle_secondary_18"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constraintStart_toEndOf="@id/avatar"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <org.thoughtcrime.securesms.components.FromTextView
+            android:id="@+id/name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="12dp"
+            android:layout_marginTop="7dp"
+            android:layout_marginEnd="12dp"
+            android:textAppearance="@style/TextAppearance.Signal.Subtitle.Bold"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="Miles Morales" />
+
+        <org.thoughtcrime.securesms.components.emoji.EmojiTextView
+            android:id="@+id/body"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="12dp"
+            android:layout_marginTop="1dp"
+            android:layout_marginEnd="20dp"
+            android:layout_marginBottom="1dp"
+            android:text="@string/ThreadRecord_this_message_was_deleted"
+            android:textAppearance="@style/Signal.Text.Body"
+            android:textStyle="italic"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintBottom_toTopOf="@id/viewed_at_below"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/name"
+            app:layout_goneMarginBottom="7dp"
+            app:measureLastLine="true" />
+
+        <TextView
+            android:id="@+id/viewed_at"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="6dp"
+            android:layout_marginEnd="12dp"
+            android:layout_marginBottom="5dp"
+            android:textAppearance="@style/Signal.Text.Caption"
+            android:textColor="@color/transparent_white_60"
+            android:visibility="gone"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="1"
+            app:layout_constraintStart_toEndOf="@id/body"
+            tools:text="15m"
+            tools:textColor="@color/signal_text_secondary"
+            tools:visibility="visible" />
+
+        <TextView
+            android:id="@+id/viewed_at_below"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="6dp"
+            android:layout_marginEnd="12dp"
+            android:layout_marginBottom="5dp"
+            android:textAppearance="@style/Signal.Text.Caption"
+            android:textColor="@color/transparent_white_60"
+            android:visibility="gone"
+            app:layout_constraintBottom_toBottomOf="@id/bubble"
+            app:layout_constraintEnd_toEndOf="@id/bubble"
+            tools:text="15m"
+            tools:textColor="@color/signal_text_secondary" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file