Skip to content
Open
Changes from 9 commits
Commits
Show all changes
140 commits
Select commit Hold shift + click to select a range
480b00a
draft for expiring event PR
toger5 May 7, 2024
8839b8d
Add msc number
toger5 May 7, 2024
8bf6db7
add security consideration and alternatives
toger5 May 7, 2024
8ec6374
alternative name and alternative content
toger5 May 8, 2024
9f45cfa
review andrewF
toger5 May 8, 2024
54fff99
draft of iteration two (after meeting with the backend team)
toger5 May 10, 2024
abdfe1c
timeout_refresh_token is not a well description since the same token …
toger5 May 13, 2024
53f6186
rename msc, rephrase introduction
toger5 May 14, 2024
087c74e
Add usecase specific section.
toger5 May 20, 2024
538b853
add GET futures endpoint
toger5 May 20, 2024
f7a1aad
shorten introduction
toger5 May 21, 2024
f5f4b38
add alternative section to not include the `m.send_now` field
toger5 May 22, 2024
c16afbc
Update proposals/4140-delayed-events-futures.md
toger5 May 31, 2024
c52c80d
batch sending considerations
toger5 May 31, 2024
bf22260
Update proposals/4140-delayed-events-futures.md
toger5 Jun 3, 2024
7f0d80f
Update proposals/4140-delayed-events-futures.md
toger5 Jun 3, 2024
7b192ac
Update proposals/4140-delayed-events-futures.md
toger5 Jun 3, 2024
f3bf66d
review
toger5 Jun 3, 2024
2d7b27d
add background to usecase specific considerations
toger5 Jun 4, 2024
1140ce9
Simplify main proposal for widget api usage
toger5 Jun 5, 2024
0a7896e
make `future_group_id` server generated and small adjustments
toger5 Jun 6, 2024
8fa33d6
review
toger5 Jun 6, 2024
49d5294
user scoping details
toger5 Jun 13, 2024
7550d9b
Update proposals/4140-delayed-events-futures.md
toger5 Jun 13, 2024
a663bb4
add rate limiting section
toger5 Jun 14, 2024
99b3a20
rename `/futures` to `/future`
toger5 Jun 19, 2024
eb50a19
Update everything to v1
toger5 Jun 20, 2024
9ff051e
Update proposals/4140-delayed-events-futures.md
toger5 Jun 22, 2024
425b9bf
review
toger5 Jun 24, 2024
2e7be46
Swap the alternative of reusing the send and state request with the m…
toger5 Jun 24, 2024
5653fe1
add reference to MSC4143 (MatrixRTC)
toger5 Jul 2, 2024
af060cf
Meeting feedback:
toger5 Jul 15, 2024
bff704c
remove parent concept.
toger5 Jul 22, 2024
fa461e2
Refactor + unstable prefixes
hughns Jul 23, 2024
d195218
Update to GET /futures
hughns Jul 23, 2024
b5ac9b2
Standardise endpoint for interacting with futures
hughns Jul 23, 2024
c07dd9b
Make it clear that events can be cancelled
hughns Jul 24, 2024
b79e4e2
Make wording more authoritative
hughns Jul 24, 2024
e125901
Remove erroneous old example
hughns Jul 24, 2024
0443cd9
Take view that clients don't act on behalf of a user
hughns Jul 24, 2024
7df0919
Use name delay_id and delayed_events
hughns Jul 24, 2024
ff95144
Clean up more references to future
hughns Jul 24, 2024
93c932a
event_type => type
hughns Jul 24, 2024
6cce3bd
Tidying
hughns Jul 24, 2024
ac2d2c5
Pagination on GET endpoint
hughns Jul 24, 2024
84e20fd
Add alternative of M_INVALID_PARAM
hughns Jul 24, 2024
ac3bd9d
change order of manage <-> get sections
toger5 Jul 25, 2024
de77a90
Obey lint rules and more renames
toger5 Jul 25, 2024
677d6f3
TOC update
hughns Jul 25, 2024
4caecd1
Additional notes on security considerations
hughns Jul 25, 2024
c482d58
Add rate limiting requirements
hughns Jul 25, 2024
9098fea
Describe MatrixRTC use case in terms of heartbeats
hughns Jul 25, 2024
7e06e85
"leave" => "hangup" for consistency
hughns Jul 25, 2024
a1b8121
Wording clarification
hughns Jul 25, 2024
3a3a5b5
Additional reference to heartbeat
hughns Jul 25, 2024
97d4141
TOC update (use monospace and dont list msc title
toger5 Jul 25, 2024
99c9467
be consistent with "restart" wording (we used reset, refresh and rest…
toger5 Jul 25, 2024
6f2aa5e
Add compatibility with Cryptographic Identities to potenail issues
hughns Jul 25, 2024
5e43db0
Add placeholder for MSC3277
hughns Jul 25, 2024
84b8dc0
Add note about MSC2716 batch sending
hughns Jul 25, 2024
e7d0986
Add alternative of not providing a send action
hughns Jul 25, 2024
850bf9e
Add alternative of using DELETE HTTP method
hughns Jul 25, 2024
e1b460a
Add clarification about regular/non-state events not getting cancelled
hughns Jul 25, 2024
114da1e
Clarify contents of content field on GET response
hughns Jul 25, 2024
772590f
Expose transaction ID in GET response
hughns Jul 25, 2024
4a2ca48
Add context on MSC3277 alternative
hughns Jul 25, 2024
37979cd
Add more context to MQTT style alternative
hughns Jul 25, 2024
195ab6a
Add alternative of typing notifications
hughns Jul 25, 2024
0f8a2d1
Update TOC
hughns Jul 26, 2024
886f378
Fix TOC
hughns Aug 2, 2024
8523ed4
Note on alternative names for `running_since`
hughns Aug 2, 2024
883e6b5
Revert "Expose transaction ID in GET response"
AndrewFerr Sep 11, 2024
caece4d
Scope GET to only the requesting user's events
AndrewFerr Sep 11, 2024
28970ec
remove all references to future (except where we actually talk about …
toger5 Oct 1, 2024
2f57b0b
add terminated events section to GET endpoint
toger5 Oct 1, 2024
9d5c93a
use case specific considerations details
toger5 Oct 1, 2024
da3d75e
remove batch sending endpoint since that does not make sense in the s…
toger5 Oct 2, 2024
f7e4e9b
Clean up iteration with two significant changes:
toger5 Oct 4, 2024
b8e317f
terminated -> finalised
toger5 Oct 4, 2024
b499995
andrews changes
toger5 Oct 4, 2024
8dc05a4
requried -> required
AndrewFerr Oct 7, 2024
0a777f4
Change example of "canceled" outcome to "cancel"
AndrewFerr Oct 8, 2024
a09a883
Minor formatting changes
AndrewFerr Oct 9, 2024
72a808e
Allow servers to discard returned finalised events
AndrewFerr Oct 22, 2024
d1a37f0
Define finalised events for /sync & /transactions
AndrewFerr Oct 22, 2024
3358138
use current MatrixRTC MSC types.
toger5 Jun 27, 2025
904e3d6
review trivial changes
toger5 Jul 9, 2025
c25e5a3
Add rate limit section to `GET` `finalized` and `scheduled` endpoint
toger5 Jul 10, 2025
96f9063
clarify why there is no rate limiting for the delayed event managemen…
toger5 Jul 10, 2025
99b79ba
remove `on push` delayed event finilization section
toger5 Jul 10, 2025
dc5fd93
formatting/cleanup
toger5 Jul 10, 2025
bf5df9b
fix synapse and MSC inconsistency
toger5 Jul 10, 2025
2f53661
Constrain delay value to >0ms
AndrewFerr Sep 24, 2025
fd94c18
Editorial fixes
tulir Sep 26, 2025
e532e93
Remove optional wording of limits
tulir Sep 26, 2025
0d7821a
Add syncing failed delayed events to alternatives
tulir Sep 26, 2025
64f6dfc
Define retrying and responses for managing delayed events
tulir Sep 26, 2025
ba78cde
Specify delay_id grammar
AndrewFerr Oct 1, 2025
c2587fe
Specify missing HTTP response codes
AndrewFerr Oct 1, 2025
1036165
Explain `outcome` and `reason` on finalised events
AndrewFerr Oct 1, 2025
2afcebb
Merge GET endpoints to avoid namespace clash
AndrewFerr Oct 1, 2025
8c5b612
Add send time as alternative to `running_since`
AndrewFerr Oct 1, 2025
6afd0f3
Use opaque identifier grammar for delay_id
AndrewFerr Oct 1, 2025
1359f2b
Merge /scheduled and /finalised as query params
AndrewFerr Oct 1, 2025
59392f2
Allow GET endpoint to filter on ID
AndrewFerr Oct 1, 2025
f167eb4
Tweak wording/spelling/format
AndrewFerr Oct 1, 2025
3efecea
Move delayed state event cancelling to alternative
AndrewFerr Oct 1, 2025
4bb8929
Move M_CANCELLED_BY_STATE_UPDATE out of main spec
AndrewFerr Oct 1, 2025
24d1c7d
Tweak spelling/format
AndrewFerr Oct 1, 2025
2f4a73a
Remove rendundant & stale table of contents
AndrewFerr Oct 1, 2025
33af637
Tweak formatting & wording of `status` section
AndrewFerr Oct 2, 2025
76d015a
Don't return 400 for multiple `status` parameters
AndrewFerr Oct 2, 2025
cdcddd2
Reduce emphasis on state events
AndrewFerr Oct 2, 2025
d6c065e
Fix swapped links to /send and /state
AndrewFerr Oct 3, 2025
0a18141
Update all spec links to v1.16
AndrewFerr Oct 3, 2025
a53f83f
Fix typo
AndrewFerr Oct 10, 2025
eb0a504
Always require origin_server_ts on finalised evts
AndrewFerr Oct 10, 2025
9db8058
Limit cached finalised events per user, not global
AndrewFerr Oct 10, 2025
c19a394
Apply batch size to not just finalised events
AndrewFerr Oct 10, 2025
e2af939
Note deviations in Synapse implementation
AndrewFerr Oct 20, 2025
061dd56
Reintroduce unauthed management for delegation
AndrewFerr Oct 27, 2025
79aeaf5
Mention single-URL management as an alternative
AndrewFerr Oct 27, 2025
3ee73ab
Use correct version for event sending endpoints
AndrewFerr Dec 15, 2025
1618990
Use dedicated endpoint for delayed event creation
AndrewFerr Jan 5, 2026
b045f1d
Update all spec links to v1.17
AndrewFerr Jan 5, 2026
8a98ec4
Specify unstable version of new endpoint path
AndrewFerr Jan 5, 2026
18e0e6b
Prefer M_RESOURCE_LIMIT_EXCEEDED over a new error
AndrewFerr Jan 6, 2026
673c2f3
Fix typo
AndrewFerr Jan 6, 2026
49b200d
Add "`delay_id` in `unsigned` sync field" section
toger5 Feb 17, 2026
3ef314f
Do not use M_RESOURCE_LIMIT_EXCEEDED
toger5 Feb 17, 2026
95045cf
update error code prefix
toger5 Feb 17, 2026
642b09e
M_LIMIT_EXCEEDED -> M_USER_LIMIT_EXCEEDED
toger5 Feb 17, 2026
1e527c3
Update proposals/4140-delayed-events-futures.md
toger5 Mar 3, 2026
a790e5a
Use unstable prefix for unsigned data key
AndrewFerr Mar 5, 2026
0864869
Tweak when to include `delay_id` in `unsigned`
AndrewFerr Mar 5, 2026
bad3419
Revert "M_LIMIT_EXCEEDED -> M_USER_LIMIT_EXCEEDED"
AndrewFerr Mar 6, 2026
9dfc5d9
Restore "in the proposal stage" -> "unstable"
AndrewFerr Mar 6, 2026
30426d6
Put delay_id in unsigned for all event renderings
AndrewFerr Mar 9, 2026
1cb8134
Mention security requirement for `delay_id`
AndrewFerr Mar 13, 2026
d4172b2
Use M_LIMIT_EXCEEDED with 429 and Retry-After
AndrewFerr Mar 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
266 changes: 266 additions & 0 deletions proposals/4140-delayed-events-futures.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
# MSC4140: Delayed events (Futures)

In the context of matrixRTC where we want
to ignore expired state events of users who left the call without sending a new
state empty `m.call.member` event.

We would like the homeserver to mark this event as expired/send an expired version
in a reasonable time window after a user disconnected.

Currently there is no mechanism for a client to provide a reliable way of
communicating that an event is still valid.
The only way to update an event is to post a new one.
In some situations the client just looses connection or fails to sent the expired
Comment thread
toger5 marked this conversation as resolved.
Outdated
version of the event.

A generic way in which one can automate expirations is desired.

The described usecase is solved if we allow to send an event in advance
to the homeserver but let the homeserver compute when its actually added to the
DAG.
The condition for actually sending the delayed event would could be a timeout.

## Proposal

To make this as generic as possible, the proposed solution is to allow sending
multiple presigned events and delegate the control of when to actually send these
events to an external services. This allows to exactly define what expiration means,
since any event that will be sent once expired can be defined.

We call those events `Futures`.

A new endpoint is introduced:
`PUT /_matrix/client/v3/rooms/{roomId}/send/future/{txnId}`
Comment thread
toger5 marked this conversation as resolved.
Outdated
It behaves exactly like the normal send endpoint except that that it allows
to send a list of event contents. The body looks as following:
Comment thread
toger5 marked this conversation as resolved.
Outdated

```json
{
"m.timeout": 10,
"m.send_on_timeout": {
"content": sendEventBody0,
"type": "m.room.message",
},

"m.send_on_action:${actionName}": {
"content": sendEventBody1,
"type": "m.room.message"
},

// optional
"m.send_now": {
"content": sendEventBody2,
"type": "m.room.message"
},
}
```
Comment thread
toger5 marked this conversation as resolved.
Outdated

Each of the `sendEventBody` objects are exactly the same as sending a normal
event.

There can be an arbitrary amount of `actionName`s.
Comment thread
toger5 marked this conversation as resolved.
Outdated
Comment thread
toger5 marked this conversation as resolved.
Outdated

All of the fields are optional except the `timeout` and the `send_on_timeout`.
This guarantees that all tokens will expire eventually.
Comment thread
toger5 marked this conversation as resolved.
Outdated

The homeserver can set a limit to the timeout and return an error if the limit
is exceeded.

### Response

The response will mimic the request:

```json
Comment thread
toger5 marked this conversation as resolved.
Outdated
Comment thread
toger5 marked this conversation as resolved.
Outdated
{
"m.send_on_timeout": {
"eventId": "id_hash"
},
"m.send_on_action:${actionName}": {
"eventId": "id_hash"
},

"future_token": "token",

// optional
"m.send_now": { "eventId": "id_hash" }
}
```

### Delegating futures

The `token` can be used to call another future related endpoint:
`PUT /_matrix/client/v3/futures/refresh` and `PUT /_matrix/client/v3/futures/action/${actionName}`.
Comment thread
toger5 marked this conversation as resolved.
Outdated
Comment thread
toger5 marked this conversation as resolved.
Outdated
where the body is:

```json
{
"future_token": "token"
}
```

The information required to call this endpoint is very limited so that almost
no metadata is leaked. This allows to share a refresh link to a different
service. This allows to delegate the send time. An SFU for instance, that tracks the current client connection state,
and pings the HS to refresh and call a dedicated action to communicate
that the user has intentionally left the conference.

The homeserver does the following when receiving a Future.

- It **sends** the optional `m.send_now` event.
- It **generates** a `future_token` and stores it alongside with the time
of retrieval, the event list and the timeout duration.
- **Starts a timer** for the stored `future_token`.

- If a `PUT /_matrix/client/v3/futures/refresh` is received, it
**restarts the timer** with the stored timeout duration.
- If a `PUT /_matrix/client/v3/futures/action/${actionName}` is received, it **sends the associated action event**
`m.action:${actionName}`.
Comment thread
toger5 marked this conversation as resolved.
Outdated
- If the timer times out, **it sends the timeout event** `m.send_timeout`.
- If the future is a state event and includes a `m.send_now` event
the future is only valid while the `m.send_now`
is still the current state:
Comment thread
AndrewFerr marked this conversation as resolved.
Outdated

- This means, if the homeserver receives
a new state event for the same state key, the **`future_token`**
**gets invalidated and the associated timer is stopped**.

- There is no race condition here since a possible race between timeout and
new event will always converge to the new event:
- Timeout -> new event: the room state will be updated twice. once by
the content of the `m.send_on_timeout` event but later with the new event.
- new event -> timeout: the new event will invalidate the future. No
Comment thread
toger5 marked this conversation as resolved.
Outdated

- After the homeservers sends a timeout or action future event, the associated
timer and `future_token` is canceled/invalidated.

So for each Future the client sends, the homeserver will send one event
conditionally at an unknown time that can trigger logic on the client.
This allows for any generic timeout logic.

Timed messages/reminders or ephemeral events could be implemented using this where
clients send a redact as a future or a room event with intentional mentions.

In some scenarios it is important to allow to send an event with an associated
future at the same time.

- One example would be redacting an event. It only makes sense to redact the event
if it exists.
It might be important to have the guarantee, that the redact is received
by the server at the time where the original message is sent.
- In the case of a state event we might want to set the state to `A` and after a
timeout reset it to `{}`. If we have two separate request sending `A` could work
but the event with content `{}` could fail. The state would not automatically
reset to `{}`.

For this usecase an optional `m.send_now` field can be added to the body.
Comment thread
hughns marked this conversation as resolved.
Outdated

## Usecase specific considerations

### MatrixRTC

We want can use the actions and the timeout for matrix rtc for the following situations

- If the client takes care of its membership, we use a short timeout value (around 5-20 seconds)
The client will have to ping the refresh endpoint approx every 2-19 seconds.
- When the SFU is capable of taking care of managing our connection state and we trust the SFU to
not disconnect a really long value can be chosen (approx. 2-10hours). The SFU will then only send
an action once the user disconnects or looses connection (it could even be a different action for both cases
handling them differently on the client)
This significantly reduces the amount of calls for the `/future` endpoint since the sfu only needs to ping
once per session (per user) and every 2-5hours (instead of every `X` seconds.)

### Self destructing messages
Comment thread
toger5 marked this conversation as resolved.
Outdated

This MSC also allows to implement self destructing messages:
Comment thread
toger5 marked this conversation as resolved.
Outdated
Comment thread
toger5 marked this conversation as resolved.
Outdated

`PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}`

```json
{
"m.text": "my msg"
}
```

`PUT /_matrix/client/v3/rooms/{roomId}/send/future/{txnId}`

```json
{
"m.timeout": 10*60,
"m.send_on_timeout": {
"type":"m.room.readact",
"content":{
"redacts": "EvId"
}
}
}
```

## EventId template variable
Comment thread
toger5 marked this conversation as resolved.
Outdated

It would be useful to be able to send redactions and edits as one http request.
Comment thread
toger5 marked this conversation as resolved.
Outdated
This would make sure that the client cannot loose connection after sending the first event.
Comment thread
toger5 marked this conversation as resolved.
Outdated
For instance sending a self destructing message without the redaction.
Comment thread
toger5 marked this conversation as resolved.
Outdated

The optional proposal is to introduce template variables that are only valid in `Future` events.
`$m.send_now.event_id` in the content of one of the `m.send_on_action:${actionName}` and
`m.send_on_timeout` contents this template variable can be used.
The **Self destructing messages** example would simplify to:
Comment thread
toger5 marked this conversation as resolved.
Outdated

`PUT /_matrix/client/v3/rooms/{roomId}/send/future/{txnId}`

```json
{
"m.send_now":{
"type":"m.room.message",
"content":{
"m.text": "my msg"
}
},
"m.timeout": 10*60,
"m.send_on_timeout": {
"type":"m.room.readact",
"content":{
"redacts": "$m.send_now.event_id"
}
}
}
```

## Potential issues

## Alternatives

[MSC4018](https://github.com/matrix-org/matrix-spec-proposals/pull/4018) also
proposes a way to make call memberships reliable. It uses the client sync loop as
an indicator to determine if the event is expired. Instead of letting the SFU
inform about the call termination or using the call app ping loop like we propose
here.

---

The following names for the endpoint are considered

- Future
- DelayedEvents
- RetardedEvents
Comment thread
toger5 marked this conversation as resolved.
Outdated
Comment thread
toger5 marked this conversation as resolved.
Outdated

## Security considerations

We are using an unauthenticated endpoint to refresh the expirations. Since we use
the token it is hard to guess a correct request and force one of the actions
events of the Future.

It is an intentional decision to not provide an endpoint like
`PUT /_matrix/client/v3/futures/room/{roomId}/event/{eventId}`
where any client with access to the room could also `end` or `refresh`
the expiration. With the token the client sending the event has ownership
over the expiration and only intentional delegation of that ownership
(sharing the token) is possible.

On the other hand the token makes sure that the instance gets as little
information about the matrix metadata of the associated `future` event. It cannot
Comment thread
toger5 marked this conversation as resolved.
Outdated
even tell with which room or user it is interacting.

## Unstable prefix

## Dependencies