[JMAP] Re: Status of JMAP Metadata, RefPlus and Mail Sharing drafts ahead of IETF 125

Neil Jenkins <neilj@fastmailteam.com> Wed, 15 April 2026 04:51 UTC

Return-Path: <neilj@fastmailteam.com>
X-Original-To: jmap@mail2.ietf.org
Delivered-To: jmap@mail2.ietf.org
Received: from localhost (localhost [127.0.0.1]) by mail2.ietf.org (Postfix) with ESMTP id EF79BDC7CB91 for <jmap@mail2.ietf.org>; Tue, 14 Apr 2026 21:51:58 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ietf.org; s=ietf1; t=1776228719; bh=Bq/imW38zXkssyUXthUsc+6VNEP7/BjHQZylNXTRmb8=; h=Date:From:To:In-Reply-To:References:Subject; b=MarAKXo8Irx1UKk0GsMOIy5bXtPFemjJBGL+BM5hvc+cr9f3QVRl+p7QA+sWbVYzg CWYbq4sEgGBYlAMaizpWi2ovH9N3qHS3WKCMxgZafsvOVL4T4buQ4dq/fqtZ93ehFq A6X0s+eDeLQdF+z2EGzvXcOXkHYnpwmg9woRjN2c=
X-Virus-Scanned: amavisd-new at ietf.org
X-Spam-Flag: NO
X-Spam-Score: -2.797
X-Spam-Level:
X-Spam-Status: No, score=-2.797 tagged_above=-999 required=5 tests=[BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, HTML_MESSAGE=0.001, RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H2=0.001, RCVD_IN_VALIDITY_CERTIFIED_BLOCKED=0.001, RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001, SPF_PASS=-0.001] autolearn=ham autolearn_force=no
Authentication-Results: mail2.ietf.org (amavisd-new); dkim=pass (2048-bit key) header.d=fastmailteam.com header.b="KL2Z56OI"; dkim=pass (2048-bit key) header.d=messagingengine.com header.b="I+ZOz+qT"
Received: from mail2.ietf.org ([166.84.6.31]) by localhost (mail2.ietf.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 6KDs86MSLT4F for <jmap@mail2.ietf.org>; Tue, 14 Apr 2026 21:51:57 -0700 (PDT)
Received: from fout-a3-smtp.messagingengine.com (fout-a3-smtp.messagingengine.com [103.168.172.146]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-256) server-digest SHA256) (No client certificate requested) by mail2.ietf.org (Postfix) with ESMTPS id AFF43DC7CB11 for <jmap@ietf.org>; Tue, 14 Apr 2026 21:51:52 -0700 (PDT)
Received: from phl-compute-10.internal (phl-compute-10.internal [10.202.2.50]) by mailfout.phl.internal (Postfix) with ESMTP id 64379EC00C4 for <jmap@ietf.org>; Wed, 15 Apr 2026 00:51:45 -0400 (EDT)
Received: from phl-imap-15 ([10.202.2.104]) by phl-compute-10.internal (MEProxy); Wed, 15 Apr 2026 00:51:45 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= fastmailteam.com; h=cc:content-type:content-type:date:date:from :from:in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to; s=fm3; t=1776228705; x= 1776315105; bh=x+QZcNofofb66WY8Pnjv7agb72lQx9N37HeBA5Wh6AI=; b=K L2Z56OIPUZRwWaUQJPLVNGM6Y9p3zx1CuN9NGHl3T027mofW8vAfkTKA6cWh9uhq MG0omBlz6N0A2a9wQwdmo7wr6UPXsThSSj0f2ihBVeAY+ljjOIClyh2hkXVm+qXU g3IJgV9agbPu48VOnlCt1ROXqZNNrMOEA1JYCSxuxg453RHN/knY4fDFgizEesn9 /SI7xD18ojQfKrVLT6cOUxi4pvueKPq0TiLL4pyp/eicOsC1sy8GByV5BCA5dmsN EqgHuqc1rptp1x/PibpesqqaMPiUyh4eJ+Pk0FVj+lr3stDJH+vEUvJX5S2tJkwg 3Xd+ivFnsdCrxFhLCEoIw==
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm2; t= 1776228705; x=1776315105; bh=x+QZcNofofb66WY8Pnjv7agb72lQx9N37He BA5Wh6AI=; b=I+ZOz+qTROoa18tvrBIK0eGH4XnQxkNJDMAuTrtfTBsYTwI3A3Q PEP9ab/CA1LhxtBSk3Uj2HvdIG4V1EU0XceN0WVWpt7T2gLW7gOZnFkF6vQMqgSj o7Q7yuRVtVlJyKA25z/oJUlebdw8Vlqe6YS6u15t+BYrH6snUiAs1MZPIRC6hnuv oy/3gEnvD04ykkMFzmWbi+YFLzraPJuGlbA6rZtBBtqxi3md0CDCo9hKkGBYhlL0 MRhYhMTX0Gz8PY3C/GWunnsCcslDJmltAcAqIzF9mhVpIaLH94mkujEsGCtf83VY YswOnvFfh25tfPlWqLeyaKapnI3NIMHgbfw==
X-ME-Sender: <xms:YRnfaUoBPK0wshD-UnvDx2MRvFe6yfiDZgZuabuii8OwkrwPPMZsLg> <xme:YRnfaVfce-Nlcpa432q-jbrKKQzsXCbq2PPzVvtAekroDBkslh9mqUlK_AbXlHMUO __g2F95U0WynIS3nIM1oBnD4BWSHf9VWm5a8O2ml1gM_wNhmw>
X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefhedrtddtgdegfeduhecutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpuffrtefokffrpgfnqfghnecuuegr ihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmdenucfjug hrpefoggffhffvkfgjfhfutgesrgdtreerredtjeenucfhrhhomhepfdfpvghilhculfgv nhhkihhnshdfuceonhgvihhljhesfhgrshhtmhgrihhlthgvrghmrdgtohhmqeenucggtf frrghtthgvrhhnpeeijeevjedtgeejfeeluddtheekveeihffgueduteevueekgeetudeg ueffleeghfenucffohhmrghinhepihgvthhfrdhorhhgpdhfrghsthhmrghilhdrtghomh dprhhftgdqvgguihhtohhrrdhorhhgnecuvehluhhsthgvrhfuihiivgeptdenucfrrghr rghmpehmrghilhhfrhhomhepnhgvihhljhesfhgrshhtmhgrihhlthgvrghmrdgtohhmpd hnsggprhgtphhtthhopedupdhmohguvgepshhmthhpohhuthdprhgtphhtthhopehjmhgr phesihgvthhfrdhorhhg
X-ME-Proxy: <xmx:YRnfaaPrmKt1-v1IQCDzprtI3Lf6QQjUf9X7-ZS-oWDL9WE2ek2HXw> <xmx:YRnfaf7atK4gtMYuQqgVtat3S-65nsDC1XGQDI8jFme2sQHiXQYOLA> <xmx:YRnfaW7dC4KHNzUkoRlaE8pAtcJWS1zVT6eut1w6nfS-gbhw9d2KFw> <xmx:YRnfaf2c2_UjK9juXoFa4GJqaGampap7vr1Y-iLERxmQUwRizYNCzA> <xmx:YRnfadGmqot1eOMYyC3G3Z4VvgSMCR8NKjlbwt5G4pz65grwkfmpCwDJ>
Feedback-ID: ibc614277:Fastmail
Received: by mailuser.phl.internal (Postfix, from userid 501) id 1604A780070; Wed, 15 Apr 2026 00:51:45 -0400 (EDT)
X-Mailer: MessagingEngine.com Webmail Interface
MIME-Version: 1.0
Date: Wed, 15 Apr 2026 14:50:45 +1000
From: Neil Jenkins <neilj@fastmailteam.com>
To: JMAP IETF <jmap@ietf.org>
Message-Id: <9e8b403c-e0d3-4af6-ac47-c1af06f27e1e@dogfoodapp.fastmail.com>
In-Reply-To: <8397988B-24C5-42D7-BD1E-45CEDD009AC5@stalw.art>
References: <8397988B-24C5-42D7-BD1E-45CEDD009AC5@stalw.art>
Content-Type: multipart/alternative; boundary="6a26f77ea7d22c06368dfcfd8932afc2156f1737"
Message-ID-Hash: WVSUFW7X6ITGQBUKWROVM5XINS5XQGRZ
X-Message-ID-Hash: WVSUFW7X6ITGQBUKWROVM5XINS5XQGRZ
X-MailFrom: neilj@fastmailteam.com
X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-jmap.ietf.org-0; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header
X-Mailman-Version: 3.3.9rc6
Precedence: list
Subject: [JMAP] Re: Status of JMAP Metadata, RefPlus and Mail Sharing drafts ahead of IETF 125
List-Id: JSON Meta Access Protocol <jmap.ietf.org>
Archived-At: <https://mailarchive.ietf.org/arch/msg/jmap/jKJw9Zk3-82Sid3Qzld7Jebfd6I>
List-Archive: <https://mailarchive.ietf.org/arch/browse/jmap>
List-Help: <mailto:jmap-request@ietf.org?subject=help>
List-Owner: <mailto:jmap-owner@ietf.org>
List-Post: <mailto:jmap@ietf.org>
List-Subscribe: <mailto:jmap-join@ietf.org>
List-Unsubscribe: <mailto:jmap-leave@ietf.org>

I've read through all of these again.

> https://datatracker.ietf.org/doc/draft-ietf-jmap-mail-sharing/

This looks great, I think it should go to WG last call and be submitted for publication.

> https://datatracker.ietf.org/doc/draft-ietf-jmap-refplus/

My overall view of this is still that the added complexity (and execution performance concerns) of this outweighs the performance benefits of reducing a few extra round trips. I'm not objecting to the document moving forward, but I'd love to see some evidence that someone has a real use case for it. Regardless, some more specific concerns:

**§1.2.1. The value of this property in the JMAP Session "accountCapabilities" property is an empty object.**

I don't think the capability should be included in `accountCapabilities`, just the top-level `capabilities`. These changes are not things within a particular account; they apply at a higher level (like JMAP Core). (Caveat, see comment on /set below.)
*
*
**§2.2.1 The nodelist produced by JSON Path evaluation MUST be resolved according to the expected type of the target property**

I *really* don't like this. Instead of being a pure syntactical transformation, the dispatching server must now understand the full expected structure of the method being called in order to apply the result reference. This is a layering violation, and could be impossible for a generic JMAP proxy that doesn't understand (or need to understand) the actual method calls it's dispatching to JMAP servers downstream.

(Yes, I saw later in §3.1 you say *"The type-specific resolution rules defined in this specification (determining whether a resolved value should be treated as a single value, an array, or a map) are applied by the JMAP method implementation layer, not by the reference resolution layer."* — that's still requiring support at multiple layers.)

Instead, why not add an extra `type` property to the ResultReference object that gives the expected type? The caller should know what they expect this to resolve to, and that means the reference resolver doesn't need to know. This lets everything be done at the Syntactic Resolution Phase (per §3.2).

**§2.3 Usage in /set Methods**

I think we need to explicitly state that should you opt in to the refplus capability you *cannot *set a property within a Foo object that beings with a literal `#`. This is not a problem with any of the data types being used with JMAP so far (as far as I'm aware), but worth noting for the future. (If for some reason a client *does* need to do this in the future, they must not include the refplus capability in the `using` array of the Request object; as per RFC8620, if not explicitly opted in, the server must not apply the capability.)

**§2.3**** If a result reference fails to resolve, the server MUST reject the creation or update of that specific object with a SetError of type "invalidResultReference"**

This is problematic, because it means the resolution has to happen at method execution time, rather than at method dispatch time. I don't think it's necessary: we can resolve the references before dispatch and reject the whole method with `invalidResultReference` if any fail. That's consistent with all the other result reference resolution, and means it can be handled in the same layer.

Otherwise, if we really want to keep the currently specified behaviour, I think we will need to add the refplus capability to the `accountCapabilities` of accounts that support using ResultReferences inside `/set` create/update. Again, in the proxy case it may not be all of them.

> https://datatracker.ietf.org/doc/draft-ietf-jmap-metadata/

I think this is looking fairly solid. A few comments:

**§2.1.2 Servers SHOULD document whether they provide read-only or read-write access to ImapMetadata.**
**§2.1.3 **Servers SHOULD document whether they provide read-only or read-write access to WebDavMetadata.****

Why not put this in the capabilities?

**§***3.1 "alreadyExists" SetError […] MAY include an "existingId" property containing the id of the existing Metadata object.*

I'd change this MAY to a MUST — it's easy to implement, and if it's only a MAY a client can't rely on it, which is a pain. (As a general rule, 

**§4.1 The response to an extended /get method includes the following additional property**

I think we probably need to include `metadataState` in the response as well?
**
**
**§4.2.1 Servers SHOULD NOT generate […] state changes for automatically deleted metadata. The deletion is considered an implicit consequence of destroying the related object. However, servers MAY emit push notifications or update metadata state strings to reflect that metadata has been deleted, if such notifications would otherwise occur for explicitly deleted metadata.**

I'm not quite sure what you're going for here. Surely the metadata state *has* to change if a metadata object is destroyed, regardless of how this happened. Otherwise, the `/changes` updates won't work correctly.

Related, the document doesn't currently specify that changes purely to a Metadata object SHOULD NOT change the state string for the related type, but I think that's the idea. Although…

*Is this the right approach?*

With my client implementer hat on, I was thinking about how I would want to use metadata. Generally, I'd want to be able to say "I'm interested in metadata for this particular type" and then fetch/update it with the instances of that type themselves. I can't think of a circumstance where I'd want metadata without the related object, but I could definitely just have a small subset of the dataset for a particular object (e.g. I have loaded 100 emails for a user with 1,000,000 messages). The current approach is a bit painful:
 • I can fetch the metadata with the Email objects by using the *fetchMetadata* argument. Fine.
 • The Metadata state changes. I call `Metadata/changes` with *filterRelatedType* set to `"Email"`. I can ignore any *updated* metadata objects I don't have, but I have to always fetch the *created* metadata objects, as I have no way of knowing if they're related to the Email objects I currently have in memory or not.
 • I can't search for a Foo object that has some particular metadata AND a regular property (e.g. if I used metadata to store a memo <https://www.fastmail.com/features/memos/> on an Email, and wanted to search for all emails that are in the inbox and have a memo with the text "foo").
A very different approach would be:
 • Data types with metadata support (as per the capabilities) get a mutable `_metadata_: String[*]` property if you opt in to the capability. We could have a `_private_metadata_` property too for private metadata, if supported (per the capability). 
 • The value of this property is a (possibly empty) object (never null so you can always patch). The keys at the top level are registered, or a domain name:
   • `imap`: maps to IMAP metadata
   • `webdav`: maps to WebDAV metadata
   • `example.com`: maps to arbitrary vendor data.
 • The metadata property can be specified in the *properties* argument to `Foo/get`, like any other property. If you don't specify any properties, it will be returned with the default set of properties (normally everything) for that data type. (Or maybe we'd need a way to say "give me all the default properties, but *not* the metadata properties for this object type"?)
 • `Foo/changes` returns objects where the `_metadata_` property has changed in the *updated* argument of the response. We add an *updatedProperties* argument to the response, like in Mailbox/changes <https://www.rfc-editor.org/rfc/rfc8621.html#section-2.2>. If only metadata has changed, the server should use this to indicate that, so you can then only fetch the metadata property. Possibly we add an extra argument to `Foo/changes` to ignore metadata-only changes for data types where you're not interested in the metadata.
 • We define some extensions to the `Foo/query` FilterCondition for any data type that supports metadata:
   • the presence of metadata at a particular path, e.g., `{"metadataExists": "fastmail.com/memo"}` — this matches any object where it has a value defined at that path.
   • text match at a particular path. e.g., `{"metadataText": { path: "fastmail.com/memo", value: "foo"}` — looks for a string in the property at a particular path. Is false if the path doesn't exist or isn't a string type.
 • That's … kind of it? You can use standard `Foo/set` patching to update specific metadata properties without stomping on other vendors’ metadata. You always keep your metadata in sync with the underlying objects. You don't really need to change any code in the client, and all existing sync code will just work.
Thoughts? This seems both simpler and more functional to me, and I don't see any obvious drawbacks at first glance.

Cheers,
Neil.