Skip to content

Connect Nextcloud once: files, calendars and contacts from one connection #158

Open
0 of 1 issue completed

Description

@karatektus

Decided: a Nextcloud connection is made once, with one app password, and gives a person their files, their calendars and their contacts. This is a must-have for v0.4.0, and the only way Nextcloud calendars and contacts will come into plMail.

Today

What it should be

  • One Nextcloud connection, made where it is made today. Its credential, a username and an app password, is the one used for all three: Nextcloud serves CalDAV and CardDAV under the same /remote.php/dav with the same app password.
  • Once connected, Settings → Calendars lists that account's calendars and Settings → Contacts its address books, to tick and mirror, without asking for an address or a password again. Disconnecting Nextcloud removes all three.
  • The server address comes from the existing connection, so from the administrator's pinned address when there is one. No new environment variable, no second address setting, no second private-network exemption.
  • The generic CalDAV and CardDAV connections stay for servers that are not Nextcloud (Radicale, Baïkal, Fastmail, iCloud…).

To settle here before code

  • How a calendar or address book records the connection it came from, so disconnecting removes it and a changed app password reaches all three.
  • What happens to an existing separate CalDAV connection to the same Nextcloud: left alone, or offered a move to the Nextcloud connection.
  • The order of work: the calendar half can land on its own; contacts follow CardDAV: mirror address books, suggest contacts while composing, edit with two-way sync #109's CardDAV engine.

Related

Activity

  1. added
    enhancementNew feature or a change to how something works
    needs-triageNew; not yet looked at by a maintainer
    acceptedAgreed to be done; may be worked on
    and removed
    needs-triageNew; not yet looked at by a maintainer
    on Oct 15, 2026
  2. added this to the v0.4.0 milestone on Oct 15, 2026
  3. changed the title [-]One-click Nextcloud calendar connection when NEXTCLOUD_URL is set[/-] [+]Connect Nextcloud once: files, calendars and contacts from one connection[/+] on Oct 15, 2026
  4. jo2904 commented on Oct 15, 2026

    @jo2904
    Contributor

    Thanks for writing this up. Here is how I'd answer the three open questions, from reading the current model; tell me where you disagree before I start.

    1. How a calendar or address book records its connection

    It hangs off the Nextcloud Integration row itself: calendar.integration_id (and address_book.integration_id in #109) points at the Nextcloud integration, not at a second CalDAV/CardDAV row. Both FKs are already ON DELETE CASCADE and the uniqueness is already (integration_id, remote_id), so no new table or column:

    • Disconnecting Nextcloud removes files, calendars and address books through the cascade. The disconnect confirmation should say how many calendars and address books go with it.
    • A changed app password reaches all three, because there is only one secret.
    • The address comes from IntegrationUrlValidator::resolve() with the Nextcloud provider config, so the admin's pinned address wins and its existing private-network exemption applies. No new variable, no second address setting.

    In code:

    • CalDavCalendarDriver::supports() accepts Provider::Nextcloud for a CalendarSource, but not for a bare Provider, so Nextcloud never shows up in the "connect a CalDAV server" form.
    • Discovery starts from {base}/remote.php/dav (NextcloudDriver::DAV_ROOT) instead of .well-known.
    • CalDavClient::baseUrl() reads the config of $integration->provider instead of hard-coding Provider::CalDav.
    • A predicate on Provider (something like offersDav(DavService): Nextcloud → calendars and address books, CalDav → calendars, CardDav → address books) decides which connections Settings → Calendars and Settings → Contacts list. Nextcloud's kind() stays Files.
    • Error sentences are chosen by DAV service (calendar or address book), not by provider, since Nextcloud serves both. For Nextcloud they point at Settings → Integrations.

    2. An existing separate CalDAV connection to the same Nextcloud

    Left alone, then offered a move.

    • Guard, in the first PR: the calendar list shows a remote calendar that another of the user's connections already mirrors (same remoteId) as "already synced via …", and it can't be ticked. The unique index is per integration, so on its own it wouldn't stop the duplicate.
    • Move, in a follow-up PR: when a CalDAV connection's host is the Nextcloud integration's host, Settings → Calendars offers to attach its calendars to the Nextcloud connection. That re-points calendar.integration_id and deletes the old row. remoteIds are absolute URLs, so events, sync tokens and colours survive without a full re-sync.

    3. Order of work

    1. Calendar half (new PR): it brings in the DavService/DavResource refactor of CalDavDiscovery from CardDAV: mirror address books, suggest contacts while composing, edit with two-way sync #109, so both halves share it. Then Nextcloud support in the CalDAV driver, the calendar list in Settings → Calendars, the duplicate guard and the disconnect confirmation.
    2. CardDAV: mirror address books, suggest contacts while composing, edit with two-way sync #109 rebased onto it: standalone CardDAV stays for Radicale, Baïkal, Fastmail and iCloud. Nextcloud address books hang off the Nextcloud integration the same way. Plus messages.fr.yaml.
    3. Follow-up: the "attach to Nextcloud" move.

    I'll close #108 once the calendar PR is open, since almost none of its diff survives this design.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedAgreed to be done; may be worked onarea: calendarConcerns calendararea: mailConcerns mailenhancementNew feature or a change to how something works

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions