Skip to content

feat: jmap support - part 1#12711

Open
SebastianKrupinski wants to merge 2 commits intomainfrom
feat/jmap-1
Open

feat: jmap support - part 1#12711
SebastianKrupinski wants to merge 2 commits intomainfrom
feat/jmap-1

Conversation

@SebastianKrupinski
Copy link
Copy Markdown
Contributor

@SebastianKrupinski SebastianKrupinski commented Apr 12, 2026

Summary

  • Resolves: JMAP Protocol Abstraction and Routing Infrastructure #12609
  • Added php-jmap-client dependency
  • Modified test workflow to start additional mail server with jmap support
  • Added jmap specific console command to create account (mail:account:create-jmap)
  • Modified existing imap console command to create account (mail:account:create) with alias to old command (mail:account:create)
  • Added unified console test command (mail:account:test) with alias to old (mail:account:diagnose)
  • Added a protocol factory to create the correct client and connectors
  • Added database migrations

@SebastianKrupinski SebastianKrupinski self-assigned this Apr 12, 2026
@github-project-automation github-project-automation Bot moved this to 🏗️ In progress in 💌 📅 👥 Groupware team Apr 12, 2026
@SebastianKrupinski SebastianKrupinski force-pushed the feat/jmap-1 branch 2 times, most recently from e01d756 to 234e641 Compare April 12, 2026 23:21
@SebastianKrupinski SebastianKrupinski force-pushed the feat/jmap-1 branch 4 times, most recently from 961bbd3 to fc4e6c8 Compare April 21, 2026 12:38
@SebastianKrupinski SebastianKrupinski changed the title feat: jmap support - initial feat: jmap support - part 1 Apr 21, 2026
@SebastianKrupinski SebastianKrupinski force-pushed the feat/jmap-1 branch 9 times, most recently from 8d19aa3 to db410bf Compare April 21, 2026 21:32
@SebastianKrupinski SebastianKrupinski marked this pull request as ready for review April 21, 2026 21:33
Signed-off-by: SebastianKrupinski <[email protected]>
Comment thread lib/Command/CreateImapAccount.php Outdated
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2014-2016 owncloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
* SPDX-License-Identifier: AGPL-3.0-or-later
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert. I don't think we can do this retroactively

Copy link
Copy Markdown
Contributor Author

@SebastianKrupinski SebastianKrupinski Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's odd it says I already changed this, 2 days ago

image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or do you mean it should be "AGPL-3.0-only"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To change the license of a file we need the permissions from all contributors, hence it might be easier to just stay with AGPL-3.0-only.

That might be Christoph, Joas, Richard, Anna, me, Andy, Hamza according to the commit history. I cannot guarantee the corrects of that information.

I'm okay with changing the license ;)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! I see what you mean. Okay, I can change is back if its a big deal but it seems that all the contributors are NC employees, so idk

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it back to avoid issues

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds initial JMAP support by introducing protocol-aware client creation, new console commands, CI integration for a JMAP-capable test server, and the necessary persistence changes.

Changes:

  • Add protocol routing via ProtocolFactory plus a new JmapClientFactory.
  • Introduce new/updated console commands for creating and testing IMAP/JMAP accounts, with aliases for backwards compatibility.
  • Add DB migrations + entity fields for JMAP-related identifiers and account protocol/path, plus integration/unit tests and CI updates.

Reviewed changes

Copilot reviewed 25 out of 27 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/Unit/Command/CreateJmapAccountTest.php Unit tests for the new JMAP account creation command.
tests/Unit/Command/CreateImapAccountTest.php Updates unit tests to reflect renamed IMAP account creation command + alias.
tests/Integration/Protocol/ProtocolFactoryJmapTest.php Integration test for establishing a JMAP client connection via ProtocolFactory.
tests/Integration/Protocol/ProtocolFactoryImapTest.php Integration test for establishing an IMAP client connection via ProtocolFactory.
tests/Integration/Framework/JmapTestAccount.php Test fixture to create/persist a JMAP test account for integration tests.
tests/Integration/Db/MailAccountTest.php Extends DB/API serialization tests for new protocol and path fields.
package.json Bumps app package version.
package-lock.json Bumps lockfile version metadata to match package version.
lib/Protocol/ProtocolFactory.php Adds protocol-gated IMAP/JMAP client creation and connector resolution scaffolding.
lib/Migration/Version5800Date20260401000001.php Adds protocol and path columns to mail_accounts.
lib/Migration/Version5800Date20260401000002.php Adds JMAP-related columns (rpid, rid, state) to mail_mailboxes.
lib/Migration/Version5800Date20260401000003.php Adds rid column to mail_messages.
lib/JMAP/JmapClientFactory.php New factory to build authenticated JMAP clients from account configuration.
lib/Db/Message.php Adds rid field accessors/storage to message entity.
lib/Db/Mailbox.php Adds rpid/rid/state fields accessors/storage to mailbox entity.
lib/Db/MailAccount.php Adds protocol + path fields/constants and includes them in JSON output.
lib/Contracts/ITransmissionConnector.php Introduces protocol-agnostic transmission connector interface.
lib/Contracts/IMessageConnector.php Introduces protocol-agnostic message connector interface.
lib/Contracts/IMailboxConnector.php Introduces protocol-agnostic mailbox connector interface.
lib/Command/TestAccount.php Adds unified IMAP/JMAP connection test command (aliasing the old diagnose name).
lib/Command/DiagnoseAccount.php Removes old IMAP-only diagnose command implementation.
lib/Command/CreateJmapAccount.php Adds console command to create JMAP accounts.
lib/Command/CreateImapAccount.php Renames IMAP create command and adds an alias for the previous command name.
composer.lock Adds the JMAP client dependency (and transitive deps) to the lockfile.
composer.json Adds VCS repository + requires the JMAP client package.
appinfo/info.xml Registers new/renamed commands and bumps app version.
.github/workflows/test.yml Updates integration test services to include a JMAP-capable mail server and setup steps.

