Migrating from Legacy

This guide is for system administrators upgrading from the legacy Nextcloud Exchange Connector to the new architecture.

The new architecture represents a complete paradigm shift. The synchronization engine is no longer restricted by Nextcloud's internal cron jobs. It is now a powerful, decoupled microservice.

Because of this architectural upgrade, the way you configure the application has completely changed. You will no longer edit docker-compose.yml or appsettings.json. All settings have been unified into a single .env file. Exchange and Nextcloud connections are no longer a flat Service__* block: you declare sync clients (EventSyncClients) and a pair that points at them. A separate admins.json file is optional and only needed if you want a pool of Exchange admin accounts.

Prerequisites

Before starting the migration process:

  1. Back up your database: Always create a full snapshot or dump of your existing database before performing major upgrades.

  2. Keep your old config files handy: Do not delete your old docker-compose.yml or appsettings.json yet. You will need to copy the values from them during the mapping phase.

  3. Read the following article, how it impacts your users LINK.

Upgrade steps

Complete these steps in this order:

  1. Stop the Nextcloud Exchange Connector service. Make sure it is not running before you continue.

  2. Update the Sendent Sync app in Nextcloud to version 2.1.0.

  3. Pull the new Exchange Connector image using the 2.0.0 tag (or latest).

  4. Apply the configuration changes described this migration article.
    Check that all required setting changes are in place.

  5. Start the Nextcloud Exchange Connector service.

  6. Inform your users that they will be asked to give consent again, and that they should accept the calendar recreation notice.

Step 1. Disable Legacy Background Tasks

If the legacy background synchronization tasks and the new worker run simultaneously, they will overwrite each other, causing massive duplication of calendar events and contacts.

You must stop the old synchronization engine from executing. Depending on how your legacy application was configured, you need to remove or disable the specific cron jobs (scheduled tasks) on your Nextcloud server that trigger the old synchronization scripts.

Warning: Absolute Prerequisite

Do not proceed with the installation of a new connector version until you are 100% certain the legacy application is disabled and its background cron jobs are no longer executing.

Step 2. Preserve Your Database

The new application is fully compatible with your existing database. Because the user tokens and synchronization watermarks are preserved in the old tables, migrating your database connection ensures a seamless transition:

  • No massive "Initial Sync": The system will simply pick up exactly where it left off.

  • No user re-consent required: Your Nextcloud users do not need to click the "Grant Access" button again. Their existing consent tokens will continue to work flawlessly.

Step 3. Prepare the Deployment Files

Navigate to the Docker Compose Deployment Files article and copy the required .yml configurations. Save them as standard text files inside your new directory, ensuring the filenames match exactly.

You do not need to alter the docker-compose.yml file.

All necessary configurations are managed entirely through environment variables (.env file).

The provided configuration will automatically pull the latest official container image from the public repository: rg.nl-ams.scw.cloud/sendent-public/sendent-sync:latest

Step 4. The Configuration Mapping

You must manually translate your old configuration values into the new architecture using the .env file. An admins.json file is optional (Step 4.3)

Example: Before and After

To understand the change, here is a simplified example comparing the legacy configuration approach to the new .env file structure.

Before (Legacy docker-compose.yml environment block):

environment:
- Service__NextcloudBaseUrl=https://nc01.sendent.dev
- Service__NextcloudServiceUsername=admin
- Service__MicrosoftTenantId=df6e89a5-...
- Service__ExchangeType=1
- ConnectionStrings__DatabaseConnectionString=Host=sendent.synchronisation.db;Port=5432;...

After (New .env file):

