From 7830d0d6450e653dc5446bfbe8386a0b66dc7246 Mon Sep 17 00:00:00 2001 From: timedout Date: Sat, 13 Jun 2026 01:42:44 +0100 Subject: [PATCH 1/7] initial draft Signed-off-by: timedout --- proposals/4491-create-room-invite-reason.md | 108 ++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 proposals/4491-create-room-invite-reason.md diff --git a/proposals/4491-create-room-invite-reason.md b/proposals/4491-create-room-invite-reason.md new file mode 100644 index 00000000000..a231a0458aa --- /dev/null +++ b/proposals/4491-create-room-invite-reason.md @@ -0,0 +1,108 @@ +# MSC4491: Invite reasons in room creation + +[`POST /_matrix/client/v3/createRoom`][spec-createRoom] allows you to specify a list of user IDs to +invite (via `invite`) and any third party invites (via `invite_3pid`). It further allows you to mark +these invites as "direct", via `is_direct`, indicating to the recipient(s) that this room is +intended to be a DM room. However, there current lacks a mechanism within which to specify an +invitation *reason*, which can leave recipients confused as to why they received an invite, and may +lead them to mistake the invite for spam. + +However, this `is_direct` flag cannot be set after room creation, without manually sending the +invite membership state event. This means that inviting a recipient after the room is created (in +order to include a `reason`) will not tell the recipient that the invite is to a direct message, +which again results in confusion and poor UI. + +[spec-createRoom]: https://spec.matrix.org/v1.18/client-server-api/#post_matrixclientv3createroom + +## Proposal + +This proposal introduces two new optional fields in the `/createRoom` payload: `invite_reasons` and +`invite_reason`. + +`invite_reason` is an optional string that, if provided, should be the default `reason` included +in each `m.room.member` invite state event sent by the server resulting from the `invite` array. + +`invite_reasons` is an optional mapping of `{user_id: string}`. +Each key should be a user ID that is already present in `invite`. +The value of each key should be the `reason` to set in each invite dispatched as a result of +`invite`. + +Clients SHOULD NOT include user IDs in `invite_reasons` that are not in `invite`, and SHOULD NOT +make the value of any `invite_reasons` entry the same as `invite_reason`. Servers MUST ignore any +entries which appear in `invite_reasons` that do not also appear in `invite`. + +For each user ID in `invite`: + +1. If a user ID appears in `invite_reasons`, the value provided in that mapping should be used as + the `reason` field, even if it is empty (which is akin to removing the reason for that specific + invite, in which case the server may omit the reason field entirely). +2. If `invite_reason` is provided AND not empty, that value should populate the reason field. +3. Otherwise, no reason is attached to the invite. + +An example usage of this two-field mechanism may be: + +1. A client wishes to invite everyone in a + predecessor room after an upgrade. It can include every joined member in `invite`, and provide + `{"invite_reason": "This room was replaced, please join the new one."}`. Then, every user who + receives an invite to that room knows exactly why they were invited, and is more likely to + accept. +2. A client wishes to start a DM with a user, and would like to provide them with a reason before + inviting them. In this case, if there's only one user to invite to the DM, `invite_reason` + *could* suitably be used, but the client may instead opt to omit `invite_reason` and explicitly + name the user in `invite_reasons`: `{"invite_reasons": {"@alice:example.com": "Hi Alice!"}}`. +3. A client wishes to start a group with a few users and wishes to preemptively explain why they + were invited (via `invite_reason`), but modify the reason for + certain members(via `invite_reasons`). + +Another use-case could be safety related: clients may opt to reject any invites that lack a reason, +especially from users they do not share a room with or have no non-public mutual rooms with. This +would also open the possibility for a future extension to [invite blocking][spec-invite-blocking]. + +[spec-invite-blocking]: https://spec.matrix.org/v1.18/client-server-api/#invite-permission + +## Potential issues + +Servers which do not understand these new parameters will not be able to include reasons, which may +result in users unexpectedly issuing invites on room creation which lack a reason. To alleviate +this, clients MAY check [`GET /_matrix/clients/versions`][spec-versions] for an applicable spec +version (if merged), or the unstable prefix in [Unstable prefix](#unstable-prefix), before +presenting the user with a UI to include an invite reason when creating a room. + +[spec-versions]: https://spec.matrix.org/v1.18/client-server-api/#get_matrixclientversions + +## Alternatives + +[`POST /_matrix/client/v3/rooms/{roomId}/invite`][spec-invite] could be modified to accept +`is_direct` as a body parameter, but this might allow clients to incorrectly mark rooms as direct +and indirect when issuing an invite, and preventing that would require the server tracks the flag +for the room (at which point, the server might as well issue the invites anyway). + +Sending the `m.room.member` state event manually has also been considered, but the author deemed +this non-idiomatic, and also relies upon the server supporting creating (typically federated) +invites on-the-fly while handling new events, which is not guaranteed behaviour. + +## Security considerations + +A malicious actor could use this to distribute invite spam with abusive reasons even faster than +prior to this proposal if the homeserver does not implement sufficient rate-limiting technologies on +room-creation invites. Prior, actors would be limited either by the rate-limit applied to the +individual invite endpoint, or at least by the rate at which they could create individual requests. + +## Unstable prefix + +`uk.timedout.msc4491` should be used as a prefix for the new fields until this proposal is stable. + +| Stable | Unstable | +| ------ | -------- | +| `invite_reason` | `uk.timedout.msc4491.invite_reason` | +| `invite_reasons` | `uk.timedout.msc4491.invite_reasons` | + +`uk.timedout.msc4491.create_room_invite_reasons` SHOULD be present in the `unstable_features` of +[`GET /_matrix/client/versions`][spec-versions] until this proposal is merged, to indicate +unstable support for the new fields. + +This proposal is intended to be backwards compatible and thus does not need to bump any versions. + +## Dependencies + +None. From 33f588dce7eb25d4e71bc681b9c42717ebdd06f4 Mon Sep 17 00:00:00 2001 From: timedout Date: Sat, 13 Jun 2026 01:55:48 +0100 Subject: [PATCH 2/7] Improve clarity --- proposals/4491-create-room-invite-reason.md | 27 ++++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/proposals/4491-create-room-invite-reason.md b/proposals/4491-create-room-invite-reason.md index a231a0458aa..acffa8b9095 100644 --- a/proposals/4491-create-room-invite-reason.md +++ b/proposals/4491-create-room-invite-reason.md @@ -1,18 +1,20 @@ # MSC4491: Invite reasons in room creation [`POST /_matrix/client/v3/createRoom`][spec-createRoom] allows you to specify a list of user IDs to -invite (via `invite`) and any third party invites (via `invite_3pid`). It further allows you to mark -these invites as "direct", via `is_direct`, indicating to the recipient(s) that this room is -intended to be a DM room. However, there current lacks a mechanism within which to specify an -invitation *reason*, which can leave recipients confused as to why they received an invite, and may -lead them to mistake the invite for spam. +invite (via `invite`). It further allows you to mark these invites as "direct", via `is_direct`, +indicating to the recipient(s) that this room is intended to be a DM room. However, there is +currently no mechanism within which to specify an invitation *reason*, which can leave recipients +confused as to why they received an invite, and may lead them to mistake the invite for spam. -However, this `is_direct` flag cannot be set after room creation, without manually sending the -invite membership state event. This means that inviting a recipient after the room is created (in -order to include a `reason`) will not tell the recipient that the invite is to a direct message, -which again results in confusion and poor UI. +However, this `is_direct` flag [cannot be set after room creation][spec-is_direct-sb], without +manually sending the invite membership state event (see [Alternatives](#alternatives)). +This means that inviting a recipient after the room is created (in order to include a `reason`) +will not tell the recipient that the invite is to a direct message, which again results in confusion +and poor UI[^gomuks#731]. [spec-createRoom]: https://spec.matrix.org/v1.18/client-server-api/#post_matrixclientv3createroom +[spec-is_direct-sb]: https://spec.matrix.org/v1.18/client-server-api/#server-behaviour-16 +[^gomuks#731]: See the demo video in ## Proposal @@ -27,6 +29,13 @@ Each key should be a user ID that is already present in `invite`. The value of each key should be the `reason` to set in each invite dispatched as a result of `invite`. +Or, as a nicely presented table: + +| Key | Type | Description | +| ---------------- | ------------------- | ----------- | +| `invite_reason` | `string` (optional) | The default invite reason to include in any implied `m.room.member` events. | +| `invite_reasons` | `{string: string}` (optional) | Individual `reason`s to include in specific implied `m.room.member` events. | + Clients SHOULD NOT include user IDs in `invite_reasons` that are not in `invite`, and SHOULD NOT make the value of any `invite_reasons` entry the same as `invite_reason`. Servers MUST ignore any entries which appear in `invite_reasons` that do not also appear in `invite`. From 61b1910e0c33e3746904db8cd704eb4caaddd345 Mon Sep 17 00:00:00 2001 From: timedout Date: Sat, 13 Jun 2026 01:58:39 +0100 Subject: [PATCH 3/7] Address early feedback --- proposals/4491-create-room-invite-reason.md | 22 +++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/proposals/4491-create-room-invite-reason.md b/proposals/4491-create-room-invite-reason.md index acffa8b9095..2c0bf87d1cb 100644 --- a/proposals/4491-create-room-invite-reason.md +++ b/proposals/4491-create-room-invite-reason.md @@ -10,7 +10,7 @@ However, this `is_direct` flag [cannot be set after room creation][spec-is_direc manually sending the invite membership state event (see [Alternatives](#alternatives)). This means that inviting a recipient after the room is created (in order to include a `reason`) will not tell the recipient that the invite is to a direct message, which again results in confusion -and poor UI[^gomuks#731]. +and poor UX[^gomuks#731]. [spec-createRoom]: https://spec.matrix.org/v1.18/client-server-api/#post_matrixclientv3createroom [spec-is_direct-sb]: https://spec.matrix.org/v1.18/client-server-api/#server-behaviour-16 @@ -81,14 +81,20 @@ presenting the user with a UI to include an invite reason when creating a room. ## Alternatives -[`POST /_matrix/client/v3/rooms/{roomId}/invite`][spec-invite] could be modified to accept -`is_direct` as a body parameter, but this might allow clients to incorrectly mark rooms as direct -and indirect when issuing an invite, and preventing that would require the server tracks the flag -for the room (at which point, the server might as well issue the invites anyway). +* [`POST /_matrix/client/v3/rooms/{roomId}/invite`][spec-invite] could be modified to accept + `is_direct` as a body parameter, but this might allow clients to incorrectly mark rooms as direct + and indirect when issuing an invite, and preventing that would require the server tracks the flag + for the room (at which point, the server might as well issue the invites anyway). -Sending the `m.room.member` state event manually has also been considered, but the author deemed -this non-idiomatic, and also relies upon the server supporting creating (typically federated) -invites on-the-fly while handling new events, which is not guaranteed behaviour. +* Sending the `m.room.member` state event manually has also been considered, but the author deemed + this non-idiomatic, and also relies upon the server supporting creating (typically federated) + invites on-the-fly while handling new events, which is not guaranteed behaviour. + +* A new endpoint that replaces `invite` with a new mapping, which would allow + `is_direct` and/or `reason` to be supplied atomically, may be worth consideration, but would + require an endpoint version bump, has more complex implementation shapes, and is otherwise + something the author is not interested in pursuing unless there is expressed interest in this + more demanding change. ## Security considerations From 8ee68c0f7b511f24cfa97fae38fe1000eabf2169 Mon Sep 17 00:00:00 2001 From: timedout Date: Sat, 13 Jun 2026 02:01:03 +0100 Subject: [PATCH 4/7] Fix broken hyperlink Apparently I managed to write this entire proposal without directly referencing the invite endpoint --- proposals/4491-create-room-invite-reason.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proposals/4491-create-room-invite-reason.md b/proposals/4491-create-room-invite-reason.md index 2c0bf87d1cb..bdd4d50717b 100644 --- a/proposals/4491-create-room-invite-reason.md +++ b/proposals/4491-create-room-invite-reason.md @@ -96,6 +96,8 @@ presenting the user with a UI to include an invite reason when creating a room. something the author is not interested in pursuing unless there is expressed interest in this more demanding change. +[spec-invite]: https://spec.matrix.org/v1.18/client-server-api/#post_matrixclientv3roomsroomidinvite + ## Security considerations A malicious actor could use this to distribute invite spam with abusive reasons even faster than From f54b720b41e8496e35b9b57ce856ea663fa5763d Mon Sep 17 00:00:00 2001 From: timedout Date: Sat, 13 Jun 2026 02:09:00 +0100 Subject: [PATCH 5/7] Abuse concerns are not security concerns --- proposals/4491-create-room-invite-reason.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/proposals/4491-create-room-invite-reason.md b/proposals/4491-create-room-invite-reason.md index bdd4d50717b..04e2481eeb2 100644 --- a/proposals/4491-create-room-invite-reason.md +++ b/proposals/4491-create-room-invite-reason.md @@ -77,6 +77,21 @@ this, clients MAY check [`GET /_matrix/clients/versions`][spec-versions] for an version (if merged), or the unstable prefix in [Unstable prefix](#unstable-prefix), before presenting the user with a UI to include an invite reason when creating a room. +### Abuse + +Unsolicited invites are a known abuse vector in Matrix, with ongoing efforts to reduce the area. +As such, concerns regarding abuse are explicitly called out here: + +A malicious actor could use this to distribute invite spam with abusive reasons even faster than +prior to this proposal if the homeserver does not implement sufficient rate-limiting technologies on +room-creation invites. Prior, actors would be limited either by the rate-limit applied to the +individual invite endpoint, or at least by the rate at which they could create individual requests. + +Server implementations MAY also wish to refuse to create rooms where an invite reason is too long +or otherwise is flagged by a spam filter in order to prevent malicious users mass-distributing +abusive messages in invite reasons. Implementations may already have implemented this on the +[invite endpoint][spec-invite], although this is not explicitly specified behaviour. + [spec-versions]: https://spec.matrix.org/v1.18/client-server-api/#get_matrixclientversions ## Alternatives @@ -100,10 +115,7 @@ presenting the user with a UI to include an invite reason when creating a room. ## Security considerations -A malicious actor could use this to distribute invite spam with abusive reasons even faster than -prior to this proposal if the homeserver does not implement sufficient rate-limiting technologies on -room-creation invites. Prior, actors would be limited either by the rate-limit applied to the -individual invite endpoint, or at least by the rate at which they could create individual requests. +None. ## Unstable prefix From 63d53d3046b7637d8bc98b364cf6b7759f50e0b1 Mon Sep 17 00:00:00 2001 From: timedout Date: Sat, 13 Jun 2026 18:29:09 +0100 Subject: [PATCH 6/7] Drop `invite_reasons` --- proposals/4491-create-room-invite-reason.md | 50 +++------------------ 1 file changed, 7 insertions(+), 43 deletions(-) diff --git a/proposals/4491-create-room-invite-reason.md b/proposals/4491-create-room-invite-reason.md index 04e2481eeb2..321087323d7 100644 --- a/proposals/4491-create-room-invite-reason.md +++ b/proposals/4491-create-room-invite-reason.md @@ -18,50 +18,15 @@ and poor UX[^gomuks#731]. ## Proposal -This proposal introduces two new optional fields in the `/createRoom` payload: `invite_reasons` and -`invite_reason`. +This proposal introduces a new optional field in the `/createRoom` payload: `invite_reason`. -`invite_reason` is an optional string that, if provided, should be the default `reason` included +`invite_reason` is an optional string that, if provided, should be the `reason` included in each `m.room.member` invite state event sent by the server resulting from the `invite` array. +If the field is empty or omitted, no reason is attached to the implied invites. -`invite_reasons` is an optional mapping of `{user_id: string}`. -Each key should be a user ID that is already present in `invite`. -The value of each key should be the `reason` to set in each invite dispatched as a result of -`invite`. - -Or, as a nicely presented table: - -| Key | Type | Description | -| ---------------- | ------------------- | ----------- | -| `invite_reason` | `string` (optional) | The default invite reason to include in any implied `m.room.member` events. | -| `invite_reasons` | `{string: string}` (optional) | Individual `reason`s to include in specific implied `m.room.member` events. | - -Clients SHOULD NOT include user IDs in `invite_reasons` that are not in `invite`, and SHOULD NOT -make the value of any `invite_reasons` entry the same as `invite_reason`. Servers MUST ignore any -entries which appear in `invite_reasons` that do not also appear in `invite`. - -For each user ID in `invite`: - -1. If a user ID appears in `invite_reasons`, the value provided in that mapping should be used as - the `reason` field, even if it is empty (which is akin to removing the reason for that specific - invite, in which case the server may omit the reason field entirely). -2. If `invite_reason` is provided AND not empty, that value should populate the reason field. -3. Otherwise, no reason is attached to the invite. - -An example usage of this two-field mechanism may be: - -1. A client wishes to invite everyone in a - predecessor room after an upgrade. It can include every joined member in `invite`, and provide - `{"invite_reason": "This room was replaced, please join the new one."}`. Then, every user who - receives an invite to that room knows exactly why they were invited, and is more likely to - accept. -2. A client wishes to start a DM with a user, and would like to provide them with a reason before - inviting them. In this case, if there's only one user to invite to the DM, `invite_reason` - *could* suitably be used, but the client may instead opt to omit `invite_reason` and explicitly - name the user in `invite_reasons`: `{"invite_reasons": {"@alice:example.com": "Hi Alice!"}}`. -3. A client wishes to start a group with a few users and wishes to preemptively explain why they - were invited (via `invite_reason`), but modify the reason for - certain members(via `invite_reasons`). +An example of this in practice could be a client informing a user why they are being invited to a +DM ahead of time, or a client upgrading a room attaching a "this room was upgraded" so members +aren't surprised by a random invite to a room they may not have used in a long time. Another use-case could be safety related: clients may opt to reject any invites that lack a reason, especially from users they do not share a room with or have no non-public mutual rooms with. This @@ -71,7 +36,7 @@ would also open the possibility for a future extension to [invite blocking][spec ## Potential issues -Servers which do not understand these new parameters will not be able to include reasons, which may +Servers which do not understand the new parameter will not be able to include reasons, which may result in users unexpectedly issuing invites on room creation which lack a reason. To alleviate this, clients MAY check [`GET /_matrix/clients/versions`][spec-versions] for an applicable spec version (if merged), or the unstable prefix in [Unstable prefix](#unstable-prefix), before @@ -124,7 +89,6 @@ None. | Stable | Unstable | | ------ | -------- | | `invite_reason` | `uk.timedout.msc4491.invite_reason` | -| `invite_reasons` | `uk.timedout.msc4491.invite_reasons` | `uk.timedout.msc4491.create_room_invite_reasons` SHOULD be present in the `unstable_features` of [`GET /_matrix/client/versions`][spec-versions] until this proposal is merged, to indicate From 81b4ecb53154e053c1eb9c8eb0b713c8c84ebc48 Mon Sep 17 00:00:00 2001 From: timedout Date: Wed, 24 Jun 2026 19:09:51 +0100 Subject: [PATCH 7/7] Highlight relevant potential issues from feedback --- proposals/4491-create-room-invite-reason.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/proposals/4491-create-room-invite-reason.md b/proposals/4491-create-room-invite-reason.md index 321087323d7..e73ad45fe39 100644 --- a/proposals/4491-create-room-invite-reason.md +++ b/proposals/4491-create-room-invite-reason.md @@ -36,11 +36,13 @@ would also open the possibility for a future extension to [invite blocking][spec ## Potential issues -Servers which do not understand the new parameter will not be able to include reasons, which may -result in users unexpectedly issuing invites on room creation which lack a reason. To alleviate -this, clients MAY check [`GET /_matrix/clients/versions`][spec-versions] for an applicable spec -version (if merged), or the unstable prefix in [Unstable prefix](#unstable-prefix), before -presenting the user with a UI to include an invite reason when creating a room. +`/createRoom` is already a complex endpoint, and this proposal adds yet another (albeit minor) +feature to it. A future proposal may wish to tackle the complexity of `/createRoom`. + +`invite_reason` is unencrypted - inconsiderate UI choices may result in the user believing their +invitation reasons are also encrypted (especially when creating DMs). Clients should take additional +precautions to ensure users are aware that their invitations will not be encrypted, perhaps with a +red shield in the input box, or some other familiar mechanism. ### Abuse