Comment thread lib/JMAP/JmapClientFactory.php
Comment thread .github/workflows/test.yml
*/
private function resolveConnector(Account $account, string $interface): mixed {
$protocol = $account->getMailAccount()->getProtocol();
$class = self::CONNECTOR_MAP[$protocol][$interface] ?? null;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that work with the map commented out? 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it works with the map commented out,

Part 2 has the actual references.

Comment thread lib/Migration/Version5800Date20260401000001.php
$schema = $schemaClosure();
$mailboxesTable = $schema->getTable('mail_mailboxes');
if (!$mailboxesTable->hasColumn('rpid')) {
$mailboxesTable->addColumn('rpid', Types::STRING, [
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a fan of self-speaking variable names, and hence I would prefer a name that is easy to match to the specification.

rpid = remote_parent_id
r = remote_id

Looking at the sample at https://jmap.io/spec/rfc8621/#section-2.6, state is outside the list and should be the same for all mailboxes, or does that state depends on the requested mailboxes?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I can change the field names.

As for the state, yes jmap has a unified state for all changes. You can sync all messages from all mailboxes with a single delta request or per mailbox BUT we handle sync on a per mailbox basis that means I need to know the last sync state for that mailbox, hence the per mailbox state.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed the field names

/**
* @psalm-api
*/
#[AddColumn(table: 'mail_accounts', name: 'protocol', type: ColumnType::STRING)]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also list path so the list is complete

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@kesselb
Copy link
Copy Markdown
Contributor

kesselb commented Apr 24, 2026

@kesselb
Copy link
Copy Markdown
Contributor

kesselb commented Apr 24, 2026

jmap-client currently requires guzzlehttp as dependency.

Issue 1: While guzzle is already a dependency for server via 3rdparty, guzzle will be installed another time here leadiing to another copy of guzzle.

Issue 2: Not using the nextcloud http client means our protections like ssrf, dns pinning, configuration of proxies through config.php are not applied.

Solution:

Comment thread lib/Db/MailAccount.php Outdated
'debug' => $this->getDebug(),
'classificationEnabled' => $this->getClassificationEnabled(),
'imipCreate' => $this->getImipCreate(),
'protocol' => $this->getProtocol() ?? self::PROTOCOL_IMAP,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'protocol' => $this->getProtocol() ?? self::PROTOCOL_IMAP,
'protocol' => $this->getProtocol(),

(default already in declaration, proctol cannot be null)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Signed-off-by: SebastianKrupinski <[email protected]>
@SebastianKrupinski
Copy link
Copy Markdown
Contributor Author

SebastianKrupinski commented Apr 24, 2026

jmap-client currently requires guzzlehttp as dependency.

Issue 1: While guzzle is already a dependency for server via 3rdparty, guzzle will be installed another time here leadiing to another copy of guzzle.

Issue 2: Not using the nextcloud http client means our protections like ssrf, dns pinning, configuration of proxies through config.php are not applied.

Solution:

* jmap-client: Add https://packagist.org/packages/psr/http-client, remove guzzle, change change to code to use the interface.

* mail: Currently only the http client in Nextcloud 34 also implements the client interface, hence we need a wrapper in mail to exposes a http client implementing ClientInterface. For example like https://github.com/nextcloud/bookmarks/blob/master/lib/Http/Client.php.

I agree this is a good upgrade for the client, but this will have to wait, due to the time available for the implementation of this I cannot redesign the jmap-client at the moment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: 🏗️ In progress

Development

Successfully merging this pull request may close these issues.

JMAP Protocol Abstraction and Routing Infrastructure

4 participants