DatabaseConfiguration__ConnectionString=Host=sendent.synchronisation.db;Port=5432;...
EventSyncClients__0__Identifier=ews
EventSyncClients__0__Provider=Ews
EventSyncClients__0__Settings__ExchangeType=1
EventSyncClients__0__Settings__TenantId=df6e89a5-...
EventSyncClients__0__Settings__AppId=your-app-id
EventSyncClients__0__Settings__ClientSecret=your-client-secret
EventSyncClients__1__Identifier=dav
EventSyncClients__1__Provider=Dav
EventSyncClients__1__Settings__BaseUrl=https://nc01.sendent.dev
EventSyncClients__1__Settings__ServiceUsername=admin
EventSyncClients__1__Settings__ServicePassword=your-password
EventSyncClients__1__Settings__SharedSecret=your-shared-secret
EventSync__Pairs__0__SourceClientId=ews
EventSync__Pairs__0__TargetClientId=dav
EventSync__CalendarPriorityOrder__0=ews
EventSync__CalendarPriorityOrder__1=dav

Copy only the combination that matches your deployment. The blocks for Microsoft 365 EWS, Microsoft 365 Graph, and on-premise Kerberos are in Step 4.2.

Step 4.1: General Settings (Move to .env)

Open your new .env file and map your old appsettings.json or docker-compose.yml variables exactly as follows:

Old Variable

New Variable

Service__BatchLimit / Service:BatchLimit

Service__BatchLimit

Service__StartWeekend / Service:StartWeekend

Service__StartWeekend

Service__SyncMode / Service:SyncMode

Service__SyncMode

Service__NextcloudBaseUrl / Service:NextcloudBaseUrl

EventSyncClients__1__Settings__BaseUrl

Service__NextcloudServiceUsername / Service:NextcloudServiceUsername

EventSyncClients__1__Settings__ServiceUsername

Service__NextcloudServicePassword / Service:NextcloudServicePassword

EventSyncClients__1__Settings__ServicePassword

Service__SharedSecret / Service:SharedSecret

EventSyncClients__1__Settings__SharedSecret

Service__ExchangeType / Service:ExchangeType

EventSyncClients__0__Settings__ExchangeType

Service__ExchangeOnPremUrl / Service:ExchangeOnPremUrl

EventSyncClients__0__Settings__OnPremUrl

Service__ExchangeOnPremDomain / Service:ExchangeOnPremDomain

EventSyncClients__0__Settings__OnPremDomain

Service__DatabaseEncryptionKey / Service:DatabaseEncryptionKey

DatabaseConfiguration__DatabaseEncryptionKey

ConnectionStrings__DatabaseConnectionString / ConnectionStrings:DatabaseConnectionString

DatabaseConfiguration__ConnectionString

Service__MaxParallelProcessingUsers / Service:MaxParallelProcessingUsers

Service__ConcurrencyConfiguration__MaxParallelProcessingUsers

Service__IntervalRefreshInMinutes / Service:IntervalRefreshInMinutes

Service__SyncIntervalInSeconds (Note: You must convert your old minute value into seconds. For example, 1 minute becomes 60 seconds).

Service__MicrosoftTenantId

EventSyncClients__0__Settings__TenantId

Service__MicrosoftAppId

EventSyncClients__0__Settings__AppId

Service__MicrosoftClientSecret

EventSyncClients__0__Settings__ClientSecret

Service__ExchangeOnPremUsername

EventSyncClients__0__Settings__UserName

Service__ExchangeOnPremPassword

EventSyncClients__0__Settings__Password

Indexes __0__ / __1__ match the examples: 0 = Exchange (EWS or Graph), 1 = Nextcloud (DAV). ExchangeType, OnPremUrl, and OnPremDomain apply to EWS only. Graph uses TenantId / AppId / ClientSecret and has no on-prem URL.

Note: Obsolete Variables

You can safely ignore variables like Service__OsVersion andService__ApplicationVersion. They have been removed or hardcoded into the new application defaults.

Warning: Strict File Naming

The environment file must be named exactly .env with no prefix. Naming the file like settings.env will cause Docker to fail to read the configuration.

Step 4.2: Sync clients (required)

The mapping in Step 4.1 is not enough on its own. You must declare the clients and a pair. Identifier values must match EventSync__Pairs.

