Blob/upload creation ids cannot be back-referenced in Email/import (RFC 9404, 4.1)

Issue Description

RFC 9404, Section 4.1 says: “For each successful upload, servers MUST add an entry to the createdIds map … the value must be available to later methods defined in the same Request Object.” On v0.16.23 a creation id from Blob/upload cannot be used as #id in Email/import within the same request.

Expected Behavior

“blobId”: “#u0” in Email/import resolves to the blob created as u0 by the preceding Blob/upload call in the same request.

Actual Behavior

Blob/upload succeeds, Email/import returns:
notCreated: {“m0”: {“type”: “invalidProperties”, “description”: “Invalid blob id.”, “properties”: [“blobId”]}}
Sending the real blob id in a second request works.

Reproduction Steps

  1. Authenticate as a normal user with a mail account.
  2. Send this request (replace the account id and inbox id):
{"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:blob"],
 "methodCalls": [
  ["Blob/upload", {"accountId": "d", "create": {"u0": {"data": [{"data:asText": "From: [email protected]\r\nSubject: t\r\n\r\nx\r\n"}], "type": "message/rfc822"}}}, "u"],
  ["Email/import", {"accountId": "d", "emails": {"m0": {"blobId": "#u0", "mailboxIds": {"<inbox id>": true}}}}, "i"]
 ]}
  1. Email/import reports “Invalid blob id.”

Stalwart Version

v0.16.x

Installation Method

Binary (Linux)

Database Backend

RocksDB

Blob Storage

RocksDB

Search Engine

Internal

Directory Backend

Internal

Additional Context

Found while bulk-importing test messages over JMAP. Workaround: upload in one request, import in a second one.

Found and reproduced with the help of an AI assistant (Claude by Anthropic) while evaluating Stalwart; I reviewed this report before submitting.

I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.

on

I have searched this support forum (open and closed topics) and confirm this is not a duplicate.

on

I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.

on

Your reproduction lines up with what’s in the source. Email/import’s blobId field is deserialized straight into a plain BlobId (via MaybeInvalid<BlobId>), and there is no "#blobId" handler for it in the request parser. Compare that with mailboxIds, which explicitly supports both a literal form and a "#mailboxIds" creation-id/result-reference form (MaybeResultReference + a dedicated #mailboxIds key). Since blobId has no equivalent #blobId key, a value like "#u0" is parsed as a literal blob id string, fails to parse as a real BlobId, and is rejected with “Invalid blob id.” rather than being resolved against created_ids (which is where Blob/upload’s creation ids are actually stored, per update_created_ids in the upload/import method code).

So this does look like a genuine gap versus the RFC 9404 requirement that upload creation ids be usable by later methods in the same request, at least for the blobId argument of Email/import. Your workaround (splitting into two requests) is correct for now.

I can’t find anything in the docs or code indicating this is intentional, so this looks like a real bug rather than expected behavior; a human maintainer will need to confirm and schedule a fix.

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.

This has been fixed in v0.16.24 (yet to be released).

Thanks for the quick fix.

I’ll test it on our test instance after getting the update 0.16.24.