diff --git a/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api b/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api
index c4c1fddca..c777be37a 100644
--- a/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api
+++ b/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api
@@ -181,9 +181,11 @@ public abstract interface class net/mamoe/mirai/contact/AudioSupported : net/mam
 	public abstract fun uploadAudio (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
 }
 
-public final class net/mamoe/mirai/contact/BotIsBeingMutedException : java/lang/RuntimeException {
+public final class net/mamoe/mirai/contact/BotIsBeingMutedException : net/mamoe/mirai/contact/SendMessageFailedException {
 	public fun <init> (Lnet/mamoe/mirai/contact/Group;)V
-	public final fun getTarget ()Lnet/mamoe/mirai/contact/Group;
+	public fun getMessage ()Ljava/lang/String;
+	public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact;
+	public fun getTarget ()Lnet/mamoe/mirai/contact/Group;
 }
 
 public final class net/mamoe/mirai/contact/ClientKind : java/lang/Enum {
@@ -439,11 +441,11 @@ public final class net/mamoe/mirai/contact/MemberPermissionKt {
 	public static final fun isOwner (Lnet/mamoe/mirai/contact/Member;)Z
 }
 
-public final class net/mamoe/mirai/contact/MessageTooLargeException : java/lang/RuntimeException {
+public final class net/mamoe/mirai/contact/MessageTooLargeException : net/mamoe/mirai/contact/SendMessageFailedException {
 	public fun <init> (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/message/data/Message;Lnet/mamoe/mirai/message/data/Message;Ljava/lang/String;)V
+	public fun getMessage ()Ljava/lang/String;
 	public final fun getMessageAfterEvent ()Lnet/mamoe/mirai/message/data/Message;
-	public final fun getOriginalMessage ()Lnet/mamoe/mirai/message/data/Message;
-	public final fun getTarget ()Lnet/mamoe/mirai/contact/Contact;
+	public fun getTarget ()Lnet/mamoe/mirai/contact/Contact;
 }
 
 public abstract interface class net/mamoe/mirai/contact/NormalMember : net/mamoe/mirai/contact/Member {
@@ -529,6 +531,19 @@ public final class net/mamoe/mirai/contact/Platform : java/lang/Enum {
 public final class net/mamoe/mirai/contact/Platform$Companion {
 }
 
+public class net/mamoe/mirai/contact/SendMessageFailedException : java/lang/RuntimeException {
+	public final fun getOriginalMessage ()Lnet/mamoe/mirai/message/data/Message;
+	public final fun getReason ()Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason;
+	public fun getTarget ()Lnet/mamoe/mirai/contact/Contact;
+}
+
+public final class net/mamoe/mirai/contact/SendMessageFailedException$Reason : java/lang/Enum {
+	public static final field BOT_MUTED Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason;
+	public static final field MESSAGE_TOO_LARGE Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason;
+	public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason;
+	public static fun values ()[Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason;
+}
+
 public abstract interface class net/mamoe/mirai/contact/Stranger : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/User {
 	public fun delete ()V
 	public abstract fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
diff --git a/binary-compatibility-validator/api/binary-compatibility-validator.api b/binary-compatibility-validator/api/binary-compatibility-validator.api
index ed35cde73..272d1d96a 100644
--- a/binary-compatibility-validator/api/binary-compatibility-validator.api
+++ b/binary-compatibility-validator/api/binary-compatibility-validator.api
@@ -181,9 +181,11 @@ public abstract interface class net/mamoe/mirai/contact/AudioSupported : net/mam
 	public abstract fun uploadAudio (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
 }
 
-public final class net/mamoe/mirai/contact/BotIsBeingMutedException : java/lang/RuntimeException {
+public final class net/mamoe/mirai/contact/BotIsBeingMutedException : net/mamoe/mirai/contact/SendMessageFailedException {
 	public fun <init> (Lnet/mamoe/mirai/contact/Group;)V
-	public final fun getTarget ()Lnet/mamoe/mirai/contact/Group;
+	public fun getMessage ()Ljava/lang/String;
+	public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact;
+	public fun getTarget ()Lnet/mamoe/mirai/contact/Group;
 }
 
 public final class net/mamoe/mirai/contact/ClientKind : java/lang/Enum {
@@ -439,11 +441,11 @@ public final class net/mamoe/mirai/contact/MemberPermissionKt {
 	public static final fun isOwner (Lnet/mamoe/mirai/contact/Member;)Z
 }
 
-public final class net/mamoe/mirai/contact/MessageTooLargeException : java/lang/RuntimeException {
+public final class net/mamoe/mirai/contact/MessageTooLargeException : net/mamoe/mirai/contact/SendMessageFailedException {
 	public fun <init> (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/message/data/Message;Lnet/mamoe/mirai/message/data/Message;Ljava/lang/String;)V
+	public fun getMessage ()Ljava/lang/String;
 	public final fun getMessageAfterEvent ()Lnet/mamoe/mirai/message/data/Message;
-	public final fun getOriginalMessage ()Lnet/mamoe/mirai/message/data/Message;
-	public final fun getTarget ()Lnet/mamoe/mirai/contact/Contact;
+	public fun getTarget ()Lnet/mamoe/mirai/contact/Contact;
 }
 
 public abstract interface class net/mamoe/mirai/contact/NormalMember : net/mamoe/mirai/contact/Member {
@@ -529,6 +531,19 @@ public final class net/mamoe/mirai/contact/Platform : java/lang/Enum {
 public final class net/mamoe/mirai/contact/Platform$Companion {
 }
 
+public class net/mamoe/mirai/contact/SendMessageFailedException : java/lang/RuntimeException {
+	public final fun getOriginalMessage ()Lnet/mamoe/mirai/message/data/Message;
+	public final fun getReason ()Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason;
+	public fun getTarget ()Lnet/mamoe/mirai/contact/Contact;
+}
+
+public final class net/mamoe/mirai/contact/SendMessageFailedException$Reason : java/lang/Enum {
+	public static final field BOT_MUTED Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason;
+	public static final field MESSAGE_TOO_LARGE Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason;
+	public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason;
+	public static fun values ()[Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason;
+}
+
 public abstract interface class net/mamoe/mirai/contact/Stranger : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/User {
 	public fun delete ()V
 	public abstract fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
diff --git a/mirai-core-api/src/commonMain/kotlin/contact/Exceptions.kt b/mirai-core-api/src/commonMain/kotlin/contact/Exceptions.kt
index c523f8760..3fb9c8aca 100644
--- a/mirai-core-api/src/commonMain/kotlin/contact/Exceptions.kt
+++ b/mirai-core-api/src/commonMain/kotlin/contact/Exceptions.kt
@@ -12,6 +12,9 @@
 package net.mamoe.mirai.contact
 
 import net.mamoe.mirai.message.data.Message
+import net.mamoe.mirai.message.data.messageChainOf
+import net.mamoe.mirai.utils.DeprecatedSinceMirai
+import net.mamoe.mirai.utils.MiraiInternalApi
 import net.mamoe.mirai.utils.millisToHumanReadableString
 import kotlin.time.ExperimentalTime
 
@@ -20,18 +23,20 @@ import kotlin.time.ExperimentalTime
  *
  * @see Contact.sendMessage
  */
-public class MessageTooLargeException(
-    public val target: Contact,
+public class MessageTooLargeException constructor(
+    public override val target: Contact,
     /**
      * 原发送消息
      */
-    public val originalMessage: Message,
+    originalMessage: Message,
     /**
      * 经过事件拦截处理后的消息
      */
     public val messageAfterEvent: Message,
     exceptionMessage: String
-) : RuntimeException(exceptionMessage)
+) : SendMessageFailedException(target, Reason.MESSAGE_TOO_LARGE, originalMessage) {
+    override val message: String = exceptionMessage
+}
 
 /**
  * 发送消息时 bot 正处于被禁言状态时抛出的异常.
@@ -39,12 +44,46 @@ public class MessageTooLargeException(
  * @see Group.sendMessage
  */
 @OptIn(ExperimentalTime::class)
-public class BotIsBeingMutedException(
-    public val target: Group
-) : RuntimeException(
-    "bot is being muted, remaining ${
+public class BotIsBeingMutedException @MiraiInternalApi constructor(
+    // this constructor is since 2.9.0-RC
+    public override val target: Group,
+    originalMessage: Message,
+) : SendMessageFailedException(target, Reason.BOT_MUTED, originalMessage) {
+    @DeprecatedSinceMirai("2.9")
+    @Deprecated("Deprecated without replacement. Please consider copy this exception to your code.")
+    // this constructor is since 2.0
+    public constructor(
+        target: Group,
+    ) : this(target, messageChainOf())
+
+    override val message: String = "bot is being muted, remaining ${
         target.botMuteRemaining.times(1000).millisToHumanReadableString()
     } seconds"
-)
+}
 
-public inline val BotIsBeingMutedException.botMuteRemaining: Int get() = target.botMuteRemaining
\ No newline at end of file
+public inline val BotIsBeingMutedException.botMuteRemaining: Int get() = target.botMuteRemaining
+
+/**
+ * 发送消息失败时抛出的异常
+ *
+ * @since 2.9.0
+ */
+public open class SendMessageFailedException @MiraiInternalApi constructor(
+    public open val target: Contact,
+    public val reason: Reason,
+    public val originalMessage: Message,
+) : RuntimeException(
+    "Failed sending message to $target, reason=$reason"
+) {
+    public enum class Reason {
+        /**
+         * 消息过长
+         */
+        MESSAGE_TOO_LARGE,
+
+        /**
+         * 机器人被禁言
+         */
+        BOT_MUTED,
+    }
+}
\ No newline at end of file
diff --git a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt
index 9397165b3..6636efafe 100644
--- a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt
+++ b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt
@@ -160,7 +160,7 @@ internal class GroupImpl constructor(
         } else false
 
         require(isMiraiInternal || !message.isContentEmpty()) { "message is empty" }
-        check(!isBotMuted) { throw BotIsBeingMutedException(this) }
+        check(!isBotMuted) { throw BotIsBeingMutedException(this, message) }
 
         val chain = broadcastMessagePreSendEvent(message, isMiraiInternal, ::GroupMessagePreSendEvent)
 
diff --git a/mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt b/mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt
index 3f522f122..f67c897e8 100644
--- a/mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt
+++ b/mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt
@@ -172,7 +172,7 @@ internal abstract class SendMessageHandler<C : Contact> {
                         if (resp is MessageSvcPbSendMsg.Response.Failed) {
                             val contact = contact
                             when (resp.resultType) {
-                                120 -> if (contact is Group) throw BotIsBeingMutedException(contact)
+                                120 -> if (contact is Group) throw BotIsBeingMutedException(contact, originalMessage)
                                 121 -> if (AtAll in finalMessage) throw IllegalStateException("Send message to $contact failed, reached maximum AtAll times limit.")
                             }
                         }