Provider is Ews, Dav, or Graph. Settings depend on that provider.

Copy one Exchange-side block (A, B, or C) plus the DAV client. Do not mix A/B/C in the same .env.

Do not use EventSyncClients__*__Settings__ExchangeOnPremUrl or ExchangeOnPremDomain. Those names are the old global format. On an EWS on-premise client the keys are OnPremUrl and OnPremDomain.

A. Microsoft 365 via EWS

Use this if the old config had Service__ExchangeType=1 and you are staying on EWS.

EventSyncClients__0__Identifier=ews
EventSyncClients__0__Provider=Ews
EventSyncClients__0__Settings__ExchangeType=1
EventSyncClients__0__Settings__TenantId=yyy
EventSyncClients__0__Settings__AppId=zzz
EventSyncClients__0__Settings__ClientSecret=xxx
 
EventSyncClients__1__Identifier=dav
EventSyncClients__1__Provider=Dav
EventSyncClients__1__Settings__BaseUrl=https://cloud.example.com/
EventSyncClients__1__Settings__ServiceUsername=sync-admin
EventSyncClients__1__Settings__ServicePassword=xxx
EventSyncClients__1__Settings__SharedSecret=xxx
 
EventSync__Pairs__0__SourceClientId=ews
EventSync__Pairs__0__TargetClientId=dav
EventSync__CalendarPriorityOrder__0=ews
EventSync__CalendarPriorityOrder__1=dav

Do not put ExchangeAdminRaw under EventSyncClients__*__Settings. A single app registration is enough on the client. An admin JSON array, if you still use one, stays at Service__ExchangeConfiguration__ExchangeAdminRaw (see Step 4.3).

B. Microsoft 365 via Graph API

Graph is a different Provider, not another ExchangeType. Use this only after you registered a Graph app. Tenant can stay the same; AppId and ClientSecret are the Graph app, not the old EWS app.

DAV keys are the same as in A.

EventSyncClients__0__Identifier=graph
EventSyncClients__0__Provider=Graph
EventSyncClients__0__Settings__TenantId=yyy
EventSyncClients__0__Settings__AppId=zzz
EventSyncClients__0__Settings__ClientSecret=xxx
 
EventSyncClients__1__Identifier=dav
EventSyncClients__1__Provider=Dav
EventSyncClients__1__Settings__BaseUrl=https://cloud.example.com/
EventSyncClients__1__Settings__ServiceUsername=sync-admin
EventSyncClients__1__Settings__ServicePassword=xxx
EventSyncClients__1__Settings__SharedSecret=xxx
 
EventSync__Pairs__0__SourceClientId=graph
EventSync__Pairs__0__TargetClientId=dav
EventSync__CalendarPriorityOrder__0=graph
EventSync__CalendarPriorityOrder__1=dav

Graph has no ExchangeType, OnPremUrl, or OnPremDomain.

C. On-premise Exchange (Kerberos)

Use this if the old config had Service__ExchangeType=2. Graph does not apply on-premise.

EventSyncClients__0__Identifier=ews
EventSyncClients__0__Provider=Ews
EventSyncClients__0__Settings__ExchangeType=2
EventSyncClients__0__Settings__OnPremUrl=https://exchange.example.com/EWS/Exchange.asmx
EventSyncClients__0__Settings__OnPremDomain=example.com
EventSyncClients__0__Settings__UserName=sync-admin
EventSyncClients__0__Settings__Password=xxx
 
EventSyncClients__1__Identifier=dav
EventSyncClients__1__Provider=Dav
EventSyncClients__1__Settings__BaseUrl=https://cloud.example.com/
EventSyncClients__1__Settings__ServiceUsername=sync-admin
EventSyncClients__1__Settings__ServicePassword=xxx
EventSyncClients__1__Settings__SharedSecret=xxx
 
