diff --git a/libpurple/purplemessage.h b/libpurple/purplemessage.h
--- a/libpurple/purplemessage.h
+++ b/libpurple/purplemessage.h
@@ -378,6 +378,29 @@
 PurpleMessageFlags purple_message_get_flags(PurpleMessage *message);
 
 /**
+ * purple_message_set_error:
+ * @message: The instance.
+ * @error: (nullable) (transfer full): The error to set.
+ *
+ * Sets the error of @message to at @error.
+ *
+ * Since: 3.0.0
+ */
+void purple_message_set_error(PurpleMessage *message, GError *error);
+
+/**
+ * purple_message_get_error:
+ * @message: The instance.
+ *
+ * Gets the error from @message.
+ *
+ * Returns: (nullable) (transfer none): The error from @message or %NULL.
+ *
+ * Since: 3.0.0
+ */
+GError *purple_message_get_error(PurpleMessage *message);
+
+/**
  * purple_message_add_attachment:
  * @message: The #PurpleMessage instance.
  * @attachment: The #PurpleAttachment instance.
# HG changeset patch
# Node ID 0791906229d0
# Parent  60a6bbf3bfe4
diff --git a/libpurple/purplemessage.c b/libpurple/purplemessage.c
--- a/libpurple/purplemessage.c
+++ b/libpurple/purplemessage.c
@@ -42,6 +42,8 @@
 	GDateTime *timestamp;
 	PurpleMessageFlags flags;
 
+	GError *error;
+
 	GHashTable *attachments;
 };
 
@@ -56,6 +58,7 @@
 	PROP_CONTENT_TYPE,
 	PROP_TIMESTAMP,
 	PROP_FLAGS,
+	PROP_ERROR,
 	N_PROPERTIES
 };
 static GParamSpec *properties[N_PROPERTIES];
@@ -119,6 +122,9 @@
 		case PROP_FLAGS:
 			g_value_set_flags(value, purple_message_get_flags(message));
 			break;
+		case PROP_ERROR:
+			g_value_set_boxed(value, purple_message_get_error(message));
+			break;
 		default:
 			G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
 			break;
@@ -160,6 +166,9 @@
 		case PROP_FLAGS:
 			purple_message_set_flags(message, g_value_get_flags(value));
 			break;
+		case PROP_ERROR:
+			purple_message_set_error(message, g_value_get_boxed(value));
+			break;
 		default:
 			G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
 			break;
@@ -177,6 +186,8 @@
 	g_free(message->recipient);
 	g_free(message->contents);
 
+	g_clear_error(&message->error);
+
 	if(message->timestamp != NULL) {
 		g_date_time_unref(message->timestamp);
 	}
@@ -317,6 +328,20 @@
 		PURPLE_TYPE_MESSAGE_FLAGS, 0,
 		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
 
+	/**
+	 * PurpleMessage:error:
+	 *
+	 * An error that this message encountered. This could be something like a
+	 * failed delivery, or failed redaction, or rate limited, etc.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_ERROR] = g_param_spec_boxed(
+		"error", "error",
+		"An error that the message encountered",
+		G_TYPE_ERROR,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
 	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
 }
 
@@ -574,6 +599,25 @@
 	return message->flags;
 }
 
+void
+purple_message_set_error(PurpleMessage *message, GError *error) {
+	g_return_if_fail(PURPLE_IS_MESSAGE(message));
+
+	g_clear_error(&message->error);
+	if(error != NULL) {
+		g_propagate_error(&message->error, error);
+	}
+
+	g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_ERROR]);
+}
+
+GError *
+purple_message_get_error(PurpleMessage *message) {
+	g_return_val_if_fail(PURPLE_IS_MESSAGE(message), NULL);
+
+	return message->error;
+}
+
 gboolean
 purple_message_add_attachment(PurpleMessage *message,
                               PurpleAttachment *attachment)
diff --git a/libpurple/tests/meson.build b/libpurple/tests/meson.build
--- a/libpurple/tests/meson.build
+++ b/libpurple/tests/meson.build
@@ -14,6 +14,7 @@
     'keyvaluepair',
     'markup',
     'menu',
+    'message',
     'notification',
     'notification_manager',
     'person',
diff --git a/libpurple/tests/test_message.c b/libpurple/tests/test_message.c
new file mode 100644
--- /dev/null
+++ b/libpurple/tests/test_message.c
@@ -0,0 +1,108 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+
+#include <purple.h>
+
+/******************************************************************************
+ * Tests
+ *****************************************************************************/
+static void
+test_purple_message_properties(void) {
+	PurpleMessage *message = NULL;
+	PurpleMessageContentType content_type = 0;
+	PurpleMessageFlags flags = 0;
+	GDateTime *timestamp = NULL;
+	GDateTime *timestamp1 = NULL;
+	GError *error = NULL;
+	GError *error1 = NULL;
+	char *id = NULL;
+	char *author = NULL;
+	char *author_alias = NULL;
+	char *author_name_color = NULL;
+	char *recipient = NULL;
+	char *contents = NULL;
+
+	timestamp = g_date_time_new_from_unix_utc(911347200);
+	error = g_error_new(g_quark_from_static_string("test-message"), 0,
+	                    "delivery failed");
+
+	message = g_object_new(
+		PURPLE_TYPE_MESSAGE,
+		"id", "id",
+		"author", "author",
+		"author-alias", "alias",
+		"author-name-color", "purple",
+		"recipient", "pidgy",
+		"contents", "Now that is a big door",
+		"content-type", PURPLE_MESSAGE_CONTENT_TYPE_MARKDOWN,
+		"timestamp", timestamp,
+		"flags", PURPLE_MESSAGE_SYSTEM,
+		"error", error,
+		NULL);
+
+	g_object_get(
+		message,
+		"id", &id,
+		"author", &author,
+		"author-alias", &author_alias,
+		"author-name-color", &author_name_color,
+		"recipient", &recipient,
+		"contents", &contents,
+		"content-type", &content_type,
+		"timestamp", &timestamp1,
+		"flags", &flags,
+		"error", &error1,
+		NULL);
+
+	g_assert_cmpstr(id, ==, "id");
+	g_assert_cmpstr(author, ==, "author");
+	g_assert_cmpstr(author_alias, ==, "alias");
+	g_assert_cmpstr(author_name_color, ==, "purple");
+	g_assert_cmpstr(recipient, ==, "pidgy");
+	g_assert_cmpstr(contents, ==, "Now that is a big door");
+	g_assert_cmpint(content_type, ==, PURPLE_MESSAGE_CONTENT_TYPE_MARKDOWN);
+	g_assert_true(g_date_time_equal(timestamp1, timestamp));
+	g_assert_cmpint(flags, ==, PURPLE_MESSAGE_SYSTEM);
+	g_assert_error(error1, error->domain, error->code);
+
+	g_clear_pointer(&id, g_free);
+	g_clear_pointer(&author, g_free);
+	g_clear_pointer(&author_alias, g_free);
+	g_clear_pointer(&author_name_color, g_free);
+	g_clear_pointer(&recipient, g_free);
+	g_clear_pointer(&contents, g_free);
+	g_clear_pointer(&timestamp, g_date_time_unref);
+	g_clear_pointer(&timestamp1, g_date_time_unref);
+	g_clear_error(&error1);
+	g_clear_object(&message);
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+gint
+main(gint argc, gchar *argv[]) {
+	g_test_init(&argc, &argv, NULL);
+
+	g_test_add_func("/message/properties",
+	                test_purple_message_properties);
+
+	return g_test_run();
+}
