diff --git a/ibis/ibismessage.h b/ibis/ibismessage.h
--- a/ibis/ibismessage.h
+++ b/ibis/ibismessage.h
@@ -101,6 +101,18 @@
 /**
  * ibis_message_set_params:
  * @message: The instance.
+ * @...: The parameters to add followed by %NULL.
+ *
+ * Sets the parameters of @message.
+ *
+ * Since: 0.1
+ */
+IBIS_AVAILABLE_IN_0_1
+void ibis_message_set_params(IbisMessage *message, ...) G_GNUC_NULL_TERMINATED;
+
+/**
+ * ibis_message_set_paramsv: (rename-to ibis_message_set_params):
+ * @message: The instance.
  * @params: (transfer none) (nullable): The new parameters.
  *
  * Sets the parameters of @message to @params.
@@ -108,7 +120,7 @@
  * Since: 0.1
  */
 IBIS_AVAILABLE_IN_0_1
-void ibis_message_set_params(IbisMessage *message, GStrv params);
+void ibis_message_set_paramsv(IbisMessage *message, GStrv params);
 
 /**
  * ibis_message_get_source:
# HG changeset patch
# Node ID 2605864d74f6
# Parent  c22f75f9b543
diff --git a/ibis/ibismessage.c b/ibis/ibismessage.c
--- a/ibis/ibismessage.c
+++ b/ibis/ibismessage.c
@@ -98,7 +98,7 @@
 		ibis_message_set_command(message, g_value_get_string(value));
 		break;
 	case PROP_PARAMS:
-		ibis_message_set_params(message, g_value_get_boxed(value));
+		ibis_message_set_paramsv(message, g_value_get_boxed(value));
 		break;
 	case PROP_SOURCE:
 		ibis_message_set_source(message, g_value_get_string(value));
@@ -192,6 +192,8 @@
  *****************************************************************************/
 IbisMessage *
 ibis_message_new(const char *command) {
+	g_return_val_if_fail(!ibis_str_is_empty(command), NULL);
+
 	return g_object_new(
 		IBIS_TYPE_MESSAGE,
 		"command", command,
@@ -267,7 +269,31 @@
 }
 
 void
-ibis_message_set_params(IbisMessage *message, GStrv params) {
+ibis_message_set_params(IbisMessage *message, ...) {
+	GStrv params = NULL;
+	GStrvBuilder *builder = NULL;
+	va_list args;
+	const char *param = NULL;
+
+	g_return_if_fail(IBIS_IS_MESSAGE(message));
+
+	builder = g_strv_builder_new();
+
+	va_start(args, message);
+	while((param = va_arg(args, const char *)) != NULL) {
+		g_strv_builder_add(builder, param);
+	}
+	va_end(args);
+
+	params = g_strv_builder_end(builder);
+	g_clear_pointer(&builder, g_strv_builder_unref);
+
+	ibis_message_set_paramsv(message, params);
+	g_strfreev(params);
+}
+
+void
+ibis_message_set_paramsv(IbisMessage *message, GStrv params) {
 	g_return_if_fail(IBIS_IS_MESSAGE(message));
 
 	if(message->params != params) {
@@ -280,4 +306,3 @@
 		g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_PARAMS]);
 	}
 }
-
diff --git a/ibis/ibisparser.c b/ibis/ibisparser.c
--- a/ibis/ibisparser.c
+++ b/ibis/ibisparser.c
@@ -379,7 +379,7 @@
 	params = ibis_parser_build_params(parser, middle, coda, trailing,
 	                                          NULL);
 	if(params != NULL) {
-		ibis_message_set_params(message, params);
+		ibis_message_set_paramsv(message, params);
 	}
 
 	g_free(command);
diff --git a/ibis/tests/meson.build b/ibis/tests/meson.build
--- a/ibis/tests/meson.build
+++ b/ibis/tests/meson.build
@@ -1,5 +1,6 @@
 test_programs = [
   'formatting',
+  'message',
   'parser',
   'source',
 ]