EventSync__Pairs__0__SourceClientId=ews
EventSync__Pairs__0__TargetClientId=dav
EventSync__CalendarPriorityOrder__0=ews
EventSync__CalendarPriorityOrder__1=dav

ExchangeType=3 (Basic Auth) uses the same client keys as Kerberos. ExchangeType=4 (ADFS) uses OnPremUrl, OnPremAdfsAuthorityUrl, ClientId, and ClientSecret instead of UserName / Password.

Step 4.3: Administrator Credentials (optional - admins.json)

If you already set AppId / ClientSecret (cloud) or UserName / Password (on-premise) on the Exchange client in Step 4.2, the connector can run with that single account. Create admins.json only when you need several Exchange admin identities to spread load and avoid throttling.

  1. Inside your deployment folder, create a directory named exchangeAdmins.

  2. Create a file named admins.json inside it.

  3. If you need a pool, put extra admin identities in this JSON array. The first account can stay on the client in Step 4.2; extra accounts go here.

For Cloud (Microsoft 365) setups
  • Move Service__MicrosoftAppId to AppId

  • Move Service__MicrosoftClientSecret to ClientSecret

Example:

[
{
"AppId": "your_old_MicrosoftAppId_value",
"ClientSecret": "your_old_MicrosoftClientSecret_value"
}
]
For On-Premise (Kerberos/BasicAuth) setups
  • Move Service__ExchangeOnPremUsername to Username

  • Move Service__ExchangeOnPremPassword to Password

Example:

[
{
"Username": "your_old_ExchangeOnPremUsername_value",
"Password": "your_old_ExchangeOnPremPassword_value"
}
]

admins.json and Service__ExchangeConfiguration__ExchangeAdminRaw feed the admin pool. They do not replace client Settings such as OnPremUrl, OnPremDomain, or TenantId. Those stay on EventSyncClients.

Do not put ExchangeAdminRaw under EventSyncClients__*__Settings. If you use an inline JSON array, the key is Service__ExchangeConfiguration__ExchangeAdminRaw.

For a Graph pool, each object also needs "Provider": "Graph". Without that field the pool is treated as EWS.

Refer to Managing Service Accounts (admins.json) article to know more about managing service accounts.

Step 4.4: New Configuration Variables

The new architecture introduces several settings that were not present in the legacy appsettings.json or docker-compose.yml configurations. These new parameters provide granular control over scaling, data privacy, and logging.

You can configure these in your new .env file.

Deployment & Instance Settings

Parameter

Meaning & Usage

Example

Service__IsPrimary

Meaning: Dictates if this instance is the primary synchronization coordinator.

Usage: Exactly one instance must be set to true.

true

Service__DeploymentType

Meaning: Specifies the hosting method.

Usage: Set to 0 for Docker or 1 for Binary deployments.

0

Service__DefaultWorkerName

Meaning: The unique identifier for the instance.

Usage: Used in logs and database tracking to identify which worker processed a task.

"SendentWorker-1"

DatabaseConfiguration__DatabaseType

Meaning: Selects the database provider.

Usage: 0=Postgres, 1=SqlServer, 2=MariaDB, 3=MySql.

0

Advanced Synchronization Control

Parameter

Meaning & Usage

Example

Service__SyncType

Meaning: Level of detail for synchronization.

Usage: Set to 0 for Full sync (all data), or 1 for Sensitive sync (hides confidential details).

1

Service__WorkerIntervalSeconds

Meaning: The sleep interval between global application runs.

Usage: Determines how often the application checks the database for new users to process.

120

Service__CriticalSyncIntervalInSeconds

Meaning: The retry delay for users whose synchronization failed.

Usage: Gives the system time to recover before retrying a broken sync.

100

Service__BatchSaveSize

Meaning: Database transaction chunk size.

Usage: Maximum number of objects saved to the database in a single transaction.

100

Service__FullSyncConfiguration__ProcessAttachments

Meaning: Allows or disallows processing of attachments.