diff --git a/ibis/tests/test_message.c b/ibis/tests/test_message.c
new file mode 100644
--- /dev/null
+++ b/ibis/tests/test_message.c
@@ -0,0 +1,157 @@
+/*
+ * Ibis - IRCv3 Library
+ * Copyright (C) 2022-2024 Ibis Developers
+ *
+ * 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 <ibis.h>
+
+/******************************************************************************
+ * Tests
+ *****************************************************************************/
+static void
+test_ibis_message_new_valid(void) {
+	IbisMessage *message = NULL;
+
+	message = ibis_message_new("PRIVMSG");
+
+	g_assert_true(IBIS_IS_MESSAGE(message));
+
+	g_assert_finalize_object(message);
+}
+
+static void
+test_ibis_message_new_empty(void) {
+	if(g_test_subprocess()) {
+		ibis_message_new("");
+	}
+
+	g_test_trap_subprocess(NULL, 0, G_TEST_SUBPROCESS_DEFAULT);
+	g_test_trap_assert_stderr("*!ibis_str_is_empty*failed*");
+}
+
+static void
+test_ibis_message_new_null(void) {
+	if(g_test_subprocess()) {
+		ibis_message_new(NULL);
+	}
+
+	g_test_trap_subprocess(NULL, 0, G_TEST_SUBPROCESS_DEFAULT);
+	g_test_trap_assert_stderr("*!ibis_str_is_empty*failed*");
+}
+
+static void
+test_ibis_message_properties(void) {
+	IbisMessage *message = NULL;
+	GHashTable *tags1 = NULL;
+	GHashTable *tags2 = NULL;
+	GStrv params1 = NULL;
+	GStrv params2 = NULL;
+	GStrvBuilder *builder = NULL;
+	char *source = NULL;
+	char *command = NULL;
+	gpointer value = NULL;
+	gboolean found = FALSE;
+
+	tags1 = g_hash_table_new(g_str_hash, g_str_equal);
+	g_hash_table_insert(tags1, "foo", "bar");
+	g_hash_table_insert(tags1, "baz", NULL);
+
+	builder = g_strv_builder_new();
+	g_strv_builder_add_many(builder, "foo", "bar", "baz", NULL);
+	params1 = g_strv_builder_end(builder);
+	g_strv_builder_unref(builder);
+
+	message = g_object_new(
+		IBIS_TYPE_MESSAGE,
+		"tags", tags1,
+		"source", "source",
+		"command", "command",
+		"params", params1,
+		NULL);
+
+	g_assert_true(IBIS_IS_MESSAGE(message));
+
+	g_object_get(
+		message,
+		"tags", &tags2,
+		"source", &source,
+		"command", &command,
+		"params", &params2,
+		NULL);
+
+	g_assert_nonnull(tags2);
+	found = g_hash_table_lookup_extended(tags2, "foo", NULL, &value);
+	g_assert_true(found);
+	g_assert_cmpstr((char *)value, ==, "bar");
+
+	found = g_hash_table_lookup_extended(tags2, "baz", NULL, &value);
+	g_assert_true(found);
+	g_assert_null(value);
+	g_clear_pointer(&tags2, g_hash_table_unref);
+
+	g_assert_cmpstr(source, ==, "source");
+	g_clear_pointer(&source, g_free);
+
+	g_assert_cmpstr(command, ==, "command");
+	g_clear_pointer(&command, g_free);
+
+	g_assert_cmpstrv(params2, params1);
+	g_clear_pointer(&params2, g_strfreev);
+
+	g_clear_pointer(&tags1, g_hash_table_unref);
+	g_clear_pointer(&params1, g_strfreev);
+	g_assert_finalize_object(message);
+}
+
+static void
+test_ibis_message_set_params_variadic(void) {
+	IbisMessage *message = NULL;
+	GStrv params = NULL;
+	const char * const expected[] = {"foo", "bar", "baz", NULL};
+
+	message = ibis_message_new("command");
+
+	params = ibis_message_get_params(message);
+	g_assert_null(params);
+
+	ibis_message_set_params(message, "foo", "bar", "baz", NULL);
+
+	params = ibis_message_get_params(message);
+	g_assert_cmpstrv(params, (GStrv)expected);
+
+	g_assert_finalize_object(message);
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+int
+main(int argc, char *argv[]) {
+	g_test_init(&argc, &argv, NULL);
+
+	g_test_add_func("/ibis/message/new/valid", test_ibis_message_new_valid);
+	g_test_add_func("/ibis/message/new/empty", test_ibis_message_new_empty);
+	g_test_add_func("/ibis/message/new/null", test_ibis_message_new_null);
+
+	g_test_add_func("/ibis/message/properties", test_ibis_message_properties);
+
+	g_test_add_func("/ibis/message/set-params-variadic",
+	                test_ibis_message_set_params_variadic);
+
+	return g_test_run();
+}