Usage: Applicable only when Service__SyncType=0 (Full Sync).

true

Service__SensitiveSyncConfiguration__SensitiveTitle

Meaning: Custom title applied to events in Exchange.

Usage: Hides meeting details when Service__SyncType=1 (Sensitive Sync).

"Busy"

Service__SensitiveSyncConfiguration__SensitiveCategory

Meaning: Custom category tag applied to events.

Usage: Categorizes events originating from Nextcloud in the user's Exchange calendar.

"Nextcloud Sync"

Service__ExchangeConfiguration__ExchangeAdminFile

Meaning: Path to your JSON file containing Exchange service accounts.

Usage: Points to the admins.json created in Step 4.3

admins.json

EventSync__Pairs__0__SourceClientId

Meaning: The Exchange-side client in the sync pair.

Usage: Must match an EventSyncClients Identifier (ews or graph).

ews

EventSync__Pairs__0__TargetClientId

Meaning: The Nextcloud-side client in the sync pair.

Usage: Must match the DAV client's Identifier.

dav

Concurrency & Scaling

Parameter

Meaning & Usage

Example

Secondary_Replicas_Amount

Meaning: The number of secondary containers to deploy automatically.

Usage: Scales your synchronization power (Docker only).

2

Service__ConcurrencyConfiguration__MaxUsersPerAdmin

Meaning: Maximum users processed concurrently by a single Exchange service account.

Usage: Prevents Microsoft Exchange from throttling your connections.

10

Logging Configuration

Parameter

Meaning & Usage

Example

Serilog__MinimumLevel__Default

Meaning: The global log verbosity level.

Usage: Acceptable values are Debug, Information,Warning, Error.

Error

Service__LoggingConfiguration__LogsOutput

Meaning: Determines where logs are stored (Bitwise mask).

Usage: 1=Console, 2=Database, 4=File, 8=Grafana. Combine by summing.

5

Service__LoggingConfiguration__LogDirectoryPath

Meaning: Name of the folder where file logs are saved.

Usage: Required if using File Output (value 4).

SendentLogs

Service__LoggingConfiguration__LogFileSizeLimit

Meaning: Maximum size of a single log file.

Usage: Expressed in Megabytes (MB).

50

Step 5. Start the New Worker

Once your .env file is fully mapped (and admins.json, if you created one) within your extracted deployment folder (from Step 3), and your legacy background processes are disabled, you can safely start the new worker.

The Sendent deployment is modular. You must build your startup command by appending -f flags depending on which database and monitoring tools you want Docker to host for you.

Build your command by starting with the base file, appending your optional components, and ending with up:

1. The Base Command (Required): docker compose -f docker-compose.yml

2. Append a Database Container (Skip if you're using an external database)

  • For PostgreSQL: -f docker-compose.postgres.yml

  • For MariaDB: -f docker-compose.mariadb.yml

  • For SQL Server: -f docker-compose.sqlserver.yml

3. Append Monitoring (Optional):

  • For Loki & Grafana: -f docker-compose.grafana.yml

Examples of complete commands:

Running with a local PostgreSQL container and Grafana:

docker compose -f docker-compose.yml -f docker-compose.postgres.yml -f docker-compose.grafana.yml up

Running with an external database (no local DB container) and no Grafana:

docker compose -f docker-compose.yml up

The application will automatically connect to your existing database, read the old tokens, and seamlessly resume synchronizing user data.

Step 6. Configure Logging

The logging architecture has been completely redesigned. You will no longer define complex Serilog objects in appsettings.json. Instead, you control logging through bitwise values in the .env file.

For comprehensive instructions on configuring alternative logging methods (such as Grafana or File System), please refer to Logging & Monitoring.

Clean-up

After verifying that the new worker is running and synchronization is occurring smoothly via the logs, safely archive or delete your old docker-compose.yml and appsettings.json files to prevent future configuration confusion.


Was this article helpful?